From 9208470e535cf83fc1556b2e63d68c3d5edd8a63 Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Thu, 4 Dec 2025 19:14:14 +0000 Subject: [PATCH] - addressing feedback --- src/app/reducers/cameraFeedReducer.ts | 30 ++++---- .../CameraSettings/RegionSelector.tsx | 35 +++++++-- .../PlatePatch/SightingEntryTable.tsx | 2 +- .../PlatePatch/SightingExitTable.tsx | 2 +- .../components/PlatePatch/SightingPatch.tsx | 2 +- .../components/Video/VideoFeedGridPainter.tsx | 1 + .../output/components/ChannelCard.tsx | 9 ++- .../output/components/ChannelFields.tsx | 25 ++++--- .../output/components/OutputForms.tsx | 22 +++++- src/features/output/hooks/useCustomFields.ts | 19 +++++ .../settings/components/SystemConfig.tsx | 75 ++++++++++++++++--- .../settings/hooks/useGetNetworkConfig.ts | 49 ++++++++++++ src/types/types.ts | 6 ++ 13 files changed, 229 insertions(+), 48 deletions(-) create mode 100644 src/features/output/hooks/useCustomFields.ts create mode 100644 src/features/settings/hooks/useGetNetworkConfig.ts diff --git a/src/app/reducers/cameraFeedReducer.ts b/src/app/reducers/cameraFeedReducer.ts index fa40d6a..4f2b1d8 100644 --- a/src/app/reducers/cameraFeedReducer.ts +++ b/src/app/reducers/cameraFeedReducer.ts @@ -9,25 +9,25 @@ export const initialState: CameraFeedState = { }, regionsByCamera: { A: [ - { name: "Region 1", brushColour: "#ff0000" }, - { name: "Region 2", brushColour: "#00ff00" }, - { name: "Region 3", brushColour: "#0400ff" }, - { name: "Region 4", brushColour: "#ffff00" }, - { name: "Region 5", brushColour: "#fc35db" }, + { name: "Bay 1", brushColour: "#ff0000" }, + { name: "Bay 2", brushColour: "#00ff00" }, + { name: "Bay 3", brushColour: "#0400ff" }, + { name: "Bay 4", brushColour: "#ffff00" }, + { name: "Bay 5", brushColour: "#fc35db" }, ], B: [ - { name: "Region 1", brushColour: "#ff0000" }, - { name: "Region 2", brushColour: "#00ff00" }, - { name: "Region 3", brushColour: "#0400ff" }, - { name: "Region 4", brushColour: "#ffff00" }, - { name: "Region 5", brushColour: "#fc35db" }, + { name: "Bay 1", brushColour: "#ff0000" }, + { name: "Bay 2", brushColour: "#00ff00" }, + { name: "Bay 3", brushColour: "#0400ff" }, + { name: "Bay 4", brushColour: "#ffff00" }, + { name: "Bay 5", brushColour: "#fc35db" }, ], C: [ - { name: "Region 1", brushColour: "#ff0000" }, - { name: "Region 2", brushColour: "#00ff00" }, - { name: "Region 3", brushColour: "#0400ff" }, - { name: "Region 4", brushColour: "#ffff00" }, - { name: "Region 5", brushColour: "#fc35db" }, + { name: "Bay 1", brushColour: "#ff0000" }, + { name: "Bay 2", brushColour: "#00ff00" }, + { name: "Bay 3", brushColour: "#0400ff" }, + { name: "Bay 4", brushColour: "#ffff00" }, + { name: "Bay 5", brushColour: "#fc35db" }, ], }, diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index 0851df1..f850e08 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -43,14 +43,29 @@ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: Re }); }; + const handleAddRegionClick = () => { + const regionName = `Bay ${regions.length + 1}`; + dispatch({ + type: "ADD_NEW_REGION", + payload: { cameraFeedID: cameraFeedID, regionName: regionName, brushColour: "#ffffff" }, + }); + }; + + const handleRemoveClick = () => { + dispatch({ + type: "REMOVE_REGION", + payload: { cameraFeedID: cameraFeedID, regionName: regions[selectedRegionIndex].name }, + }); + }; + const handleSaveclick = () => { const regions: ColourData[] = []; const test = Array.from(paintedCells.entries()); - const region1 = test.filter(([, cell]) => cell.region.name === "Region 1"); - const region2 = test.filter(([, cell]) => cell.region.name === "Region 2"); - const region3 = test.filter(([, cell]) => cell.region.name === "Region 3"); - const region4 = test.filter(([, cell]) => cell.region.name === "Region 4"); - const region5 = test.filter(([, cell]) => cell.region.name === "Region 5"); + const region1 = test.filter(([, cell]) => cell.region.name === "Bay 1"); + const region2 = test.filter(([, cell]) => cell.region.name === "Bay 2"); + const region3 = test.filter(([, cell]) => cell.region.name === "Bay 3"); + const region4 = test.filter(([, cell]) => cell.region.name === "Bay 4"); + const region5 = test.filter(([, cell]) => cell.region.name === "Bay 5"); const region1Data = { id: 1, cells: region1.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]), @@ -130,7 +145,7 @@ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: Re
-

