- Enhanced Dashboard and SystemOverview components with totalSightings prop

- improved layout and loading states in VideoFeed and SightingStack components.
This commit is contained in:
2026-01-12 15:38:01 +00:00
parent 1555221825
commit 2ecc39317d
7 changed files with 50 additions and 29 deletions

View File

@@ -6,22 +6,24 @@ import { useCameraSettingsContext } from "../../app/context/CameraSettingsContex
import SystemOverview from "./SystemOverview/SystemOverview"; import SystemOverview from "./SystemOverview/SystemOverview";
const Dashboard = () => { const Dashboard = () => {
const { sightingList, isLoading } = useSightingList(); const { sightingList, isLoading, totalSightings } = useSightingList();
const { state: cameraSettings } = useCameraSettingsContext(); const { state: cameraSettings } = useCameraSettingsContext();
const size = cameraSettings.imageSize; const size = cameraSettings.imageSize;
const mostRecent = sightingList[0]; const mostRecent = sightingList[0];
return ( return (
<div> <div className="flex flex-col">
<SystemOverview /> <div className="grid grid-cols-1 md:grid-cols-2 gap-2 md:gap-5">
<div className="grid grid-cols-1 md:grid-cols-12 gap-2 md:gap-5 mt-4">
<div className="col-span-7">
<VideoFeed mostRecentSighting={mostRecent} isLoading={isLoading} size={size} /> <VideoFeed mostRecentSighting={mostRecent} isLoading={isLoading} size={size} />
<SightingStack sightings={sightingList} />
</div>
<div className="grid grid-cols-1 md:grid-cols-3 gap-2 md:gap-5 items-center">
<div className="col-span-1">
<PlateRead sighting={mostRecent} /> <PlateRead sighting={mostRecent} />
</div> </div>
<div className="col-span-5"> <div className="col-span-2">
<SightingStack sightings={sightingList} /> <SystemOverview totalSightings={totalSightings} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -5,7 +5,11 @@ import { useGetSystemHealth } from "../hooks/useGetSystemHealth";
import PlatesProcessed from "./PlatesProcessed"; import PlatesProcessed from "./PlatesProcessed";
import SystemStatusContent from "./SystemStatusContent"; import SystemStatusContent from "./SystemStatusContent";
const SystemOverview = () => { type SystemOverViewProps = {
totalSightings: number;
};
const SystemOverview = ({ totalSightings }: SystemOverViewProps) => {
const { systemHealthQuery } = useGetSystemHealth(); const { systemHealthQuery } = useGetSystemHealth();
const { storeQuery } = useGetStore(); const { storeQuery } = useGetStore();
@@ -19,23 +23,24 @@ const SystemOverview = () => {
return <div>Loading system overview...</div>; return <div>Loading system overview...</div>;
} }
return ( return (
<div> <Card className="p-4">
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Card className="p-4 hover:bg-[#233241] cursor-pointer"> <div className="p-4 border border-gray-600 rounded-lg hover:bg-[#233241] cursor-pointer ">
<SystemStatusContent status={cameraStatus} isError={isError} /> <SystemStatusContent status={cameraStatus} isError={isError} />
</Card> </div>
<Card className="p-4 hover:bg-[#233241] cursor-pointer"> <div className="p-4 border border-gray-600 rounded-lg hover:bg-[#233241] cursor-pointer">
<h3 className="text-lg">Active Sightings</h3> <h3 className="text-lg">Sightings</h3>
</Card> <p>{totalSightings}</p>
<Card className="p-4 hover:bg-[#233241] cursor-pointer"> </div>
<div className="p-4 border border-gray-600 rounded-lg hover:bg-[#233241] cursor-pointer">
<PlatesProcessed platesProcessed={platesProcessed} /> <PlatesProcessed platesProcessed={platesProcessed} />
</Card> </div>
<Card className="p-4 hover:bg-[#233241] cursor-pointer"> <div className="p-4 border border-gray-600 rounded-lg hover:bg-[#233241] cursor-pointer">
<h3 className="text-lg">Up Time</h3> <span className="text-slate-300">{upTime}</span> <h3 className="text-lg">Up Time</h3> <span className="text-slate-300">{upTime}</span>
<h3 className="text-lg">Start Time</h3> <span className="text-slate-300">{startTime}</span> <h3 className="text-lg">Start Time</h3> <span className="text-slate-300">{startTime}</span>
</div>
</div>
</Card> </Card>
</div>
</div>
); );
}; };

View File

@@ -19,7 +19,7 @@ const SightingStack = ({ sightings }: SightingStackProps) => {
return ( return (
<> <>
<Card className="p-4 w-full h-full"> <Card className="p-4 w-full h-[65vh] overflow-y-auto">
<CardHeader title="Live Sightings" /> <CardHeader title="Live Sightings" />
<div className="md:h-[65%]"> <div className="md:h-[65%]">
{sightings.map((sighting) => ( {sightings.map((sighting) => (

View File

@@ -17,6 +17,7 @@ const VideoFeed = ({ mostRecentSighting, isLoading, size, modeSetting, isModal =
const contextMode = cameraSettings.mode; const contextMode = cameraSettings.mode;
const [localMode, setLocalMode] = useState(0); const [localMode, setLocalMode] = useState(0);
const mode = isModal ? localMode : contextMode; const mode = isModal ? localMode : contextMode;
console.log(mode);
const { image, plateRect, plateTrack } = useCreateVideoSnapshot(mostRecentSighting); const { image, plateRect, plateTrack } = useCreateVideoSnapshot(mostRecentSighting);
const handleModeChange = (newMode: number) => { const handleModeChange = (newMode: number) => {
@@ -33,7 +34,7 @@ const VideoFeed = ({ mostRecentSighting, isLoading, size, modeSetting, isModal =
useEffect(() => { useEffect(() => {
const updateSize = () => { const updateSize = () => {
const width = window.innerWidth * 0.57; const width = window.innerWidth * 0.48;
const height = (width * 2) / 3; const height = (width * 2) / 3;
dispatch({ type: "SET_IMAGE_SIZE", payload: { width, height } }); dispatch({ type: "SET_IMAGE_SIZE", payload: { width, height } });
}; };

View File

@@ -4,6 +4,7 @@ import type { SightingType } from "../../../utils/types";
export const useSightingList = () => { export const useSightingList = () => {
const [sightingList, setSightingList] = useState<SightingType[]>([]); const [sightingList, setSightingList] = useState<SightingType[]>([]);
const [totalSightings, setTotalSightings] = useState<number>(0);
const { videoFeedQuery } = useVideoFeed(); const { videoFeedQuery } = useVideoFeed();
const latestSighting = videoFeedQuery?.data; const latestSighting = videoFeedQuery?.data;
const lastProcessedRef = useRef<number>(-1); const lastProcessedRef = useRef<number>(-1);
@@ -11,6 +12,8 @@ export const useSightingList = () => {
useEffect(() => { useEffect(() => {
if (!latestSighting || latestSighting.ref === undefined || latestSighting.ref === -1) return; if (!latestSighting || latestSighting.ref === undefined || latestSighting.ref === -1) return;
// eslint-disable-next-line react-hooks/set-state-in-effect
setTotalSightings((prev) => (latestSighting.ref! > prev ? latestSighting.ref! : prev));
if (latestSighting.ref !== lastProcessedRef.current) { if (latestSighting.ref !== lastProcessedRef.current) {
lastProcessedRef.current = latestSighting.ref; lastProcessedRef.current = latestSighting.ref;
@@ -23,5 +26,5 @@ export const useSightingList = () => {
}); });
} }
}, [latestSighting, latestSighting?.ref]); }, [latestSighting, latestSighting?.ref]);
return { sightingList, isLoading }; return { sightingList, isLoading, totalSightings };
}; };

View File

@@ -13,7 +13,8 @@ const cols = 40;
const gap = 0; const gap = 0;
const VideoFeedSetup = () => { const VideoFeedSetup = () => {
const { latestBitmapRef, isLoading } = useCreateVideoPreviewSnapshot(); const { latestBitmapRef, isPreviewLoading } = useCreateVideoPreviewSnapshot();
const { state, dispatch } = useCameraSettingsContext(); const { state, dispatch } = useCameraSettingsContext();
const cameraMode = state.cameraMode; const cameraMode = state.cameraMode;
const paintedCells = state.regionPainter.paintedCells; const paintedCells = state.regionPainter.paintedCells;
@@ -84,9 +85,10 @@ const VideoFeedSetup = () => {
updateSize(); updateSize();
window.addEventListener("resize", updateSize); window.addEventListener("resize", updateSize);
return () => window.removeEventListener("resize", updateSize); return () => window.removeEventListener("resize", updateSize);
}, []); }, [dispatch]);
if (isPreviewLoading) return <>Loading Preview...</>;
if (isLoading) return <>Loading...</>;
return ( return (
<div className="mt-[1%]"> <div className="mt-[1%]">
<Stage width={size.width} height={size.height} onMouseDown={handleStageMouseDown} onMouseMove={handleMouseMove}> <Stage width={size.width} height={size.height} onMouseDown={handleStageMouseDown} onMouseMove={handleMouseMove}>

View File

@@ -6,7 +6,9 @@ export const useCreateVideoPreviewSnapshot = () => {
const { state } = useCameraSettingsContext(); const { state } = useCameraSettingsContext();
const { videoPreviewQuery, targetDetectionFeedQuery } = useVideoPreview(state.cameraMode); const { videoPreviewQuery, targetDetectionFeedQuery } = useVideoPreview(state.cameraMode);
const latestBitmapRef = useRef<ImageBitmap | null>(null); const latestBitmapRef = useRef<ImageBitmap | null>(null);
const isLoading = videoPreviewQuery?.isPending || targetDetectionFeedQuery?.isPending; const isPreviewLoading = videoPreviewQuery?.isPending;
const isTargetDetectionLoading = targetDetectionFeedQuery?.isPending;
let snapshot; let snapshot;
if (state.cameraMode === 0) { if (state.cameraMode === 0) {
snapshot = videoPreviewQuery?.data; snapshot = videoPreviewQuery?.data;
@@ -20,6 +22,7 @@ export const useCreateVideoPreviewSnapshot = () => {
try { try {
const bitmap = await createImageBitmap(imageBlob); const bitmap = await createImageBitmap(imageBlob);
latestBitmapRef.current = bitmap; latestBitmapRef.current = bitmap;
} catch (error) { } catch (error) {
console.log(error); console.log(error);
@@ -28,5 +31,10 @@ export const useCreateVideoPreviewSnapshot = () => {
createImageBitmapFromBlob(); createImageBitmapFromBlob();
}, [imageBlob]); }, [imageBlob]);
return { latestBitmapRef, isLoading, imageURL: imageBlob ? URL.createObjectURL(imageBlob) : null }; return {
latestBitmapRef,
isPreviewLoading,
isTargetDetectionLoading,
imageURL: imageBlob ? URL.createObjectURL(imageBlob) : null,
};
}; };