Merged in bugfix/sessionstats (pull request #14)

bugfix/sessionstats
This commit is contained in:
2025-10-08 14:52:24 +00:00
7 changed files with 82 additions and 24 deletions

View File

@@ -19,6 +19,8 @@ const AlertItem = ({ item }: AlertItemProps) => {
// const {d} = useCameraBlackboard(); // const {d} = useCameraBlackboard();
const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY";
// [34].metadata.hotlistMatches["MAV_Hotlist.csv"]
//check if true is in any hotlist property
const isHotListHit = item?.metadata?.hotlistMatches?.Hotlist0 === true; const isHotListHit = item?.metadata?.hotlistMatches?.Hotlist0 === true;
const isNPEDHitA = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "A"; const isNPEDHitA = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "B"; const isNPEDHitB = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";

View File

@@ -1,32 +1,80 @@
import Card from "../UI/Card"; import Card from "../UI/Card";
import CardHeader from "../UI/CardHeader"; import CardHeader from "../UI/CardHeader";
import { useNPEDContext } from "../../context/NPEDUserContext"; import { useNPEDContext } from "../../context/NPEDUserContext";
import type { ReducedSightingType } from "../../types/types";
import { toast } from "sonner";
const SessionCard = () => { const SessionCard = () => {
const { sessionStarted, setSessionStarted, sessionList } = useNPEDContext(); const { sessionStarted, setSessionStarted, sessionList } = useNPEDContext();
const handleStartClick = () => { const handleStartClick = () => {
setSessionStarted(!sessionStarted); setSessionStarted(!sessionStarted);
toast(
`${
sessionStarted
? "Vehicle tracking session Ended"
: "Vehicle tracking session Started"
}`
);
}; };
const sightings = [
...new Map(sessionList.map((vehicle) => [vehicle.vrm, vehicle])),
];
const dedupedSightings = sightings.map((sighting) => sighting[1]);
const vehicles = dedupedSightings.reduce<
Record<string, ReducedSightingType[]>
>(
(acc, item) => {
if (item.metadata?.npedJSON["NPED CATEGORY"] === "A")
acc.npedCatA.push(item);
if (item.metadata?.npedJSON["NPED CATEGORY"] === "B")
acc.npedCatB.push(item);
if (item.metadata?.npedJSON["NPED CATEGORY"] === "C")
acc.npedCatC.push(item);
if (item.metadata?.npedJSON["NPED CATEGORY"] === "D")
acc.npedCatD.push(item);
if (item.metadata?.npedJSON["TAX STATUS"] === false)
acc.notTaxed.push(item);
if (item.metadata?.npedJSON["MOT STATUS"] === false)
acc.notMOT.push(item);
return acc;
},
{
npedCatA: [],
npedCatB: [],
npedCatC: [],
npedCatD: [],
notTaxed: [],
notMOT: [],
}
);
return ( return (
<Card className="p-4"> <Card className="p-4">
<CardHeader title="Session" /> <CardHeader title="Session" />
<div className="flex flex-col gap-4 px-2"> <div className="flex flex-col gap-4 px-2">
<button <button
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-full" className={`${
sessionStarted ? "bg-red-600" : "bg-[#26B170]"
} text-white px-4 py-2 rounded ${
sessionStarted ? "hover:bg-red-700" : "hover:bg-green-700"
} transition w-full`}
onClick={handleStartClick} onClick={handleStartClick}
> >
Start Session {sessionStarted ? "End Session" : "Start Session"}
</button> </button>
<ul className="text-white space-y-2"> <ul className="text-white space-y-2">
<li>Number of Vehicles: {sessionList.length} </li> <li>Number of Vehicles: {dedupedSightings.length} </li>
<li>Vehicles without Tax: </li> <li>Vehicles without Tax: {vehicles.notTaxed.length}</li>
<li>Vehicles without MOT: </li> <li>Vehicles without MOT: {vehicles.notMOT.length}</li>
<li>Vehicles with NPED Cat A: </li> <li>Vehicles with NPED Cat A: {vehicles.npedCatA.length}</li>
<li>Vehicles with NPED Cat B: </li> <li>Vehicles with NPED Cat B: {vehicles.npedCatB.length}</li>
<li>Vehicles with NPED Cat C: </li> <li>Vehicles with NPED Cat C: {vehicles.npedCatC.length}</li>
</ul> </ul>
</div> </div>
</Card> </Card>

View File

@@ -1,5 +1,5 @@
import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import type { SightingType } from "../../types/types"; import type { ReducedSightingType, SightingType } from "../../types/types";
import { BLANK_IMG, getSoundFileURL } from "../../utils/utils"; import { BLANK_IMG, getSoundFileURL } from "../../utils/utils";
import NumberPlate from "../PlateStack/NumberPlate"; import NumberPlate from "../PlateStack/NumberPlate";
import Card from "../UI/Card"; import Card from "../UI/Card";
@@ -61,10 +61,18 @@ export default function SightingHistoryWidget({
const { dispatch } = useAlertHitContext(); const { dispatch } = useAlertHitContext();
const { sessionStarted, setSessionList, sessionList } = useNPEDContext(); const { sessionStarted, setSessionList, sessionList } = useNPEDContext();
const reduceObject = (obj: SightingType): ReducedSightingType => {
return {
vrm: obj.vrm,
metadata: obj?.metadata,
};
};
useEffect(() => { useEffect(() => {
if (sessionStarted) { if (sessionStarted) {
if (!mostRecent) return; if (!mostRecent) return;
setSessionList([...sessionList, mostRecent]); const reducedMostRecent = reduceObject(mostRecent);
setSessionList([...sessionList, reducedMostRecent]);
} }
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [mostRecent, sessionStarted, setSessionList]); }, [mostRecent, sessionStarted, setSessionList]);

View File

@@ -1,13 +1,13 @@
import { createContext, useContext, type SetStateAction } from "react"; import { createContext, useContext, type SetStateAction } from "react";
import type { NPEDUser, SightingType } from "../types/types"; import type { NPEDUser, ReducedSightingType } from "../types/types";
type UserContextValue = { type UserContextValue = {
user: NPEDUser | null; user: NPEDUser | null;
setUser: React.Dispatch<SetStateAction<NPEDUser | null>>; setUser: React.Dispatch<SetStateAction<NPEDUser | null>>;
sessionStarted: boolean; sessionStarted: boolean;
setSessionStarted: React.Dispatch<SetStateAction<boolean>>; setSessionStarted: React.Dispatch<SetStateAction<boolean>>;
sessionList: SightingType[]; sessionList: ReducedSightingType[];
setSessionList: React.Dispatch<SetStateAction<SightingType[]>>; setSessionList: React.Dispatch<SetStateAction<ReducedSightingType[]>>;
}; };
export const NPEDUserContext = createContext<UserContextValue | undefined>( export const NPEDUserContext = createContext<UserContextValue | undefined>(

View File

@@ -1,5 +1,5 @@
import { useState, type ReactNode } from "react"; import { useState, type ReactNode } from "react";
import type { NPEDUser, SightingType } from "../../types/types"; import type { NPEDUser, ReducedSightingType } from "../../types/types";
import { NPEDUserContext } from "../NPEDUserContext"; import { NPEDUserContext } from "../NPEDUserContext";
type NPEDUserProviderType = { type NPEDUserProviderType = {
@@ -9,7 +9,7 @@ type NPEDUserProviderType = {
export const NPEDUserProvider = ({ children }: NPEDUserProviderType) => { export const NPEDUserProvider = ({ children }: NPEDUserProviderType) => {
const [user, setUser] = useState<NPEDUser | null>(null); const [user, setUser] = useState<NPEDUser | null>(null);
const [sessionStarted, setSessionStarted] = useState(false); const [sessionStarted, setSessionStarted] = useState(false);
const [sessionList, setSessionList] = useState<SightingType[]>([]); const [sessionList, setSessionList] = useState<ReducedSightingType[]>([]);
return ( return (
<NPEDUserContext.Provider <NPEDUserContext.Provider

View File

@@ -22,7 +22,6 @@ export function useSightingFeed(url: string | undefined) {
const [sightings, setSightings] = useState<SightingType[]>([]); const [sightings, setSightings] = useState<SightingType[]>([]);
const [selectedRef, setSelectedRef] = useState<number | null>(null); const [selectedRef, setSelectedRef] = useState<number | null>(null);
const [sessionStarted, setSessionStarted] = useState(false); const [sessionStarted, setSessionStarted] = useState(false);
const [sessionList, setSessionList] = useState<SightingType[]>([]);
const mostRecent = sightings[0] ?? null; const mostRecent = sightings[0] ?? null;
const latestRef = mostRecent?.ref ?? null; const latestRef = mostRecent?.ref ?? null;
const [selectedSighting, setSelectedSighting] = useState<SightingType | null>( const [selectedSighting, setSelectedSighting] = useState<SightingType | null>(
@@ -76,13 +75,6 @@ export function useSightingFeed(url: string | undefined) {
staleTime: 0, staleTime: 0,
}); });
useEffect(() => {
if (sessionStarted) {
if (!mostRecent) return;
setSessionList([...sessionList, mostRecent]);
}
}, [mostRecent, sessionList, sessionStarted]);
useEffect(() => { useEffect(() => {
const data = query.data; const data = query.data;
if (!data) return; if (!data) return;
@@ -129,7 +121,6 @@ export function useSightingFeed(url: string | undefined) {
setSelectedRef, setSelectedRef,
mostRecent, mostRecent,
selectedSighting, selectedSighting,
sessionList,
sessionStarted, sessionStarted,
setSessionStarted, setSessionStarted,
setSelectedSighting, setSelectedSighting,

View File

@@ -30,6 +30,11 @@ export type SightingType = {
metadata?: Metadata; metadata?: Metadata;
}; };
export type ReducedSightingType = {
vrm: string;
metadata?: Metadata;
};
export type CameraSettingValues = { export type CameraSettingValues = {
friendlyName: string; friendlyName: string;
cameraAddress: string; cameraAddress: string;
@@ -103,6 +108,10 @@ export type NpedJSON = {
status_code: number; status_code: number;
reason_phrase: string; reason_phrase: string;
"NPED CATEGORY": "A" | "B" | "C" | "D"; "NPED CATEGORY": "A" | "B" | "C" | "D";
"MOT STATUS": boolean;
"TAX STATUS": boolean;
vrm: string;
"INSURANCE STATUS": string;
}; };
export type NPEDUser = { export type NPEDUser = {