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 */}
-
-
-
-
- | VRM |
- Bay ID |
- Seen Count |
- First Seen |
- Last Seen |
-
-
-
- {readings?.map((reading: DecodeReading) => (
-
- | {reading?.vrm} |
- {reading?.laneID} |
- {reading?.seenCount} |
- {reading?.firstSeenTimeHumane} |
- {reading?.lastSeenTimeHumane} |
+ <>
+
+ {/* Desktop Table */}
+
+
+
+
+ | VRM |
+ Bay ID |
+ Seen Count |
+ First Seen |
+ Last 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 */}
-
-
-
-
- | VRM |
- Bay ID |
- Seen Count |
- First Seen |
- Last Seen |
-
-
-
- {readings?.map((reading: DecodeReading) => (
-
- | {reading?.vrm} |
- {reading?.laneID} |
- {reading?.seenCount} |
- {reading?.firstSeenTimeHumane} |
- {reading?.lastSeenTimeHumane} |
+ <>
+
+ {/* Desktop Table */}
+
+
+
+
+ | VRM |
+ Bay ID |
+ Seen Count |
+ First Seen |
+ Last 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"}
+
+
+
+
+
+

+
+
+
+
+
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) =
Close