import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ReducedSightingType, SightingType } from "../../types/types"; import { BLANK_IMG, getSoundFileURL } from "../../utils/utils"; import NumberPlate from "../PlateStack/NumberPlate"; import Card from "../UI/Card"; import CardHeader from "../UI/CardHeader"; import clsx from "clsx"; import { useSightingFeedContext } from "../../context/SightingFeedContext"; import SightingModal from "../SightingModal/SightingModal"; import { useAlertHitContext } from "../../context/AlertHitContext"; import HotListImg from "/Hotlist_Hit.svg"; import NPED_CAT_A from "/NPED_Cat_A.svg"; import NPED_CAT_B from "/NPED_Cat_B.svg"; import NPED_CAT_C from "/NPED_Cat_C.svg"; import popup from "../../assets/sounds/ui/popup_open.mp3"; import notification from "../../assets/sounds/ui/notification.mp3"; import { useSound } from "react-sounds"; import { useNPEDContext } from "../../context/NPEDUserContext"; import { useSoundContext } from "../../context/SoundContext"; import Loading from "../UI/Loading"; import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils"; function useNow(tickMs = 1000) { const [, setNow] = useState(() => Date.now()); useEffect(() => { const id = setInterval(() => setNow(Date.now()), tickMs); return () => clearInterval(id); }, [tickMs]); return null; } type SightingHistoryProps = { baseUrl?: string; entries?: number; pollMs?: number; autoSelectLatest?: boolean; title: string; className?: string; }; export default function SightingHistoryWidget({ className, title, }: SightingHistoryProps) { useNow(1000); const { state } = useSoundContext(); const soundSrcNped = useMemo(() => { return getSoundFileURL(state.NPEDsound) ?? popup; }, [state.NPEDsound]); const soundSrcHotlist = useMemo(() => { return getSoundFileURL(state?.hotlists?.[0]?.sound) ?? notification; }, [state.hotlists]); const { play: npedSound } = useSound(soundSrcNped); const { play: hotlistsound } = useSound(soundSrcHotlist); const { sightings, setSelectedSighting, setSightingModalOpen, isSightingModalOpen, selectedSighting, mostRecent, isLoading, } = useSightingFeedContext(); const { dispatch } = useAlertHitContext(); const { sessionStarted, setSessionList, sessionList } = useNPEDContext(); const processedRefs = useRef>(new Set()); const hasAutoOpenedRef = useRef(false); const npedRef = useRef(false); const reduceObject = (obj: SightingType): ReducedSightingType => { return { vrm: obj.vrm, metadata: obj?.metadata, }; }; useEffect(() => { if (sessionStarted) { if (!mostRecent) return; const reducedMostRecent = reduceObject(mostRecent); setSessionList([...sessionList, reducedMostRecent]); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [mostRecent, sessionStarted, setSessionList]); const onRowClick = useCallback( (sighting: SightingType) => { if (!sighting) return; setSightingModalOpen(true); setSelectedSighting(sighting); }, [setSelectedSighting, setSightingModalOpen] ); const rows = useMemo( () => sightings?.filter(Boolean) as SightingType[], [sightings] ); useEffect(() => { if (!rows?.length) return; for (const sighting of rows) { const id = sighting.vrm; if (processedRefs.current.has(id)) continue; const isHot = checkIsHotListHit(sighting); const cat = sighting?.metadata?.npedJSON?.["NPED CATEGORY"]; if (cat === "A" || cat === "B" || cat === "C") { npedSound(); setSelectedSighting(sighting); setSightingModalOpen(true); processedRefs.current.add(id); break; // stop after one new open per render cycle } if (isHot) { hotlistsound(); setSelectedSighting(sighting); setSightingModalOpen(true); processedRefs.current.add(id); break; } } }, [ rows, hotlistsound, npedSound, setSightingModalOpen, setSelectedSighting, ]); useEffect(() => { rows?.forEach((obj) => { const cat = getNPEDCategory(obj); const isNPEDHitA = cat === "A"; const isNPEDHitB = cat === "B"; const isNPEDHitC = cat === "C"; if (isNPEDHitA || isNPEDHitB || isNPEDHitC) { dispatch({ type: "ADD", payload: obj, }); } }); }, [dispatch]); useEffect(() => { if (hasAutoOpenedRef.current || npedRef.current) return; const firstNPED = rows.find((r) => { const cat = getNPEDCategory(r); const isNPEDHitA = cat === "A"; const isNPEDHitB = cat === "B"; const isNPEDHitC = cat === "C"; return isNPEDHitA || isNPEDHitB || isNPEDHitC; }); const firstHot = rows?.find((r) => { const isHotListHit = checkIsHotListHit(r); return isHotListHit; }); if (firstNPED) { setSelectedSighting(firstNPED); npedSound(); setSightingModalOpen(true); npedRef.current = true; } if (firstHot) { setSelectedSighting(firstHot); hotlistsound(); setSightingModalOpen(true); hasAutoOpenedRef.current = true; } }, [hotlistsound, npedSound, setSelectedSighting]); const handleClose = () => { setSightingModalOpen(false); }; return ( <>
{isLoading && (
)} {/* Rows */}
{rows?.map((obj) => { const cat = getNPEDCategory(obj); const isNPEDHitA = cat === "A"; const isNPEDHitB = cat === "B"; const isNPEDHitC = cat === "C"; const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY"; const isHotListHit = checkIsHotListHit(obj); return (
onRowClick(obj)} >
colour patch
{isHotListHit && ( hotlistHit )} {isNPEDHitA && ( hotlistHit )} {isNPEDHitB && ( hotlistHit )} {isNPEDHitC && ( hotlistHit )}
); })}
); }