Merged in develop (pull request #16)

Develop
This commit is contained in:
2025-10-09 13:23:30 +00:00
19 changed files with 192 additions and 122 deletions

View File

@@ -8,6 +8,7 @@ import { useAlertHitContext } from "../../context/AlertHitContext";
import NPED_CAT_A from "/NPED_Cat_A.svg"; import NPED_CAT_A from "/NPED_Cat_A.svg";
import NPED_CAT_B from "/NPED_Cat_B.svg"; import NPED_CAT_B from "/NPED_Cat_B.svg";
import NPED_CAT_C from "/NPED_Cat_C.svg"; import NPED_CAT_C from "/NPED_Cat_C.svg";
import { checkIsHotListHit } from "../../utils/utils";
type AlertItemProps = { type AlertItemProps = {
item: SightingType; item: SightingType;
@@ -19,7 +20,8 @@ const AlertItem = ({ item }: AlertItemProps) => {
// const {d} = useCameraBlackboard(); // const {d} = useCameraBlackboard();
const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY";
const isHotListHit = item?.metadata?.hotlistMatches?.Hotlist0 === true;
const isHotListHit = checkIsHotListHit(item);
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";
const isNPEDHitC = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "C"; const isNPEDHitC = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";

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

@@ -2,15 +2,14 @@ import { Field, FieldArray, Form, Formik } from "formik";
import FormGroup from "../components/FormGroup"; import FormGroup from "../components/FormGroup";
import type { FormValues, Hotlist } from "../../../types/types"; import type { FormValues, Hotlist } from "../../../types/types";
import { useSoundContext } from "../../../context/SoundContext"; import { useSoundContext } from "../../../context/SoundContext";
import { useCameraBlackboard } from "../../../hooks/useCameraBlackboard";
import { toast } from "sonner"; import { toast } from "sonner";
const SoundSettingsFields = () => { const SoundSettingsFields = () => {
const { state, dispatch } = useSoundContext(); const { state, dispatch } = useSoundContext();
const hotlists: Hotlist[] = [ const { mutation } = useCameraBlackboard();
{ name: "hotlist0", sound: "" },
{ name: "hotlist1", sound: "" }, const hotlists: Hotlist[] = state.hotlists;
{ name: "hotlist2", sound: "" },
];
const soundOptions = state?.soundOptions?.map((soundOption) => ({ const soundOptions = state?.soundOptions?.map((soundOption) => ({
value: soundOption?.name, value: soundOption?.name,
@@ -23,9 +22,18 @@ const SoundSettingsFields = () => {
hotlists, hotlists,
}; };
const handleSubmit = (values: FormValues) => { const handleSubmit = async (values: FormValues) => {
dispatch({ type: "UPDATE", payload: values }); dispatch({ type: "UPDATE", payload: values });
toast.success("Sound settings updated"); const result = await mutation.mutateAsync({
operation: "INSERT",
path: "soundSettings",
value: values,
});
if (result.reason !== "OK") {
toast.error("Cannot update sound settings");
} else {
toast.success("Sound Settings successfully updated");
}
}; };
return ( return (
<Formik initialValues={initialValues} onSubmit={handleSubmit}> <Formik initialValues={initialValues} onSubmit={handleSubmit}>
@@ -68,8 +76,8 @@ const SoundSettingsFields = () => {
name="hotlists" name="hotlists"
render={() => ( render={() => (
<div className="w-full m-2"> <div className="w-full m-2">
{values.hotlists.length > 0 ? ( {values?.hotlists?.length > 0 ? (
values.hotlists.map((hotlist, index) => ( values?.hotlists?.map((hotlist, index) => (
<div <div
key={hotlist.name} key={hotlist.name}
className="flex items-center m-2 w-full justify-between" className="flex items-center m-2 w-full justify-between"

View File

@@ -10,6 +10,7 @@ import HotListImg from "/Hotlist_Hit.svg";
import NPED_CAT_A from "/NPED_Cat_A.svg"; import NPED_CAT_A from "/NPED_Cat_A.svg";
import NPED_CAT_B from "/NPED_Cat_B.svg"; import NPED_CAT_B from "/NPED_Cat_B.svg";
import NPED_CAT_C from "/NPED_Cat_C.svg"; import NPED_CAT_C from "/NPED_Cat_C.svg";
import { checkIsHotListHit } from "../../utils/utils";
type SightingModalProps = { type SightingModalProps = {
isSightingModalOpen: boolean; isSightingModalOpen: boolean;
@@ -65,7 +66,7 @@ const SightingModal = ({
}; };
const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY";
const isHotListHit = sighting?.metadata?.hotlistMatches?.Hotlist0 === true; const isHotListHit = checkIsHotListHit(sighting);
const isNPEDHitA = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "A"; const isNPEDHitA = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "B"; const isNPEDHitB = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "C"; const isNPEDHitC = sighting?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";

View File

@@ -5,7 +5,6 @@ type InfoBarprops = {
obj: SightingType; obj: SightingType;
}; };
const InfoBar = ({ obj }: InfoBarprops) => { const InfoBar = ({ obj }: InfoBarprops) => {
// const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404;
const isNPEDHitD = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "D"; const isNPEDHitD = obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "D";
return ( return (

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";
@@ -13,10 +13,12 @@ import NPED_CAT_A from "/NPED_Cat_A.svg";
import NPED_CAT_B from "/NPED_Cat_B.svg"; import NPED_CAT_B from "/NPED_Cat_B.svg";
import NPED_CAT_C from "/NPED_Cat_C.svg"; import NPED_CAT_C from "/NPED_Cat_C.svg";
import popup from "../../assets/sounds/ui/popup_open.mp3"; import popup from "../../assets/sounds/ui/popup_open.mp3";
import notification from "../../assets/sounds/ui/notification.mp3";
import { useSound } from "react-sounds"; import { useSound } from "react-sounds";
import { useNPEDContext } from "../../context/NPEDUserContext"; import { useNPEDContext } from "../../context/NPEDUserContext";
import { useSoundContext } from "../../context/SoundContext"; import { useSoundContext } from "../../context/SoundContext";
import Loading from "../UI/Loading"; import Loading from "../UI/Loading";
import { checkIsHotListHit } from "../../utils/utils";
function useNow(tickMs = 1000) { function useNow(tickMs = 1000) {
const [, setNow] = useState(() => Date.now()); const [, setNow] = useState(() => Date.now());
@@ -43,11 +45,16 @@ export default function SightingHistoryWidget({
useNow(1000); useNow(1000);
const { state } = useSoundContext(); const { state } = useSoundContext();
const soundSrc = useMemo(() => { const soundSrcNped = useMemo(() => {
return getSoundFileURL(state.NPEDsound) ?? popup; return getSoundFileURL(state.NPEDsound) ?? popup;
}, [state.NPEDsound]); }, [state.NPEDsound]);
const { play } = useSound(soundSrc); const soundSrcHotlist = useMemo(() => {
return getSoundFileURL(state?.hotlists?.[0]?.sound) ?? notification;
}, [state.hotlists]);
const { play: npedSound } = useSound(soundSrcNped);
const { play: hotlistsound } = useSound(soundSrcHotlist);
const { const {
sightings, sightings,
setSelectedSighting, setSelectedSighting,
@@ -61,15 +68,24 @@ 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]);
const hasAutoOpenedRef = useRef(false); const hasAutoOpenedRef = useRef(false);
const npedRef = useRef(false);
const onRowClick = useCallback( const onRowClick = useCallback(
(sighting: SightingType) => { (sighting: SightingType) => {
@@ -98,24 +114,43 @@ export default function SightingHistoryWidget({
}); });
} }
}); });
}, [dispatch, rows]); }, [dispatch]);
useEffect(() => { useEffect(() => {
if (hasAutoOpenedRef.current) return; if (hasAutoOpenedRef.current || npedRef.current) return;
const firstHot = rows?.find((r) => { const firstNPED = rows.find((r) => {
const isHotListHit = r?.metadata?.hotlistMatches?.Hotlist0 === true;
const isNPEDHitA = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "A"; const isNPEDHitA = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "A";
const isNPEDHitB = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "B"; const isNPEDHitB = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "C"; const isNPEDHitC = r?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
return isNPEDHitA || isNPEDHitB || isNPEDHitC || isHotListHit; return isNPEDHitA || isNPEDHitB || isNPEDHitC;
}); });
const firstHot = rows?.find((r) => {
const isHotListHit = checkIsHotListHit(r);
return isHotListHit;
});
if (firstNPED) {
setSelectedSighting(firstNPED);
console.log("first");
npedSound();
setSightingModalOpen(true);
npedRef.current = true;
}
if (firstHot) { if (firstHot) {
setSelectedSighting(firstHot); setSelectedSighting(firstHot);
play(); hotlistsound();
setSightingModalOpen(true); setSightingModalOpen(true);
hasAutoOpenedRef.current = true; hasAutoOpenedRef.current = true;
} }
}, [play, rows, setSelectedSighting, setSightingModalOpen]); }, [
hotlistsound,
npedSound,
rows,
setSelectedSighting,
setSightingModalOpen,
]);
const handleClose = () => { const handleClose = () => {
setSightingModalOpen(false); setSightingModalOpen(false);
@@ -144,11 +179,8 @@ export default function SightingHistoryWidget({
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "B"; obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "B";
const isNPEDHitC = const isNPEDHitC =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "C"; obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "C";
const isNPEDHitD =
obj?.metadata?.npedJSON?.["NPED CATEGORY"] === "D";
const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY"; const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY";
const isHotListHit = const isHotListHit = checkIsHotListHit(obj);
obj?.metadata?.hotlistMatches?.Hotlist0 === true;
return ( return (
<div <div
key={obj.ref} key={obj.ref}
@@ -156,9 +188,7 @@ export default function SightingHistoryWidget({
onClick={() => onRowClick(obj)} onClick={() => onRowClick(obj)}
> >
<div <div
className={`flex items-center gap-3 mt-2 justify-between ${ className={`flex items-center gap-3 mt-2 justify-between `}
isNPEDHitD ? " border-amber-600" : ""
}`}
> >
<div className={`border p-1 `}> <div className={`border p-1 `}>
<img <img

View File

@@ -1,13 +1,25 @@
import { faVolumeHigh, faVolumeXmark } from "@fortawesome/free-solid-svg-icons"; import { faVolumeHigh, faVolumeXmark } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { useSoundEnabled } from "react-sounds"; import { useSoundEnabled } from "react-sounds";
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
import { useEffect } from "react";
const SoundBtn = () => { const SoundBtn = () => {
const { mutation, query } = useCameraBlackboard();
const [enabled, setEnabled] = useSoundEnabled(); const [enabled, setEnabled] = useSoundEnabled();
const handleClick = () => { const handleClick = async () => {
setEnabled(!enabled); const newEnabled = !enabled;
setEnabled(newEnabled);
await mutation.mutateAsync({
operation: "INSERT",
path: "soundEnabled",
value: { enabled: newEnabled },
});
}; };
useEffect(() => {
setEnabled(query?.data?.soundEnabled?.enabled);
}, [query?.data?.soundEnabled?.enabled, setEnabled]);
return ( return (
<button onClick={handleClick}> <button onClick={handleClick}>

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

@@ -15,9 +15,7 @@ type SightingFeedContextType = {
isError: boolean; isError: boolean;
isLoading: boolean; isLoading: boolean;
data: SightingType | undefined; data: SightingType | undefined;
sessionList: SightingType[];
sessionStarted: boolean; sessionStarted: boolean;
setSessionStarted: (started: boolean) => void;
}; };
export const SightingFeedContext = createContext< export const SightingFeedContext = createContext<

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

@@ -23,9 +23,7 @@ export const SightingFeedProvider = ({
setSelectedSighting, setSelectedSighting,
selectedSighting, selectedSighting,
mostRecent, mostRecent,
sessionList,
sessionStarted, sessionStarted,
setSessionStarted,
} = useSightingFeed(url); } = useSightingFeed(url);
const [isSightingModalOpen, setSightingModalOpen] = useState(false); const [isSightingModalOpen, setSightingModalOpen] = useState(false);
@@ -45,9 +43,7 @@ export const SightingFeedProvider = ({
isLoading, isLoading,
side, side,
data, data,
sessionList,
sessionStarted, sessionStarted,
setSessionStarted,
}} }}
> >
{children} {children}

View File

@@ -1,6 +1,7 @@
import { useMemo, useReducer, type ReactNode } from "react"; import { useEffect, useMemo, useReducer, type ReactNode } from "react";
import { SoundContext } from "../SoundContext"; import { SoundContext } from "../SoundContext";
import { initialState, reducer } from "../reducers/SoundContextReducer"; import { initialState, reducer } from "../reducers/SoundContextReducer";
import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
type SoundContextProviderProps = { type SoundContextProviderProps = {
children: ReactNode; children: ReactNode;
@@ -8,6 +9,20 @@ type SoundContextProviderProps = {
const SoundContextProvider = ({ children }: SoundContextProviderProps) => { const SoundContextProvider = ({ children }: SoundContextProviderProps) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const { mutation } = useCameraBlackboard();
useEffect(() => {
const fetchSound = async () => {
const result = await mutation.mutateAsync({
operation: "VIEW",
path: "soundSettings",
});
dispatch({ type: "UPDATE", payload: result.result });
};
fetchSound();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const value = useMemo(() => ({ state, dispatch }), [state, dispatch]); const value = useMemo(() => ({ state, dispatch }), [state, dispatch]);
return ( return (
<SoundContext.Provider value={value}>{children}</SoundContext.Provider> <SoundContext.Provider value={value}>{children}</SoundContext.Provider>

View File

@@ -3,7 +3,7 @@ import type { SoundAction, SoundState } from "../../types/types";
export const initialState: SoundState = { export const initialState: SoundState = {
sightingSound: "switch", sightingSound: "switch",
NPEDsound: "popup", NPEDsound: "popup",
hotlists: [], hotlists: [{ name: "hotlistName", sound: "notification" }],
soundOptions: [ soundOptions: [
{ name: "switch (Default)", soundFile: null }, { name: "switch (Default)", soundFile: null },
{ name: "popup", soundFile: null }, { name: "popup", soundFile: null },
@@ -18,7 +18,7 @@ export function reducer(state: SoundState, action: SoundAction): SoundState {
...state, ...state,
sightingSound: action.payload.sightingSound, sightingSound: action.payload.sightingSound,
NPEDsound: action.payload.NPEDsound, NPEDsound: action.payload.NPEDsound,
hotlists: action.payload.hotlists.map((hotlist) => ({ hotlists: action.payload.hotlists?.map((hotlist) => ({
name: hotlist.name, name: hotlist.name,
sound: hotlist.sound, sound: hotlist.sound,
})), })),

View File

@@ -45,7 +45,6 @@ export const useCameraBlackboard = () => {
id: "viewBlackboardData", id: "viewBlackboardData",
}); });
}, },
onSuccess: () => toast.success("Sighting successfully added to alert list"),
}); });
useEffect(() => { useEffect(() => {

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>(
@@ -39,7 +38,7 @@ export function useSightingFeed(url: string | undefined) {
return latestRef; return latestRef;
}, [latestRef]); }, [latestRef]);
const soundSrc = useMemo(() => { const soundSrc = useMemo(() => {
return getSoundFileURL(state.sightingSound) ?? switchSound; return getSoundFileURL(state?.sightingSound) ?? switchSound;
}, [state.sightingSound]); }, [state.sightingSound]);
//use latestref instead of trigger to revert back //use latestref instead of trigger to revert back
@@ -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

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 = {

View File

@@ -1,6 +1,7 @@
import switchSound from "../assets/sounds/ui/switch.mp3"; import switchSound from "../assets/sounds/ui/switch.mp3";
import popup from "../assets/sounds/ui/popup_open.mp3"; import popup from "../assets/sounds/ui/popup_open.mp3";
import notification from "../assets/sounds/ui/notification.mp3"; import notification from "../assets/sounds/ui/notification.mp3";
import type { SightingType } from "../types/types";
export function getSoundFileURL(name: string) { export function getSoundFileURL(name: string) {
const sounds: Record<string, string> = { const sounds: Record<string, string> = {
@@ -129,59 +130,12 @@ export function drawRects(
}); });
} }
// setSelectedRef(data?.ref); export const checkIsHotListHit = (sigthing: SightingType | null) => {
if (!sigthing) return;
//setItems(data); if (sigthing?.metadata?.hotlistMatches) {
const isHotListHit = Object.values(
// const selected = useMemo( sigthing?.metadata?.hotlistMatches
// () => ).includes(true);
// selectedRef == null return isHotListHit;
// ? null }
// : items.find((x) => x?.ref === selectedRef) ?? null, };
// [items, selectedRef]
// );
// const effectiveSelected = selected ?? mostRecent ?? null;
// useEffect(() => {
// let delay = pollMs;
// let dead = false;
// const controller = new AbortController();
// async function tick() {
// try {
// // Pause when tab hidden to save CPU/network
// if (document.hidden) {
// setTimeout(tick, Math.max(delay, 2000));
// return;
// }
// if (obj && typeof obj.ref === "number" && obj.ref > -1) {
// setItems((prev) => {
// const next = [obj, ...prev].slice(0, limit);
// // maintain selection if still present; otherwise select newest if allowed
// const stillExists =
// selectedRef != null && next.some((x) => x?.ref === selectedRef);
// if (autoSelectLatest && !stillExists) {
// setSelectedRef(obj.ref);
// }
// return next;
// });
// setMostRecent(obj);
// mostRecentRef.current = obj.ref;
// delay = pollMs; // reset backoff on success
// }
// } catch {
// // exponential backoff (max 10s)
// delay = Math.min(delay * 2, 10000);
// } finally {
// if (!dead) setTimeout(tick, delay);
// }
// }
// const t = setTimeout(tick, pollMs);
// return () => {
// dead = true;
// controller.abort();
// clearTimeout(t);
// };
// }, [baseUrl, limit, pollMs, autoSelectLatest, selectedRef]);

View File

@@ -5,6 +5,14 @@ import tailwindcss from "@tailwindcss/vite";
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react(), tailwindcss()], plugins: [react(), tailwindcss()],
server: { host: true }, server: {
host: true,
proxy: {
"/api": {
target: "http://100.118.196.113:8080",
changeOrigin: true,
},
},
},
base: "/Mobile", base: "/Mobile",
}); });