- added modal for entry and exit sightings and plate patches
This commit is contained in:
@@ -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 = ({
|
||||
})}
|
||||
</>
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
<button className="border border-blue-900 bg-blue-700 px-4 py-1 rounded-md" onClick={handleAddRegionClick}>
|
||||
<button
|
||||
className="border border-blue-900 bg-blue-700 px-4 py-1 rounded-md hover:bg-blue-800 hover:cursor-pointer"
|
||||
onClick={handleAddRegionClick}
|
||||
>
|
||||
Add Bay
|
||||
</button>
|
||||
<button className="border border-red-900 bg-red-700 px-4 py-1 rounded-md" onClick={handleRemoveClick}>
|
||||
<button
|
||||
className="border border-red-900 bg-red-700 px-4 py-1 rounded-md hover:bg-red-800 hover:cursor-pointer"
|
||||
onClick={handleRemoveClick}
|
||||
>
|
||||
Remove Bay
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,28 @@
|
||||
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<DecodeReading | null>(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 <span className="text-slate-500">Loading Sighting data…</span>;
|
||||
return (
|
||||
<>
|
||||
<div className="border border-gray-600 rounded-lg m-2">
|
||||
{/* Desktop Table */}
|
||||
<div className="hidden md:block overflow-y-auto">
|
||||
@@ -27,7 +38,11 @@ const SightingEntryTable = () => {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700">
|
||||
{readings?.map((reading: DecodeReading) => (
|
||||
<tr className="hover:bg-gray-800/30 transition-colors" key={reading?.id}>
|
||||
<tr
|
||||
className="hover:bg-gray-800/30 transition-colors hover:cursor-pointer"
|
||||
key={reading?.id}
|
||||
onClick={() => handleRowClick(reading)}
|
||||
>
|
||||
<td className="px-4 py-3 font-mono font-semibold text-blue-400 text-lg">{reading?.vrm}</td>
|
||||
<td className="px-4 py-3 text-gray-300">{reading?.laneID}</td>
|
||||
<td className="px-4 py-3 text-center text-gray-300">{reading?.seenCount}</td>
|
||||
@@ -39,12 +54,13 @@ const SightingEntryTable = () => {
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{/* Mobile Cards */}
|
||||
{/* Mobile */}
|
||||
<div className="md:hidden overflow-y-auto space-y-3 p-3">
|
||||
{readings?.map((reading: DecodeReading) => (
|
||||
<div
|
||||
key={reading?.id}
|
||||
className="bg-gray-800/30 rounded-lg p-4 space-y-2 border border-gray-700 hover:border-gray-600 transition-colors"
|
||||
onClick={() => handleRowClick(reading)}
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<span className="font-mono font-semibold text-blue-400 text-xl">{reading?.vrm}</span>
|
||||
@@ -68,6 +84,13 @@ const SightingEntryTable = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<PlatePatchModal
|
||||
isPlatePatchModalOpen={isPlatePatchModalOpen}
|
||||
handleClose={() => setIsPlatePatchModalOpen(false)}
|
||||
currentPatch={currentPatch}
|
||||
direction={"entry"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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<DecodeReading | null>(null);
|
||||
const { state } = useCameraFeedContext();
|
||||
const cameraFeedID = state.cameraFeedID;
|
||||
const { exitQuery } = useSightingEntryAndExit(cameraFeedID);
|
||||
@@ -10,8 +14,14 @@ const SightingExitTable = () => {
|
||||
const isLoading = exitQuery?.isFetching;
|
||||
const readings = exitQuery?.data?.decodes;
|
||||
|
||||
const handleRowClick = (reading: DecodeReading) => {
|
||||
setCurrentPatch(reading);
|
||||
setIsPlatePatchModalOpen(true);
|
||||
};
|
||||
|
||||
if (isLoading) return <span className="text-slate-500">Loading Sighting data…</span>;
|
||||
return (
|
||||
<>
|
||||
<div className="border border-gray-600 rounded-lg m-2">
|
||||
{/* Desktop Table */}
|
||||
<div className="hidden md:block overflow-y-auto">
|
||||
@@ -27,7 +37,11 @@ const SightingExitTable = () => {
|
||||
</thead>
|
||||
<tbody className="divide-y divide-gray-700">
|
||||
{readings?.map((reading: DecodeReading) => (
|
||||
<tr className="hover:bg-gray-800/30 transition-colors" key={reading?.id}>
|
||||
<tr
|
||||
className="hover:bg-gray-800/30 transition-colors hover:cursor-pointer"
|
||||
key={reading?.id}
|
||||
onClick={() => handleRowClick(reading)}
|
||||
>
|
||||
<td className="px-4 py-3 font-mono font-semibold text-red-400 text-lg">{reading?.vrm}</td>
|
||||
<td className="px-4 py-3 text-gray-300">{reading?.laneID}</td>
|
||||
<td className="px-4 py-3 text-center text-gray-300">{reading?.seenCount}</td>
|
||||
@@ -45,6 +59,7 @@ const SightingExitTable = () => {
|
||||
<div
|
||||
key={reading?.id}
|
||||
className="bg-gray-800/30 rounded-lg p-4 space-y-2 border border-gray-700 hover:border-gray-600 transition-colors"
|
||||
onClick={() => handleRowClick(reading)}
|
||||
>
|
||||
<div className="flex justify-between items-start">
|
||||
<span className="font-mono font-semibold text-red-400 text-xl">{reading?.vrm}</span>
|
||||
@@ -68,6 +83,13 @@ const SightingExitTable = () => {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<PlatePatchModal
|
||||
isPlatePatchModalOpen={isPlatePatchModalOpen}
|
||||
handleClose={() => setIsPlatePatchModalOpen(false)}
|
||||
currentPatch={currentPatch}
|
||||
direction={"exit"}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<ModalComponent isModalOpen={isPlatePatchModalOpen} close={handleClose}>
|
||||
<PlatePatchModalContent currentPatch={currentPatch} direction={direction} />
|
||||
</ModalComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlatePatchModal;
|
||||
@@ -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 (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center justify-between border-b border-gray-600 pb-3">
|
||||
<h2
|
||||
className={`font-mono font-bold text-3xl tracking-wide
|
||||
${direction === "entry" ? "text-blue-400" : "text-red-400"}`}
|
||||
>
|
||||
{currentPatch?.vrm}
|
||||
</h2>
|
||||
<span
|
||||
className={`px-3 py-1 rounded-full text-xs font-semibold uppercase
|
||||
${direction === "entry" ? "bg-blue-500/20 text-blue-400" : "bg-red-500/20 text-red-400"}`}
|
||||
>
|
||||
{direction === "entry" ? "Entry" : "Exit"}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div className="border border-gray-600 rounded-2xl">
|
||||
<div className="flex bg-gray-800/50 rounded-lg p-4">
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt={`${direction === "entry" ? "Entry" : "Exit"} Image for ${currentPatch?.vrm || "N/A"}`}
|
||||
className="rounded-lg border border-gray-600 max-w-full h-auto shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-4 bg-gray-800/30 rounded-lg p-4">
|
||||
<div className="space-y-1">
|
||||
<p className="text-gray-400 text-xs uppercase tracking-wider">Bay ID</p>
|
||||
<p className="text-gray-200 font-semibold text-lg">{currentPatch?.laneID || "N/A"}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<p className="text-gray-400 text-xs uppercase tracking-wider">Seen Count</p>
|
||||
<p className="text-gray-200 font-semibold text-lg">{currentPatch?.seenCount || "N/A"}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 col-span-2">
|
||||
<p className="text-gray-400 text-xs uppercase tracking-wider">First Seen</p>
|
||||
<p className="text-gray-300 text-sm">{currentPatch?.firstSeenTimeHumane || "N/A"}</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1 col-span-2">
|
||||
<p className="text-gray-400 text-xs uppercase tracking-wider">Last Seen</p>
|
||||
<p className="text-gray-300 text-sm">{currentPatch?.lastSeenTimeHumane || "N/A"}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default PlatePatchModalContent;
|
||||
@@ -35,12 +35,12 @@ const VideoFeedGridPainter = () => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const stageRef = useRef<any>(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<any>(null);
|
||||
|
||||
const currentScale = stageSize.width / BACKEND_WIDTH;
|
||||
const size = BACKEND_CELL_SIZE * currentScale;
|
||||
|
||||
const cameraASocket = useCameraFeedASocket();
|
||||
const cameraBSocket = useCameraFeedBSocket();
|
||||
const cameraCSocket = useCameraFeedCSocket();
|
||||
|
||||
@@ -202,6 +202,7 @@ export type DecodeReading = {
|
||||
duplicate?: true;
|
||||
firstSeenTimeHumane: string;
|
||||
lastSeenTimeHumane: string;
|
||||
plate?: string;
|
||||
};
|
||||
|
||||
export type ColourData = {
|
||||
|
||||
@@ -26,7 +26,7 @@ const ModalComponent = ({ isModalOpen, children, close }: ModalComponentProps) =
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
onClick={close}
|
||||
className="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg mb-4"
|
||||
className="bg-gray-700 hover:bg-gray-600 text-white font-bold py-2 px-4 rounded-lg mb-4 hover:cursor-pointer"
|
||||
>
|
||||
Close
|
||||
</button>
|
||||
|
||||
Reference in New Issue
Block a user