From f9188bb46f8a43a37233dd0802b1cb787437564a Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Wed, 7 Jan 2026 09:39:46 +0000 Subject: [PATCH 1/3] - implement user settings reset functionality with modal confirmation and bug fixes regarding lane IDs to sighting endpoints --- package.json | 2 +- src/App.tsx | 31 +++++----- .../SettingForms/SettingForms.tsx | 52 ++++++++++------ .../System/SystemConfigFields.tsx | 18 +++--- .../resetUserSettings/ResetUserSettings.tsx | 26 ++++++++ .../ResetUserSettingsModal.tsx | 59 +++++++++++++++++++ src/components/UI/ModalComponent.tsx | 5 +- src/context/WebsocketContext.ts | 33 ----------- src/context/providers/WebSocketProvider.tsx | 40 ------------- src/context/reducers/SoundContextReducer.ts | 2 + src/hooks/useCameraOutput.ts | 36 ++++++----- src/hooks/useSightingAmend.ts | 16 ++--- src/types/types.ts | 6 +- 13 files changed, 186 insertions(+), 140 deletions(-) create mode 100644 src/components/SettingForms/System/resetUserSettings/ResetUserSettings.tsx create mode 100644 src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx delete mode 100644 src/context/WebsocketContext.ts delete mode 100644 src/context/providers/WebSocketProvider.tsx diff --git a/package.json b/package.json index 0bfd5a8..9db8683 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "in-car-system-fe", "private": true, - "version": "1.0.24", + "version": "1.0.25", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.tsx b/src/App.tsx index d7d0262..5aaa5a7 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -9,28 +9,25 @@ import { IntegrationsProvider } from "./context/providers/IntegrationsContextPro import { AlertHitProvider } from "./context/providers/AlertHitProvider"; import { SoundProvider } from "react-sounds"; import SoundContextProvider from "./context/providers/SoundContextProvider"; -import WebSocketProvider from "./context/providers/WebSocketProvider"; function App() { return ( - - - - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - + + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + ); diff --git a/src/components/SettingForms/SettingForms/SettingForms.tsx b/src/components/SettingForms/SettingForms/SettingForms.tsx index 42b89fd..badc1bf 100644 --- a/src/components/SettingForms/SettingForms/SettingForms.tsx +++ b/src/components/SettingForms/SettingForms/SettingForms.tsx @@ -1,7 +1,7 @@ import { Form, Formik } from "formik"; import BearerTypeCard from "../BearerType/BearerTypeCard"; import ChannelCard from "../Channel1-JSON/ChannelCard"; -import { useCameraOutput, useGetDispatcherConfig } from "../../../hooks/useCameraOutput"; +import { useCameraOutput, useGetDispatcherConfig, useLaneIds } from "../../../hooks/useCameraOutput"; import type { BearerTypeFieldType, InitialValuesForm, @@ -14,25 +14,37 @@ import { useUpdateBackOfficeConfig } from "../../../hooks/useBackOfficeConfig"; import { useFormVaidate } from "../../../hooks/useFormValidate"; import { useSightingAmend } from "../../../hooks/useSightingAmend"; import StoreCard from "../Store/StoreCard"; +import { useEffect } from "react"; const SettingForms = () => { const qc = useQueryClient(); - const { dispatcherQuery, dispatcherMutation, backOfficeDispatcherMutation, bof2LandMutation, laneIdQuery } = - useCameraOutput(); + const { dispatcherQuery, dispatcherMutation, backOfficeDispatcherMutation } = useCameraOutput(); + const { laneIdQuery: laneIDAQuery, laneIdMutation: laneIDAMutation } = useLaneIds("A"); + const { laneIdQuery: laneIDBQuery, laneIdMutation: laneIDBMutation } = useLaneIds("B"); const { backOfficeMutation } = useUpdateBackOfficeConfig(); const { bof2ConstantsQuery } = useGetDispatcherConfig(); const { validateMutation } = useFormVaidate(); - const { sightingAmendQuery, sightingAmendMutation } = useSightingAmend(); + const { sightingAmendQuery: sightingAmendAQuery, sightingAmendMutation: sightingAmendMutationA } = + useSightingAmend("A"); + const { sightingAmendMutation: sightingAmendMutationB } = useSightingAmend("B"); + + useEffect(() => { + async function fetchData() { + await Promise.all([laneIDAQuery.refetch(), laneIDBQuery.refetch()]); + } + fetchData(); + }, [laneIDAQuery, laneIDBQuery]); const format = dispatcherQuery?.data?.propFormat?.value; const enabled = dispatcherQuery?.data?.propEnabled?.value; - const sightingQuality = sightingAmendQuery?.data?.propOverviewQuality?.value; - const cropSizeFactor = sightingAmendQuery?.data?.propOverviewImageScaleFactor?.value; + const sightingQualityA = sightingAmendAQuery?.data?.propOverviewQuality?.value; + const cropSizeFactorA = sightingAmendAQuery?.data?.propOverviewImageScaleFactor?.value; - const laneID = laneIdQuery?.data?.id; - const LID1 = laneIdQuery?.data?.propLaneID1?.value; - const LID2 = laneIdQuery?.data?.propLaneID2?.value; + const laneIDA = laneIDAQuery?.data?.id; + + const LID1 = laneIDAQuery?.data?.propLaneID1?.value; + const LID2 = laneIDBQuery?.data?.propLaneID1?.value; const FFID = bof2ConstantsQuery?.data?.propFeedIdentifier?.value; const SCID = bof2ConstantsQuery?.data?.propSourceIdentifier?.value; @@ -50,8 +62,8 @@ const SettingForms = () => { password: "", connectTimeoutSeconds: Number(5), readTimeoutSeconds: Number(15), - overviewQuality: sightingQuality ?? "HIGH", - cropSizeFactor: cropSizeFactor ?? "3/4", + overviewQuality: sightingQualityA ?? "HIGH", + cropSizeFactor: cropSizeFactorA ?? "3/4", // Bof2 - optional constants FFID: FFID ?? "", @@ -60,7 +72,7 @@ const SettingForms = () => { GPSFormat: GPSFormat ?? "", //BOF2 - optional Lane IDs - laneId: laneID ?? "", + laneId: laneIDA ?? "", LID1: LID1 ?? "", LID2: LID2 ?? "", }; @@ -101,7 +113,8 @@ const SettingForms = () => { if (validResponse?.reason === "OK") { await backOfficeMutation.mutateAsync(values); - await sightingAmendMutation.mutateAsync(values); + await sightingAmendMutationA.mutateAsync(values); + await sightingAmendMutationB.mutateAsync(values); if (values.format.toLowerCase() === "bof2") { const bof2ConstantsData: OptionalBOF2Constants = { @@ -111,12 +124,17 @@ const SettingForms = () => { GPSFormat: values.GPSFormat, }; - const bof2LaneData: OptionalBOF2LaneIDs = { - laneId: laneIdQuery?.data?.id, + const bof2LaneAData: OptionalBOF2LaneIDs = { + laneId: laneIDAQuery?.data?.id, LID1: values.LID1, - LID2: values.LID2, }; - await bof2LandMutation.mutateAsync(bof2LaneData); + const bof2LaneBData: OptionalBOF2LaneIDs = { + laneId: laneIDBQuery?.data?.id, + LID1: values.LID2, + }; + + await laneIDAMutation.mutateAsync(bof2LaneAData); + await laneIDBMutation.mutateAsync(bof2LaneBData); await backOfficeDispatcherMutation.mutateAsync(bof2ConstantsData); } } else { diff --git a/src/components/SettingForms/System/SystemConfigFields.tsx b/src/components/SettingForms/System/SystemConfigFields.tsx index c343c8a..76bf378 100644 --- a/src/components/SettingForms/System/SystemConfigFields.tsx +++ b/src/components/SettingForms/System/SystemConfigFields.tsx @@ -8,6 +8,7 @@ import { useDNSSettings, useSystemConfig } from "../../../hooks/useSystemConfig" import { ValidateIPaddress, isUtcOutOfSync } from "../../../utils/utils"; import { toast } from "sonner"; import { useSystemStatus } from "../../../hooks/useSystemStatus"; +// import ResetUserSettings from "./resetUserSettings/ResetUserSettings"; const SystemConfigFields = () => { const { saveSystemSettings, systemSettingsData, saveSystemSettingsLoading } = useSystemConfig(); @@ -27,6 +28,7 @@ const SystemConfigFields = () => { localdate: localDate, localtime: localTime, }); + const syncTime = new Date(systemStatusQuery?.data?.SystemStatus?.synctime * 1000).toLocaleString(); const sntpInterval = systemSettingsData?.sntpInterval; const dnsPrimary = dnsQuery?.data?.propNameServerPrimary?.value; @@ -84,13 +86,6 @@ const SystemConfigFields = () => { > {({ values, errors, touched, isSubmitting }) => (
-
- {utcOutOfSync?.outOfSync ? ( - UTC is out of sync - ) : ( - UTC is in sync - )} -
+
+ {utcOutOfSync?.outOfSync ? ( + UTC is out of sync + ) : ( + UTC is in sync + )} +

