From e395777ae9cebd6c32cbaf9317d85f454c5fe5c9 Mon Sep 17 00:00:00 2001 From: Toba Ojo Date: Wed, 10 Dec 2025 22:32:30 +0000 Subject: [PATCH] - added modal for entry and exit sightings and plate patches --- .../CameraSettings/RegionSelector.tsx | 12 +- .../PlatePatch/SightingEntryTable.tsx | 123 +++++++++++------- .../PlatePatch/SightingExitTable.tsx | 122 ++++++++++------- .../platePatchModal/PlatePatchModal.tsx | 20 +++ .../PlatePatchModalContent.tsx | 64 +++++++++ .../components/Video/VideoFeedGridPainter.tsx | 6 +- src/types/types.ts | 1 + src/ui/ModalComponent.tsx | 2 +- 8 files changed, 243 insertions(+), 107 deletions(-) create mode 100644 src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModal.tsx create mode 100644 src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModalContent.tsx diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index c1e2699..db1a026 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -152,7 +152,7 @@ const RegionSelector = ({ colourMutation.mutate({ cameraFeedID, regions: regions }); - // Convert Map to plain object for blackboard + // Convert map to plain object for blackboard const serializableState = { ...state, paintedCells: { @@ -282,10 +282,16 @@ const RegionSelector = ({ })}
- -
diff --git a/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx b/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx index b85e795..010c1c3 100644 --- a/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx +++ b/src/features/cameras/components/PlatePatch/SightingEntryTable.tsx @@ -1,73 +1,96 @@ +import { useState } from "react"; import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; import type { DecodeReading } from "../../../../types/types"; import { useSightingEntryAndExit } from "../../hooks/useSightingEntryAndExit"; +import PlatePatchModal from "./platePatchModal/PlatePatchModal"; + const SightingEntryTable = () => { const { state } = useCameraFeedContext(); + const [isPlatePatchModalOpen, setIsPlatePatchModalOpen] = useState(false); + const [currentPatch, setCurrentPatch] = useState(null); const cameraFeedID = state.cameraFeedID; const { entryQuery } = useSightingEntryAndExit(cameraFeedID); const isLoading = entryQuery?.isFetching; const readings = entryQuery?.data?.decodes; + const handleRowClick = (reading: DecodeReading) => { + setCurrentPatch(reading); + setIsPlatePatchModalOpen(true); + }; + if (isLoading) return Loading Sighting data…; return ( -
- {/* Desktop Table */} -
- - - - - - - - - - - - {readings?.map((reading: DecodeReading) => ( - - - - - - + <> +
+ {/* Desktop Table */} +
+
VRMBay IDSeen CountFirst SeenLast Seen
{reading?.vrm}{reading?.laneID}{reading?.seenCount}{reading?.firstSeenTimeHumane}{reading?.lastSeenTimeHumane}
+ + + + + + + - ))} - -
VRMBay IDSeen CountFirst SeenLast Seen
-
+ + + {readings?.map((reading: DecodeReading) => ( + handleRowClick(reading)} + > + {reading?.vrm} + {reading?.laneID} + {reading?.seenCount} + {reading?.firstSeenTimeHumane} + {reading?.lastSeenTimeHumane} + + ))} + + +
- {/* Mobile Cards */} -
- {readings?.map((reading: DecodeReading) => ( -
-
- {reading?.vrm} - Bay {reading?.laneID} -
-
- Seen Count: - {reading?.seenCount} -
-
-
- First Seen: - {reading?.firstSeenTimeHumane} + {/* Mobile */} +
+ {readings?.map((reading: DecodeReading) => ( +
handleRowClick(reading)} + > +
+ {reading?.vrm} + Bay {reading?.laneID}
-
- Last Seen: - {reading?.lastSeenTimeHumane} +
+ Seen Count: + {reading?.seenCount} +
+
+
+ First Seen: + {reading?.firstSeenTimeHumane} +
+
+ Last Seen: + {reading?.lastSeenTimeHumane} +
-
- ))} + ))} +
-
+ setIsPlatePatchModalOpen(false)} + currentPatch={currentPatch} + direction={"entry"} + /> + ); }; diff --git a/src/features/cameras/components/PlatePatch/SightingExitTable.tsx b/src/features/cameras/components/PlatePatch/SightingExitTable.tsx index 3ba01f9..62e3acf 100644 --- a/src/features/cameras/components/PlatePatch/SightingExitTable.tsx +++ b/src/features/cameras/components/PlatePatch/SightingExitTable.tsx @@ -1,8 +1,12 @@ +import { useState } from "react"; import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; import type { DecodeReading } from "../../../../types/types"; import { useSightingEntryAndExit } from "../../hooks/useSightingEntryAndExit"; +import PlatePatchModal from "./platePatchModal/PlatePatchModal"; const SightingExitTable = () => { + const [isPlatePatchModalOpen, setIsPlatePatchModalOpen] = useState(false); + const [currentPatch, setCurrentPatch] = useState(null); const { state } = useCameraFeedContext(); const cameraFeedID = state.cameraFeedID; const { exitQuery } = useSightingEntryAndExit(cameraFeedID); @@ -10,64 +14,82 @@ const SightingExitTable = () => { const isLoading = exitQuery?.isFetching; const readings = exitQuery?.data?.decodes; + const handleRowClick = (reading: DecodeReading) => { + setCurrentPatch(reading); + setIsPlatePatchModalOpen(true); + }; + if (isLoading) return Loading Sighting data…; return ( -
- {/* Desktop Table */} -
- - - - - - - - - - - - {readings?.map((reading: DecodeReading) => ( - - - - - - + <> +
+ {/* Desktop Table */} +
+
VRMBay IDSeen CountFirst SeenLast Seen
{reading?.vrm}{reading?.laneID}{reading?.seenCount}{reading?.firstSeenTimeHumane}{reading?.lastSeenTimeHumane}
+ + + + + + + - ))} - -
VRMBay IDSeen CountFirst SeenLast Seen
-
+ + + {readings?.map((reading: DecodeReading) => ( + handleRowClick(reading)} + > + {reading?.vrm} + {reading?.laneID} + {reading?.seenCount} + {reading?.firstSeenTimeHumane} + {reading?.lastSeenTimeHumane} + + ))} + + +
- {/* Mobile Cards */} -
- {readings?.map((reading: DecodeReading) => ( -
-
- {reading?.vrm} - Bay {reading?.laneID} -
-
- Seen Count: - {reading?.seenCount} -
-
-
- First Seen: - {reading?.firstSeenTimeHumane} + {/* Mobile Cards */} +
+ {readings?.map((reading: DecodeReading) => ( +
handleRowClick(reading)} + > +
+ {reading?.vrm} + Bay {reading?.laneID}
-
- Last Seen: - {reading?.lastSeenTimeHumane} +
+ Seen Count: + {reading?.seenCount} +
+
+
+ First Seen: + {reading?.firstSeenTimeHumane} +
+
+ Last Seen: + {reading?.lastSeenTimeHumane} +
-
- ))} + ))} +
-
+ setIsPlatePatchModalOpen(false)} + currentPatch={currentPatch} + direction={"exit"} + /> + ); }; diff --git a/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModal.tsx b/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModal.tsx new file mode 100644 index 0000000..9c223e3 --- /dev/null +++ b/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModal.tsx @@ -0,0 +1,20 @@ +import type { DecodeReading } from "../../../../../types/types"; +import ModalComponent from "../../../../../ui/ModalComponent"; +import PlatePatchModalContent from "./PlatePatchModalContent"; + +type PlatePatchModalProps = { + isPlatePatchModalOpen: boolean; + handleClose: () => void; + currentPatch: DecodeReading | null; + direction?: "entry" | "exit"; +}; + +const PlatePatchModal = ({ isPlatePatchModalOpen, handleClose, currentPatch, direction }: PlatePatchModalProps) => { + return ( + + + + ); +}; + +export default PlatePatchModal; diff --git a/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModalContent.tsx b/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModalContent.tsx new file mode 100644 index 0000000..a5c8477 --- /dev/null +++ b/src/features/cameras/components/PlatePatch/platePatchModal/PlatePatchModalContent.tsx @@ -0,0 +1,64 @@ +import type { DecodeReading } from "../../../../../types/types"; + +type PlatePatchModalContentProps = { + currentPatch: DecodeReading | null; + direction?: "entry" | "exit"; +}; + +const PlatePatchModalContent = ({ currentPatch, direction }: PlatePatchModalContentProps) => { + const imageSrc = `data:image/png;base64,${currentPatch?.plate || ""}`; + const imageUrl = currentPatch ? imageSrc : ""; + + return ( +
+
+

+ {currentPatch?.vrm} +

+ + {direction === "entry" ? "Entry" : "Exit"} + +
+ +
+
+ {`${direction +
+ +
+
+

Bay ID

+

{currentPatch?.laneID || "N/A"}

+
+ +
+

Seen Count

+

{currentPatch?.seenCount || "N/A"}

+
+ +
+

First Seen

+

{currentPatch?.firstSeenTimeHumane || "N/A"}

+
+ +
+

Last Seen

+

{currentPatch?.lastSeenTimeHumane || "N/A"}

+
+
+
+
+ ); +}; + +export default PlatePatchModalContent; diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index e6ea83f..fd38f24 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -35,12 +35,12 @@ const VideoFeedGridPainter = () => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const stageRef = useRef(null); - const currentScale = stageSize.width / BACKEND_WIDTH; - const size = BACKEND_CELL_SIZE * currentScale; - // eslint-disable-next-line @typescript-eslint/no-explicit-any const paintLayerRef = useRef(null); + const currentScale = stageSize.width / BACKEND_WIDTH; + const size = BACKEND_CELL_SIZE * currentScale; + const cameraASocket = useCameraFeedASocket(); const cameraBSocket = useCameraFeedBSocket(); const cameraCSocket = useCameraFeedCSocket(); diff --git a/src/types/types.ts b/src/types/types.ts index 0ecad3d..b7dce94 100644 --- a/src/types/types.ts +++ b/src/types/types.ts @@ -202,6 +202,7 @@ export type DecodeReading = { duplicate?: true; firstSeenTimeHumane: string; lastSeenTimeHumane: string; + plate?: string; }; export type ColourData = { diff --git a/src/ui/ModalComponent.tsx b/src/ui/ModalComponent.tsx index 3352678..92b1b49 100644 --- a/src/ui/ModalComponent.tsx +++ b/src/ui/ModalComponent.tsx @@ -26,7 +26,7 @@ const ModalComponent = ({ isModalOpen, children, close }: ModalComponentProps) =