Region Select

+

Bay Select

<> {regions?.map((region, idx) => { const isSelected = selectedRegionIndex === idx; @@ -162,6 +177,14 @@ const RegionSelector = ({ regions, selectedRegionIndex, mode, cameraFeedID }: Re ); })} +
+ + +
diff --git a/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx b/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx index 21cdb99..49edebd 100644 --- a/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx +++ b/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx @@ -18,7 +18,7 @@ const SightingEntryTable = () => { VRM - Lane ID + Bay ID Seen Count First Seen Last Seen diff --git a/src/features/cameras/components/PlatePatch/SightingExitTable.tsx b/src/features/cameras/components/PlatePatch/SightingExitTable.tsx index 7ca806c..02d7ce4 100644 --- a/src/features/cameras/components/PlatePatch/SightingExitTable.tsx +++ b/src/features/cameras/components/PlatePatch/SightingExitTable.tsx @@ -18,7 +18,7 @@ const SightingExitTable = () => { VRM - Lane ID + Bay ID Seen Count First Seen Last Seen diff --git a/src/features/cameras/components/PlatePatch/SightingPatch.tsx b/src/features/cameras/components/PlatePatch/SightingPatch.tsx index c4ffbde..38685e8 100644 --- a/src/features/cameras/components/PlatePatch/SightingPatch.tsx +++ b/src/features/cameras/components/PlatePatch/SightingPatch.tsx @@ -8,7 +8,7 @@ const PlatePatch = () => { return ( - + Entry Sightings Exit Sightings diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index f415eec..3300d2e 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -118,6 +118,7 @@ const VideoFeedGridPainter = () => { onMouseMove={handleStageMouseMove} onMouseUp={handleStageMouseUp} onMouseLeave={handleStageMouseUp} + className="max-w-[55%]" > diff --git a/src/features/output/components/ChannelCard.tsx b/src/features/output/components/ChannelCard.tsx index bab00ed..04fa3e0 100644 --- a/src/features/output/components/ChannelCard.tsx +++ b/src/features/output/components/ChannelCard.tsx @@ -5,7 +5,11 @@ import ChannelFields from "./ChannelFields"; import type { FormTypes } from "../../../types/types"; import { useGetBearerConfig } from "../hooks/useBearer"; -const ChannelCard = () => { +type ChannelCardProps = { + customFields: (string | undefined)[]; +}; + +const ChannelCard = ({ customFields }: ChannelCardProps) => { const { values, errors, touched, setFieldValue } = useFormikContext(); const { bearerQuery } = useGetBearerConfig(values?.format?.toLowerCase() || "json"); const outputData = bearerQuery?.data; @@ -18,10 +22,11 @@ const ChannelCard = () => { values={values} outputData={outputData} onSetFieldValue={setFieldValue} + customFields={customFields} /> diff --git a/src/features/output/components/ChannelFields.tsx b/src/features/output/components/ChannelFields.tsx index d35fe21..40a7728 100644 --- a/src/features/output/components/ChannelFields.tsx +++ b/src/features/output/components/ChannelFields.tsx @@ -12,12 +12,13 @@ type ChannelFieldsProps = { }; outputData?: OutputDataResponse; onSetFieldValue: (field: string, value: string, shouldValidate?: boolean | undefined) => void; + customFields: (string | undefined)[]; }; -const ChannelFields = ({ errors, touched, values, outputData, onSetFieldValue }: ChannelFieldsProps) => { +const ChannelFields = ({ errors, touched, values, outputData, onSetFieldValue, customFields }: ChannelFieldsProps) => { const { optionalConstantsQuery } = useOptionalConstants(outputData?.id?.split("-")[1] || ""); const optionalConstants = optionalConstantsQuery?.data; - + console.log(customFields); const channelFieldsObject = useMemo(() => { return { connectTimeoutSeconds: outputData?.propConnectTimeoutSeconds?.value || "5", @@ -269,24 +270,28 @@ const ChannelFields = ({ errors, touched, values, outputData, onSetFieldValue }: {(arrayHelpers) => ( <> - {values?.customFields?.map((_, index) => ( + {values?.customFields?.slice(0, 6).map((_, index) => (
- + +
))} diff --git a/src/features/output/components/OutputForms.tsx b/src/features/output/components/OutputForms.tsx index f43b25c..f42fcd4 100644 --- a/src/features/output/components/OutputForms.tsx +++ b/src/features/output/components/OutputForms.tsx @@ -5,10 +5,12 @@ import type { BearerTypeFields, FormTypes, OptionalBOF2Constants, OptionalUTMCCo import { usePostBearerConfig } from "../hooks/useBearer"; import { useDispatcherConfig } from "../hooks/useDispatcherConfig"; import { useOptionalConstants } from "../hooks/useOptionalConstants"; +import { useCustomFields } from "../hooks/useCustomFields"; const OutputForms = () => { const { bearerMutation } = usePostBearerConfig(); const { dispatcherQuery, dispatcherMutation } = useDispatcherConfig(); + const { customFieldsQuery } = useCustomFields(); const isLoading = dispatcherQuery?.isLoading; const format = dispatcherQuery?.data?.propFormat?.value; @@ -18,6 +20,22 @@ const OutputForms = () => { const timestampSource = optionalConstantsQuery?.data?.propTimeZoneType?.value; const gpsFormat = optionalConstantsQuery?.data?.propGpsFormat?.value; + const customFieldLabel1 = customFieldsQuery?.data?.propCustomFieldName1?.value; + const customFieldLabel2 = customFieldsQuery?.data?.propCustomFieldName2?.value; + const customFieldLabel3 = customFieldsQuery?.data?.propCustomFieldName3?.value; + const customFieldLabel4 = customFieldsQuery?.data?.propCustomFieldName4?.value; + const customFieldLabel5 = customFieldsQuery?.data?.propCustomFieldName5?.value; + const customFieldLabel6 = customFieldsQuery?.data?.propCustomFieldName6?.value; + + const customfields = [ + customFieldLabel1, + customFieldLabel2, + customFieldLabel3, + customFieldLabel4, + customFieldLabel5, + customFieldLabel6, + ]; + const inititalValues: FormTypes = { format: format ?? "JSON", enabled: true, @@ -43,7 +61,7 @@ const OutputForms = () => { // ftp - fields //custom fields - customFields: [], + customFields: customfields ?? ["", "", "", "", "", ""], }; const handleSubmit = async (values: FormTypes) => { @@ -99,7 +117,7 @@ const OutputForms = () => {
- +
); diff --git a/src/features/output/hooks/useCustomFields.ts b/src/features/output/hooks/useCustomFields.ts new file mode 100644 index 0000000..c5d6ae9 --- /dev/null +++ b/src/features/output/hooks/useCustomFields.ts @@ -0,0 +1,19 @@ +import { useQuery } from "@tanstack/react-query"; +import { CAMBASE } from "../../../utils/config"; + +const fetchCustomFields = async () => { + const response = await fetch(`${CAMBASE}/api/fetch-config?id=SightingAmmend0-custom-fields`); + if (!response.ok) { + throw new Error("Network response was not ok"); + } + return response.json(); +}; + +export const useCustomFields = () => { + const customFieldsQuery = useQuery({ + queryKey: ["customFields"], + queryFn: fetchCustomFields, + }); + + return { customFieldsQuery }; +}; diff --git a/src/features/settings/components/SystemConfig.tsx b/src/features/settings/components/SystemConfig.tsx index 00cfde0..c356634 100644 --- a/src/features/settings/components/SystemConfig.tsx +++ b/src/features/settings/components/SystemConfig.tsx @@ -1,11 +1,15 @@ import { Formik, Form, Field, FieldArray } from "formik"; import { useSystemSettings } from "../hooks/useSystemSettings"; -import type { SystemSettings } from "../../../types/types"; +import type { NetworkConfig, SystemSettings } from "../../../types/types"; import { toast } from "sonner"; +import { useGetNetworkConfig } from "../hooks/useGetNetworkConfig"; const SystemConfig = () => { const { systemSettingsQuery, systemSettingsMutation } = useSystemSettings(); + const { networkConfigQuery, networkConfigMutation } = useGetNetworkConfig(); + const isLoading = networkConfigMutation?.isPending || networkConfigMutation?.isPending; + const isGettingLoading = systemSettingsQuery?.isLoading || networkConfigQuery?.isLoading; const timeZoneOptions = systemSettingsQuery?.data?.propLocalTimeZone?.accepted; const timeZoneOpts = timeZoneOptions?.split(",").map((option: string) => option.trim().replace(/\[|\]/g, "")); const timeSourceOptions = systemSettingsQuery?.data?.propTimeSource?.accepted; @@ -15,8 +19,11 @@ const SystemConfig = () => { const SNTPServer = systemSettingsQuery?.data?.propSNTPServer?.value; const SNTPInterval = systemSettingsQuery?.data?.propSNTPIntervalMinutes?.value; const timeSource = systemSettingsQuery?.data?.propTimeSource?.value; - // const primaryServer = systemSettingsQuery?.data?.propPrimaryDNSServer?.value; - // const secondaryServer = systemSettingsQuery?.data?.propSecondaryDNSServer?.value; + const primaryServer = networkConfigQuery?.data?.propNameServerPrimary?.value; + const secondaryServer = networkConfigQuery?.data?.propNameServerSecondary?.value; + const ipAddress = networkConfigQuery?.data?.propHost?.value; + const subnetMask = networkConfigQuery?.data?.propNetmask?.value; + const gateway = networkConfigQuery?.data?.propGateway?.value; const initialValues = { deviceName: deviceName ?? "", @@ -25,22 +32,36 @@ const SystemConfig = () => { SNTPServer: SNTPServer ?? "", SNTPInterval: SNTPInterval ?? 60, SNTPIntervalMinutes: SNTPInterval ?? 60, - primaryServer: "", - secondaryServer: "", + primaryServer: primaryServer ?? "", + secondaryServer: secondaryServer ?? "", timeSource: timeSource ?? "", + ipAddress: ipAddress ?? "", + subnetMask: subnetMask ?? "", + gateway: gateway ?? "", customFields: [], }; - const handleSubmit = async (values: SystemSettings) => { + const handleSubmit = async (values: SystemSettings & NetworkConfig) => { const result = await systemSettingsMutation.mutateAsync(values); + const networkResult = await networkConfigMutation.mutateAsync({ + ipAddress: values.ipAddress, + subnetMask: values.subnetMask, + gateway: values.gateway, + primaryServer: values.primaryServer, + secondaryServer: values.secondaryServer, + }); - if (result.id) { + if (result.id && networkResult.id) { toast.success("System settings updated successfully"); } else { toast.error("Failed to update system settings"); } }; + if (isGettingLoading) { + return
Loading...
; + } + return ( {({ values }) => ( @@ -106,6 +127,36 @@ const SystemConfig = () => { autoComplete="off" />
+
+ + +
+
+ + +
+
+ + +
{ {(arrayHelpers) => ( <> - {values.customFields.map((field, index) => ( + {values.customFields.map((_, index) => (
- )} diff --git a/src/features/settings/hooks/useGetNetworkConfig.ts b/src/features/settings/hooks/useGetNetworkConfig.ts new file mode 100644 index 0000000..1750634 --- /dev/null +++ b/src/features/settings/hooks/useGetNetworkConfig.ts @@ -0,0 +1,49 @@ +import { useQuery, useMutation } from "@tanstack/react-query"; +import { CAMBASE } from "../../../utils/config"; +import type { NetworkConfig } from "../../../types/types"; + +const fetchNetworkConfig = async () => { + const response = await fetch(`${CAMBASE}/api/fetch-config?id=GLOBAL--NetworkConfig`); + if (!response.ok) throw new Error("Network response was not ok"); + return response.json(); +}; + +const postNetworkConfig = async (networkConfig: NetworkConfig) => { + const fields = [ + { property: "propNetmask", value: networkConfig.subnetMask }, + { property: "propHost", value: networkConfig.ipAddress }, + { property: "propGateway", value: networkConfig.gateway }, + ]; + + if (networkConfig.primaryServer !== undefined) { + fields.push({ property: "propNameServerPrimary", value: networkConfig.primaryServer }); + } + if (networkConfig.secondaryServer !== undefined) { + fields.push({ property: "propNameServerSecondary", value: networkConfig.secondaryServer }); + } + const networkConfigPayload = { + id: "GLOBAL--NetworkConfig", + fields, + }; + + const respones = await fetch(`${CAMBASE}/api/update-config?id=GLOBAL--NetworkConfig`, { + method: "POST", + body: JSON.stringify(networkConfigPayload), + }); + if (!respones.ok) throw new Error("Network response was not ok"); + return respones.json(); +}; + +export const useGetNetworkConfig = () => { + const networkConfigQuery = useQuery({ + queryKey: ["networkConfig"], + queryFn: fetchNetworkConfig, + }); + + const networkConfigMutation = useMutation({ + mutationKey: ["networkConfigMutation"], + mutationFn: postNetworkConfig, + }); + + return { networkConfigQuery, networkConfigMutation }; +}; diff --git a/src/types/types.ts b/src/types/types.ts index 262514f..92398b8 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -186,6 +186,12 @@ export type SystemSettings = { timeSource: string; SNTPServer: string; SNTPIntervalMinutes: number; +}; + +export type NetworkConfig = { + ipAddress: string; + subnetMask: string; + gateway: string; primaryServer?: string; secondaryServer?: string; };