Last Sync Time: {syncTime}

+
+ {/* */} )} diff --git a/src/components/SettingForms/System/resetUserSettings/ResetUserSettings.tsx b/src/components/SettingForms/System/resetUserSettings/ResetUserSettings.tsx new file mode 100644 index 0000000..ee7b087 --- /dev/null +++ b/src/components/SettingForms/System/resetUserSettings/ResetUserSettings.tsx @@ -0,0 +1,26 @@ +import { useState } from "react"; +import ResetUserSettingsModal from "./ResetUserSettingsModal"; + +const ResetUserSettings = () => { + const [isUserSettingsModalOpen, setIsUserSettingsModalOpen] = useState(false); + const handleResetButtonClick = () => { + setIsUserSettingsModalOpen(true); + }; + return ( + <> + + + + ); +}; + +export default ResetUserSettings; diff --git a/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx new file mode 100644 index 0000000..288ced7 --- /dev/null +++ b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx @@ -0,0 +1,59 @@ +import { useSoundContext } from "../../../../context/SoundContext"; +import ModalComponent from "../../../UI/ModalComponent"; + +type ResetUserSettingsModalProps = { + isUserSettingsModalOpen: boolean; + setIsUserSettingsModalOpen: (isOpen: boolean) => void; +}; + +const ResetUserSettingsModal = ({ + isUserSettingsModalOpen, + setIsUserSettingsModalOpen, +}: ResetUserSettingsModalProps) => { + const { state, dispatch } = useSoundContext(); + console.log(state); + const handleClose = () => { + setIsUserSettingsModalOpen(false); + }; + + const handleResetAll = () => { + // Logic to reset all user settings goes here + dispatch({ type: "RESET" }); + handleClose(); + }; + return ( + +
+

Reset All User Settings

+

Are you sure you want to reset all user settings to their default values?

+

This action cannot be undone.

+
+

+ This will RESET ALL user settings including{" "} + Uploaded user sounds , + Hotlist Sound hits and{" "} + NPED Sound hits. It will also reset any customized settings within + the app to their original defaults and Clear out the session data and{" "} + alert history sightings. +

+
+
+ + +
+
+
+ ); +}; + +export default ResetUserSettingsModal; diff --git a/src/components/UI/ModalComponent.tsx b/src/components/UI/ModalComponent.tsx index 9a56e83..f06f7cc 100644 --- a/src/components/UI/ModalComponent.tsx +++ b/src/components/UI/ModalComponent.tsx @@ -1,5 +1,6 @@ import type React from "react"; import Modal from "react-modal"; +import clsx from "clsx"; type ModalComponentProps = { isModalOpen: boolean; @@ -12,7 +13,9 @@ const ModalComponent = ({ isModalOpen, children, close }: ModalComponentProps) = void; - send?: (msg: string) => void; -}; - -type heatmapSocketState = { - data: null; - readyState: ReadyState; - sendJson: (msg: unknown) => void; - send?: (msg: string) => void; -}; - -export type WebsocketContextValue = { - info: InfoSocketState; - heatmap?: heatmapSocketState; -}; - -export const WebsocketContext = createContext(null); - -const useWebSocketContext = () => { - const ctx = useContext(WebsocketContext); - if (!ctx) throw new Error("useWebSocketContext must be used inside "); - return ctx; -}; - -export const useInfoBarSocket = () => useWebSocketContext().info; -export const useHeatmapSocket = () => useWebSocketContext().heatmap; diff --git a/src/context/providers/WebSocketProvider.tsx b/src/context/providers/WebSocketProvider.tsx deleted file mode 100644 index 6b448a8..0000000 --- a/src/context/providers/WebSocketProvider.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useEffect, useMemo, useState, type ReactNode } from "react"; -import type { InfoBarData } from "../../types/types"; -import useWebSocket from "react-use-websocket"; -import { ws_config } from "../../utils/ws_config"; -import { WebsocketContext, type WebsocketContextValue } from "../WebsocketContext"; - -type WebSocketProviderProps = { - children: ReactNode; -}; - -const WebSocketProvider = ({ children }: WebSocketProviderProps) => { - const [systemData, setSystemData] = useState(null); - - const infoSocket = useWebSocket(ws_config.infoBar, { share: true, shouldReconnect: () => true }); - - useEffect(() => { - async function parseData() { - if (infoSocket.lastMessage) { - const text = await infoSocket.lastMessage.data.text(); - const data = JSON.parse(text); - setSystemData(data); - } - } - parseData(); - }, [infoSocket.lastMessage]); - - const value = useMemo( - () => ({ - info: { - data: systemData, - readyState: infoSocket.readyState, - sendJson: infoSocket.sendJsonMessage, - }, - }), - [systemData, infoSocket.readyState, infoSocket.sendJsonMessage] - ); - return {children}; -}; - -export default WebSocketProvider; diff --git a/src/context/reducers/SoundContextReducer.ts b/src/context/reducers/SoundContextReducer.ts index 15e2139..13f3e8c 100644 --- a/src/context/reducers/SoundContextReducer.ts +++ b/src/context/reducers/SoundContextReducer.ts @@ -69,6 +69,8 @@ export function reducer(state: SoundState, action: SoundAction): SoundState { ...state, uploadedSound: action.payload, }; + case "RESET": + return initialState; default: return state; } diff --git a/src/hooks/useCameraOutput.ts b/src/hooks/useCameraOutput.ts index 4bdb9ca..568e05e 100644 --- a/src/hooks/useCameraOutput.ts +++ b/src/hooks/useCameraOutput.ts @@ -78,7 +78,11 @@ const updateBOF2LaneId = async (data: OptionalBOF2LaneIDs) => { }, { property: "propLaneID2", - value: data?.LID2, + value: data?.LID1, + }, + { + property: "propLaneID3", + value: data?.LID1, }, ], }; @@ -91,8 +95,8 @@ const updateBOF2LaneId = async (data: OptionalBOF2LaneIDs) => { return response.json(); }; -const getBOF2LaneId = async () => { - const response = await fetch(`${CAM_BASE}/api/fetch-config?id=SightingAmmendA-lane-ids`); +const getBOF2LaneId = async (cameraID: string) => { + const response = await fetch(`${CAM_BASE}/api/fetch-config?id=SightingAmmend${cameraID}-lane-ids`); if (!response.ok) throw new Error("Canot get Lane Ids"); return response.json(); }; @@ -124,16 +128,6 @@ export const useCameraOutput = () => { }, }); - const bof2LandMutation = useMutation({ - mutationKey: ["updateBOF2LaneId"], - mutationFn: updateBOF2LaneId, - }); - - const laneIdQuery = useQuery({ - queryKey: ["getBOF2LaneId"], - queryFn: getBOF2LaneId, - }); - useEffect(() => { if (dispatcherQuery.isError) toast.error(dispatcherQuery.error.message); }, [dispatcherQuery?.error?.message, dispatcherQuery.isError]); @@ -142,8 +136,6 @@ export const useCameraOutput = () => { dispatcherQuery, dispatcherMutation, backOfficeDispatcherMutation, - bof2LandMutation, - laneIdQuery, }; }; @@ -155,3 +147,17 @@ export const useGetDispatcherConfig = () => { return { bof2ConstantsQuery }; }; + +export const useLaneIds = (cameraID: string) => { + const laneIdQuery = useQuery({ + queryKey: ["getBOF2LaneId", cameraID], + queryFn: () => getBOF2LaneId(cameraID), + }); + + const laneIdMutation = useMutation({ + mutationKey: ["updateBOF2LaneId", cameraID], + mutationFn: (data: OptionalBOF2LaneIDs) => updateBOF2LaneId(data), + }); + + return { laneIdQuery, laneIdMutation }; +}; diff --git a/src/hooks/useSightingAmend.ts b/src/hooks/useSightingAmend.ts index 4115efa..382f6ac 100644 --- a/src/hooks/useSightingAmend.ts +++ b/src/hooks/useSightingAmend.ts @@ -2,15 +2,15 @@ import { useMutation, useQuery } from "@tanstack/react-query"; import { CAM_BASE } from "../utils/config"; import type { InitialValuesForm } from "../types/types"; -const getSightingAmend = async () => { - const response = await fetch(`${CAM_BASE}/api/fetch-config?id=SightingAmmendA`); +const getSightingAmend = async (cameraId: string) => { + const response = await fetch(`${CAM_BASE}/api/fetch-config?id=SightingAmmend${cameraId}`); if (!response.ok) throw new Error("Cannot reach sighting amend endpoint"); return response.json(); }; -const updateSightingAmend = async (data: InitialValuesForm) => { +const updateSightingAmend = async (data: InitialValuesForm, cameraID: string) => { const updateSightingAmendPayload = { - id: "SightingAmmendA", + id: `SightingAmmend${cameraID}`, fields: [ { property: "propOverviewQuality", @@ -30,15 +30,15 @@ const updateSightingAmend = async (data: InitialValuesForm) => { return response.json(); }; -export const useSightingAmend = () => { +export const useSightingAmend = (cameraID: string) => { const sightingAmendQuery = useQuery({ queryKey: ["getSightingAmend"], - queryFn: getSightingAmend, + queryFn: () => getSightingAmend(cameraID), }); const sightingAmendMutation = useMutation({ - mutationKey: ["updateSightingAmend"], - mutationFn: updateSightingAmend, + mutationKey: ["updateSightingAmend", cameraID], + mutationFn: (data: InitialValuesForm) => updateSightingAmend(data, cameraID), }); return { diff --git a/src/types/types.ts b/src/types/types.ts index d085f40..236e34e 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -363,7 +363,11 @@ type UploadedState = { payload: Blob | undefined; }; -export type SoundAction = UpdateAction | AddAction | VolumeAction | UploadedState; +type ResetAction = { + type: "RESET"; +}; + +export type SoundAction = UpdateAction | AddAction | VolumeAction | UploadedState | ResetAction; export type WifiSettingValues = { ssid: string; password: string; -- 2.25.1 From 71e23c7d45877baa4ba0d30c1a576711cfb4ccbc Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Wed, 7 Jan 2026 09:41:19 +0000 Subject: [PATCH 2/3] - removed console.log --- .../System/resetUserSettings/ResetUserSettingsModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx index 288ced7..79280ea 100644 --- a/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx +++ b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx @@ -10,8 +10,7 @@ const ResetUserSettingsModal = ({ isUserSettingsModalOpen, setIsUserSettingsModalOpen, }: ResetUserSettingsModalProps) => { - const { state, dispatch } = useSoundContext(); - console.log(state); + const { dispatch } = useSoundContext(); const handleClose = () => { setIsUserSettingsModalOpen(false); }; -- 2.25.1 From bec28b91e152abc070fe93a65790d6ec254a8fdb Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Wed, 7 Jan 2026 09:52:01 +0000 Subject: [PATCH 3/3] add ResetUserSettings component and integrate it into SystemConfigFields --- .../SettingForms/System/SystemConfigFields.tsx | 4 ++-- .../ResetUserSettingsModal.tsx | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/src/components/SettingForms/System/SystemConfigFields.tsx b/src/components/SettingForms/System/SystemConfigFields.tsx index 76bf378..22755e0 100644 --- a/src/components/SettingForms/System/SystemConfigFields.tsx +++ b/src/components/SettingForms/System/SystemConfigFields.tsx @@ -8,7 +8,7 @@ import { useDNSSettings, useSystemConfig } from "../../../hooks/useSystemConfig" import { ValidateIPaddress, isUtcOutOfSync } from "../../../utils/utils"; import { toast } from "sonner"; import { useSystemStatus } from "../../../hooks/useSystemStatus"; -// import ResetUserSettings from "./resetUserSettings/ResetUserSettings"; +import ResetUserSettings from "./resetUserSettings/ResetUserSettings"; const SystemConfigFields = () => { const { saveSystemSettings, systemSettingsData, saveSystemSettingsLoading } = useSystemConfig(); @@ -218,7 +218,7 @@ const SystemConfigFields = () => { > {hardRebootMutation.isPending || isSubmitting ? "Rebooting" : "Hardware Reboot"} - {/* */} + )} diff --git a/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx index 79280ea..eba907b 100644 --- a/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx +++ b/src/components/SettingForms/System/resetUserSettings/ResetUserSettingsModal.tsx @@ -1,4 +1,5 @@ import { useSoundContext } from "../../../../context/SoundContext"; +import { useCameraBlackboard } from "../../../../hooks/useCameraBlackboard"; import ModalComponent from "../../../UI/ModalComponent"; type ResetUserSettingsModalProps = { @@ -10,14 +11,22 @@ const ResetUserSettingsModal = ({ isUserSettingsModalOpen, setIsUserSettingsModalOpen, }: ResetUserSettingsModalProps) => { - const { dispatch } = useSoundContext(); + const { state, dispatch } = useSoundContext(); + const { mutation } = useCameraBlackboard(); const handleClose = () => { setIsUserSettingsModalOpen(false); }; - const handleResetAll = () => { + const handleResetAll = async () => { // Logic to reset all user settings goes here dispatch({ type: "RESET" }); + + await mutation.mutateAsync({ + operation: "INSERT", + path: "soundSettings", + value: state, + }); + handleClose(); }; return ( @@ -31,9 +40,10 @@ const ResetUserSettingsModal = ({ This will RESET ALL user settings including{" "} Uploaded user sounds , Hotlist Sound hits and{" "} - NPED Sound hits. It will also reset any customized settings within + NPED Sound hits. + {/* It will also reset any customized settings within the app to their original defaults and Clear out the session data and{" "} - alert history sightings. + alert history sightings. */}

-- 2.25.1