Refactor camera feed components and add sighting tables

- Updated mode settings in camera feed reducer to use "painter"
- Renamed PlatePatch component to SightingPatch and updated imports
- Removed obsolete PlatePatch component
- Added SightingEntryTable and SightingExitTable components for displaying sighting data
- Implemented useSightingEntryAndExit hook for fetching entry and exit sightings
- Adjusted VideoFeedGridPainter for improved width calculation
- Introduced DecodeReading type for better typing
This commit is contained in:
2025-11-28 12:58:42 +00:00
parent c910a3dd50
commit ddb1fa1bf1
9 changed files with 160 additions and 13 deletions

View File

@@ -3,7 +3,7 @@ import { useState } from "react";
import VideoFeedGridPainter from "./Video/VideoFeedGridPainter";
import CameraSettings from "./CameraSettings/CameraSettings";
import PlatePatch from "./PlatePatch/PlatePatch";
import PlatePatch from "./PlatePatch/SightingPatch";
const CameraGrid = () => {
const [tabIndex, setTabIndex] = useState(0);

View File

@@ -1,7 +0,0 @@
import Card from "../../../../ui/Card";
const PlatePatch = () => {
return <Card className="md:row-start-4 md:col-span-2">PlatePatch</Card>;
};
export default PlatePatch;

View File

@@ -0,0 +1,44 @@
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
import type { DecodeReading } from "../../../../types/types";
import { useSightingEntryAndExit } from "../../hooks/useSightingEntryAndExit";
const SightingEntryTable = () => {
const { state } = useCameraFeedContext();
const cameraFeedID = state.cameraFeedID;
const { entryQuery } = useSightingEntryAndExit(cameraFeedID);
const isLoading = entryQuery?.isFetching;
const readings = entryQuery?.data?.decodes;
if (isLoading) return <span className="text-slate-500">Loading Sighting data</span>;
return (
<div className="border border-gray-600 rounded-lg overflow-hidden m-2">
<div className="overflow-y-auto ">
<table className="w-full text-left text-sm">
<thead className="bg-gray-700/50 text-gray-200 sticky top-0">
<tr>
<th className="px-4 py-3 font-semibold">VRM</th>
<th className="px-4 py-3 font-semibold">Lane ID</th>
<th className="px-4 py-3 font-semibold text-center">Seen Count</th>
<th className="px-4 py-3 font-semibold">First Seen</th>
<th className="px-4 py-3 font-semibold">Last Seen</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700">
{readings?.map((reading: DecodeReading) => (
<tr className="hover:bg-gray-800/30 transition-colors" key={reading?.id}>
<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>
<td className="px-4 py-3 text-gray-400 text-md">{reading?.firstSeenTimeHumane}</td>
<td className="px-4 py-3 text-gray-400 text-md">{reading?.lastSeenTimeHumane}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default SightingEntryTable;

View File

@@ -0,0 +1,44 @@
import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext";
import type { DecodeReading } from "../../../../types/types";
import { useSightingEntryAndExit } from "../../hooks/useSightingEntryAndExit";
const SightingExitTable = () => {
const { state } = useCameraFeedContext();
const cameraFeedID = state.cameraFeedID;
const { exitQuery } = useSightingEntryAndExit(cameraFeedID);
const isLoading = exitQuery?.isFetching;
const readings = exitQuery?.data?.decodes;
if (isLoading) return <span className="text-slate-500">Loading Sighting data</span>;
return (
<div className="border border-gray-600 rounded-lg overflow-hidden m-2">
<div className="overflow-y-auto ">
<table className="w-full text-left text-sm">
<thead className="bg-gray-700/50 text-gray-200 sticky top-0">
<tr>
<th className="px-4 py-3 font-semibold">VRM</th>
<th className="px-4 py-3 font-semibold">Lane ID</th>
<th className="px-4 py-3 font-semibold text-center">Seen Count</th>
<th className="px-4 py-3 font-semibold">First Seen</th>
<th className="px-4 py-3 font-semibold">Last Seen</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700">
{readings?.map((reading: DecodeReading) => (
<tr className="hover:bg-gray-800/30 transition-colors" key={reading?.id}>
<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>
<td className="px-4 py-3 text-gray-400 text-md">{reading?.firstSeenTimeHumane}</td>
<td className="px-4 py-3 text-gray-400 text-md">{reading?.lastSeenTimeHumane}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
export default SightingExitTable;

View File

@@ -0,0 +1,27 @@
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import Card from "../../../../ui/Card";
import CardHeader from "../../../../ui/CardHeader";
import SightingEntryTable from "./SightingEntryTable";
import SightingExitTable from "./SightingExitTable";
const PlatePatch = () => {
return (
<Card className="md:row-start-4 md:col-span-2 p-4 h-[190%]">
<CardHeader title="Entry / Exit" />
<Tabs>
<TabList>
<Tab>Entry Sightings</Tab>
<Tab>Exit Sightings</Tab>
</TabList>
<TabPanel>
<SightingEntryTable />
</TabPanel>
<TabPanel>
<SightingExitTable />
</TabPanel>
</Tabs>
</Card>
);
};
export default PlatePatch;

View File

@@ -88,7 +88,7 @@ const VideoFeedGridPainter = () => {
const width = window.innerWidth;
const aspectRatio = 740 / 460;
const newWidth = width * 0.36;
const newWidth = width * 0.39;
const newHeight = newWidth / aspectRatio;
setStageSize({ width: newWidth, height: newHeight });
};

View File

@@ -0,0 +1,28 @@
import { useQuery } from "@tanstack/react-query";
import { CAMBASE } from "../../../utils/config";
const fetchEntrySightings = async (cameraFeedID: string) => {
const response = await fetch(`${CAMBASE}/EntrySightingCreator${cameraFeedID}-list-proto-sightings`);
if (!response.ok) throw new Error("Cannot reach sighing entry endpoint");
return response.json();
};
const fetchExitSightings = async (cameraFeedID: string) => {
const response = await fetch(`${CAMBASE}/ExitSightingCreator${cameraFeedID}-list-proto-sightings`);
if (!response.ok) throw new Error("Cannot reach sighing exit endpoint");
return response.json();
};
export const useSightingEntryAndExit = (cameraFeedID: string) => {
const entryQuery = useQuery({
queryKey: ["Entry Sightings", cameraFeedID],
queryFn: () => fetchEntrySightings(cameraFeedID),
});
const exitQuery = useQuery({
queryKey: ["Exit Sightings", cameraFeedID],
queryFn: () => fetchExitSightings(cameraFeedID),
});
return { entryQuery, exitQuery };
};