
([]);
useNow(1000);
const { state } = useSoundContext();
const soundSrcNped = useMemo(() => {
+ if (state?.NPEDsound?.includes(".mp3") || state.NPEDsound?.includes(".wav")) {
+ const file = state.soundOptions?.find((item) => item.name === state.NPEDsound);
+ return file?.soundUrl ?? popup;
+ }
return getSoundFileURL(state.NPEDsound) ?? popup;
- }, [state.NPEDsound]);
+ }, [state.NPEDsound, state.soundOptions]);
const soundSrcHotlist = useMemo(() => {
+ if (state?.hotlistSound?.includes(".mp3") || state.hotlistSound?.includes(".wav")) {
+ const file = state.soundOptions?.find((item) => item.name === state.hotlistSound);
+ return file?.soundUrl ?? notification;
+ }
return getSoundFileURL(state?.hotlistSound) ?? notification;
- }, [state?.hotlistSound]);
+ }, [state.hotlistSound, state.soundOptions]);
const { play: npedSound } = useSound(soundSrcNped, { volume: state.NPEDsoundVolume });
const { play: hotlistsound } = useSound(soundSrcHotlist, { volume: state.hotlistSoundVolume });
@@ -63,13 +72,23 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
} = useSightingFeedContext();
const { dispatch } = useAlertHitContext();
- const { sessionStarted, setSessionList, sessionList } = useNPEDContext();
+ const { state: integrationState, dispatch: integrationDispatch } = useIntegrationsContext();
+ const sessionStarted = integrationState.sessionStarted;
+ const sessionPaused = integrationState.sessionPaused;
const processedRefs = useRef
>(new Set());
const hasAutoOpenedRef = useRef(false);
const npedRef = useRef(false);
+ const enqueue = useCallback((sighting: SightingType, kind: HitKind) => {
+ const id = sighting.vrm ?? sighting.ref;
+ if (processedRefs.current.has(id)) return;
+ processedRefs.current.add(id);
+
+ setModalQueue((q) => [...q, { id, sighting, kind }]);
+ }, []);
+
const reduceObject = (obj: SightingType): ReducedSightingType => {
return {
vrm: obj.vrm,
@@ -80,11 +99,12 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
useEffect(() => {
if (sessionStarted) {
if (!mostRecent) return;
+ if (sessionPaused) return;
const reducedMostRecent = reduceObject(mostRecent);
- setSessionList([...sessionList, reducedMostRecent]);
+ integrationDispatch({ type: "ADD", payload: reducedMostRecent });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
- }, [mostRecent, sessionStarted, setSessionList]);
+ }, [mostRecent, sessionStarted]);
const onRowClick = useCallback(
(sighting: SightingType) => {
@@ -104,26 +124,15 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
const id = sighting.vrm;
if (processedRefs.current.has(id)) continue;
- const isHot = checkIsHotListHit(sighting);
- const cat = sighting?.metadata?.npedJSON?.["NPED CATEGORY"];
+ const isHotlistHit = checkIsHotListHit(sighting);
+ const npedcategory = sighting?.metadata?.npedJSON?.["NPED CATEGORY"];
+ const isNPED = npedcategory === "A" || npedcategory === "B" || npedcategory === "C";
- 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;
+ if (isNPED || isHotlistHit) {
+ enqueue(sighting, isNPED ? "NPED" : "HOTLIST"); // enqueue ONLY
}
}
- }, [rows, hotlistsound, npedSound, setSightingModalOpen, setSelectedSighting]);
+ }, [rows, enqueue]);
useEffect(() => {
rows?.forEach((obj) => {
@@ -156,22 +165,33 @@ export default function SightingHistoryWidget({ className, title }: SightingHist
});
if (firstNPED) {
- setSelectedSighting(firstNPED);
- npedSound();
- setSightingModalOpen(true);
+ enqueue(firstNPED, "NPED");
+
npedRef.current = true;
}
if (firstHot) {
- setSelectedSighting(firstHot);
- hotlistsound();
- setSightingModalOpen(true);
+ enqueue(firstHot, "HOTLIST");
+
hasAutoOpenedRef.current = true;
}
- }, [hotlistsound, npedSound, setSelectedSighting]);
+ }, [enqueue, hotlistsound, npedSound, rows, setSelectedSighting, setSightingModalOpen]);
+
+ useEffect(() => {
+ if (!isSightingModalOpen && modalQueue.length > 0) {
+ const next = modalQueue[0];
+
+ // if (next.kind === "NPED") npedSound();
+ // else hotlistsound();
+
+ setSelectedSighting(next.sighting);
+ setSightingModalOpen(true);
+ }
+ }, [isSightingModalOpen, npedSound, hotlistsound, setSelectedSighting, setSightingModalOpen, modalQueue]);
const handleClose = () => {
setSightingModalOpen(false);
+ setModalQueue((q) => q.slice(1));
};
return (
<>
diff --git a/src/components/UI/Header.tsx b/src/components/UI/Header.tsx
index b48c938..c093949 100644
--- a/src/components/UI/Header.tsx
+++ b/src/components/UI/Header.tsx
@@ -1,21 +1,18 @@
import { Link } from "react-router";
import Logo from "/MAV.svg";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import {
- faGear,
- faHome,
- faListCheck,
- faMaximize,
- faMinimize,
- faRotate,
-} from "@fortawesome/free-solid-svg-icons";
+import { faGear, faHome, faListCheck, faMaximize, faMinimize, faRotate } from "@fortawesome/free-solid-svg-icons";
import { useState } from "react";
import SoundBtn from "./SoundBtn";
-import { useNPEDContext } from "../../context/NPEDUserContext";
+import { useIntegrationsContext } from "../../context/IntegrationsContext";
export default function Header() {
const [isFullscreen, setIsFullscreen] = useState(false);
- const { sessionStarted } = useNPEDContext();
+ const { state } = useIntegrationsContext();
+
+ const sessionStarted = state.sessionStarted;
+
+ const sessionPaused = state.sessionPaused;
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
@@ -39,9 +36,13 @@ export default function Header() {
- {sessionStarted && (
-
Session Active
- )}
+
+ {sessionStarted && sessionPaused ? (
+
Session Paused
+ ) : (
+ sessionStarted &&
Session Active
+ )}
+
@@ -59,11 +60,7 @@ export default function Header() {
-
+
diff --git a/src/components/UI/VehicleSessionItem.tsx b/src/components/UI/VehicleSessionItem.tsx
new file mode 100644
index 0000000..a0e8ea2
--- /dev/null
+++ b/src/components/UI/VehicleSessionItem.tsx
@@ -0,0 +1,18 @@
+import clsx from "clsx";
+
+type VehicleSessionItemProps = {
+ sessionNumber: number;
+ textColour: string;
+ vehicleTag: string;
+};
+
+const VehicleSessionItem = ({ sessionNumber, textColour, vehicleTag }: VehicleSessionItemProps) => {
+ return (
+
+ {vehicleTag}
+ {sessionNumber}
+
+ );
+};
+
+export default VehicleSessionItem;
diff --git a/src/context/IntegrationsContext.ts b/src/context/IntegrationsContext.ts
new file mode 100644
index 0000000..a99c596
--- /dev/null
+++ b/src/context/IntegrationsContext.ts
@@ -0,0 +1,14 @@
+import { createContext, useContext, type ActionDispatch } from "react";
+import type { NPEDACTION, NPEDSTATE } from "../types/types";
+
+type IntegrationsValue = {
+ state: NPEDSTATE;
+ dispatch: ActionDispatch<[action: NPEDACTION]>;
+};
+
+export const IntegrationsContext = createContext
(undefined);
+export const useIntegrationsContext = () => {
+ const ctx = useContext(IntegrationsContext);
+ if (!ctx) throw new Error("useNPEDContext must be used within ");
+ return ctx;
+};
diff --git a/src/context/NPEDUserContext.ts b/src/context/NPEDUserContext.ts
deleted file mode 100644
index 7b36b9e..0000000
--- a/src/context/NPEDUserContext.ts
+++ /dev/null
@@ -1,21 +0,0 @@
-import { createContext, useContext, type SetStateAction } from "react";
-import type { NPEDUser, ReducedSightingType } from "../types/types";
-
-type UserContextValue = {
- user: NPEDUser | null;
- setUser: React.Dispatch>;
- sessionStarted: boolean;
- setSessionStarted: React.Dispatch>;
- sessionList: ReducedSightingType[];
- setSessionList: React.Dispatch>;
-};
-
-export const NPEDUserContext = createContext(
- undefined
-);
-export const useNPEDContext = () => {
- const ctx = useContext(NPEDUserContext);
- if (!ctx)
- throw new Error("useNPEDContext must be used within ");
- return ctx;
-};
diff --git a/src/context/providers/IntegrationsContextProvider.tsx b/src/context/providers/IntegrationsContextProvider.tsx
new file mode 100644
index 0000000..d12540c
--- /dev/null
+++ b/src/context/providers/IntegrationsContextProvider.tsx
@@ -0,0 +1,36 @@
+import { useEffect, useReducer, type ReactNode } from "react";
+import { IntegrationsContext } from "../IntegrationsContext";
+import { useCameraBlackboard } from "../../hooks/useCameraBlackboard";
+import { initialState, reducer } from "../reducers/IntegrationsContextReducer";
+
+type IntegrationsProviderType = {
+ children: ReactNode;
+};
+
+export const IntegrationsProvider = ({ children }: IntegrationsProviderType) => {
+ const [state, dispatch] = useReducer(reducer, initialState);
+ const { mutation } = useCameraBlackboard();
+
+ useEffect(() => {
+ const fetchData = async () => {
+ const result = await mutation.mutateAsync({
+ operation: "VIEW",
+ path: "sessionStats",
+ });
+ if (!result.result || typeof result.result === "string") return;
+
+ dispatch({ type: "UPDATE", payload: result?.result });
+ };
+ fetchData();
+ }, []);
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/context/providers/NPEDUserContextProvider.tsx b/src/context/providers/NPEDUserContextProvider.tsx
deleted file mode 100644
index e76247f..0000000
--- a/src/context/providers/NPEDUserContextProvider.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-import { useState, type ReactNode } from "react";
-import type { NPEDUser, ReducedSightingType } from "../../types/types";
-import { NPEDUserContext } from "../NPEDUserContext";
-
-type NPEDUserProviderType = {
- children: ReactNode;
-};
-
-export const NPEDUserProvider = ({ children }: NPEDUserProviderType) => {
- const [user, setUser] = useState(null);
- const [sessionStarted, setSessionStarted] = useState(false);
- const [sessionList, setSessionList] = useState([]);
-
- return (
-
- {children}
-
- );
-};
diff --git a/src/context/providers/SoundContextProvider.tsx b/src/context/providers/SoundContextProvider.tsx
index 37e75ec..f534800 100644
--- a/src/context/providers/SoundContextProvider.tsx
+++ b/src/context/providers/SoundContextProvider.tsx
@@ -21,7 +21,11 @@ const SoundContextProvider = ({ children }: SoundContextProviderProps) => {
path: "soundSettings",
});
- dispatch({ type: "UPDATE", payload: result.result });
+ if (!result.result || typeof result.result !== "object") {
+ dispatch({ type: "UPDATE", payload: state });
+ } else {
+ dispatch({ type: "UPDATE", payload: result.result });
+ }
};
fetchSound();
// eslint-disable-next-line react-hooks/exhaustive-deps
diff --git a/src/context/reducers/IntegrationsContextReducer.ts b/src/context/reducers/IntegrationsContextReducer.ts
new file mode 100644
index 0000000..f740f4c
--- /dev/null
+++ b/src/context/reducers/IntegrationsContextReducer.ts
@@ -0,0 +1,46 @@
+import type { NPEDACTION, NPEDSTATE } from "../../types/types";
+
+export const initialState = {
+ sessionStarted: false,
+ sessionList: [],
+ sessionPaused: false,
+ savedSightings: [],
+ npedUser: null,
+};
+
+export function reducer(state: NPEDSTATE, action: NPEDACTION) {
+ switch (action.type) {
+ case "SESSIONSTART":
+ return {
+ ...state,
+ sessionStarted: action.payload,
+ };
+ case "LOGIN":
+ return {
+ ...state,
+ npedUser: action.payload,
+ };
+ case "LOGOUT":
+ return {
+ ...state,
+ npedUser: action.payload,
+ };
+ case "SESSIONPAUSE":
+ return {
+ ...state,
+ sessionPaused: action.payload,
+ };
+ case "ADD":
+ return {
+ ...state,
+ sessionList: [...state.sessionList, action.payload],
+ };
+ case "UPDATE":
+ return {
+ ...state,
+ sessionList: action.payload,
+ };
+ default:
+ return { ...state };
+ }
+}
diff --git a/src/context/reducers/SoundContextReducer.ts b/src/context/reducers/SoundContextReducer.ts
index 86cac6e..0145094 100644
--- a/src/context/reducers/SoundContextReducer.ts
+++ b/src/context/reducers/SoundContextReducer.ts
@@ -34,6 +34,7 @@ export function reducer(state: SoundState, action: SoundAction): SoundState {
NPEDsoundVolume: action.payload.NPEDsoundVolume,
sightingVolume: action.payload.sightingVolume,
hotlistSoundVolume: action.payload.hotlistSoundVolume,
+ soundOptions: action.payload.soundOptions,
};
}
diff --git a/src/hooks/useNPEDAuth.ts b/src/hooks/useNPEDAuth.ts
index 619f4e5..31c7ea7 100644
--- a/src/hooks/useNPEDAuth.ts
+++ b/src/hooks/useNPEDAuth.ts
@@ -1,6 +1,6 @@
import { useMutation, useQuery } from "@tanstack/react-query";
import type { NPEDFieldType } from "../types/types";
-import { useNPEDContext } from "../context/NPEDUserContext";
+import { useIntegrationsContext } from "../context/IntegrationsContext";
import { useEffect } from "react";
import { CAM_BASE } from "../utils/config";
import { toast } from "sonner";
@@ -42,8 +42,7 @@ async function signIn(loginDetails: NPEDFieldType) {
}),
]);
- if (!frontRes.ok || !rearRes.ok)
- throw new Error("Cannot reach NPED endpoint");
+ if (!frontRes.ok || !rearRes.ok) throw new Error("Cannot reach NPED endpoint");
return {
frontResponse: frontRes.json(),
@@ -73,7 +72,7 @@ async function signOut() {
}
export const useNPEDAuth = () => {
- const { setUser, user } = useNPEDContext();
+ const { dispatch } = useIntegrationsContext();
const signInMutation = useMutation({
mutationKey: ["NPEDSignin"],
@@ -84,7 +83,8 @@ export const useNPEDAuth = () => {
onSuccess: async (data) => {
toast.dismiss();
toast.success("Signed in successfully!");
- setUser(await data.frontResponse);
+
+ dispatch({ type: "LOGIN", payload: await data.frontResponse });
},
onError: (error) => {
toast.dismiss();
@@ -101,7 +101,7 @@ export const useNPEDAuth = () => {
mutationFn: signOut,
onSuccess: () => {
toast.success("Signed out successfully");
- setUser(null);
+ dispatch({ type: "LOGOUT", payload: null });
},
onError: (error) => {
toast.error(`Sign-out failed: ${error.message}`);
@@ -115,11 +115,11 @@ export const useNPEDAuth = () => {
useEffect(() => {
if (fetchdataQuery.isSuccess && fetchdataQuery.data) {
- setUser(fetchdataQuery.data);
+ dispatch({ type: "LOGIN", payload: fetchdataQuery.data });
} else {
- setUser(null);
+ dispatch({ type: "LOGOUT", payload: null });
}
- }, [fetchdataQuery.data, fetchdataQuery.isSuccess, setUser]);
+ }, [dispatch, fetchdataQuery.data, fetchdataQuery.isSuccess]);
useEffect(() => {
if (fetchdataQuery.isError) toast.error(fetchdataQuery.error.message);
@@ -134,8 +134,6 @@ export const useNPEDAuth = () => {
data: signInMutation.data,
fetchdataQueryError: fetchdataQuery.error,
fetchdataQueryLoading: fetchdataQuery.isLoading,
- user,
- setUser,
signOut: signOutMutation.mutate,
};
};
diff --git a/src/hooks/useSightingFeed.ts b/src/hooks/useSightingFeed.ts
index d9a386d..a3d19a8 100644
--- a/src/hooks/useSightingFeed.ts
+++ b/src/hooks/useSightingFeed.ts
@@ -1,10 +1,13 @@
import { useEffect, useMemo, useRef, useState } from "react";
+import { useDebouncedCallback } from "use-debounce";
import { Query, useQuery } from "@tanstack/react-query";
import type { SightingType } from "../types/types";
-import { useSoundOnChange } from "react-sounds";
+import { useSound } from "react-sounds";
import { useSoundContext } from "../context/SoundContext";
-import { getSoundFileURL } from "../utils/utils";
+import { checkIsHotListHit, getNPEDCategory, getSoundFileURL } from "../utils/utils";
import switchSound from "../assets/sounds/ui/switch.mp3";
+import notification from "../assets/sounds/ui/notification.mp3";
+import popup from "../assets/sounds/ui/popup_open.mp3";
async function fetchSighting(url: string | undefined, ref: number): Promise {
const res = await fetch(`${url}${ref}`, {
@@ -21,30 +24,38 @@ export function useSightingFeed(url: string | undefined) {
const [sessionStarted, setSessionStarted] = useState(false);
const [selectedSighting, setSelectedSighting] = useState(null);
- const mostRecent = sightings[0] ?? null;
- const latestRef = mostRecent?.ref ?? null;
-
- const first = useRef(true);
- const lastSoundAt = useRef(0);
- const COOLDOWN_MS = 1500;
- const currentRef = useRef(-1);
- const lastValidTimestamp = useRef(Date.now());
-
- const trigger = useMemo(() => {
- if (latestRef == null || !audioArmed) return null;
- if (first.current) {
- first.current = false;
- return Symbol("skip");
+ const soundSrcHotlist = useMemo(() => {
+ if (state?.hotlistSound?.includes(".mp3") || state.hotlistSound?.includes(".wav")) {
+ const file = state.soundOptions?.find((item) => item.name === state.hotlistSound);
+ return file?.soundUrl ?? notification;
}
- const now = Date.now();
- if (now - lastSoundAt.current < COOLDOWN_MS) return Symbol("skip");
- lastSoundAt.current = now;
- return latestRef;
- }, [audioArmed, latestRef]);
+ return getSoundFileURL(state?.hotlistSound) ?? notification;
+ }, [state.hotlistSound, state.soundOptions]);
+
+ const soundSrcNped = useMemo(() => {
+ if (state?.NPEDsound?.includes(".mp3") || state.NPEDsound?.includes(".wav")) {
+ const file = state.soundOptions?.find((item) => item.name === state.NPEDsound);
+ return file?.soundUrl ?? popup;
+ }
+ return getSoundFileURL(state.NPEDsound) ?? popup;
+ }, [state.NPEDsound, state.soundOptions]);
const soundSrc = useMemo(() => {
+ if (state?.sightingSound?.includes(".mp3") || state.sightingSound?.includes(".wav")) {
+ const file = state.soundOptions?.find((item) => item.name === state.sightingSound);
+ return file?.soundUrl ?? switchSound;
+ }
return getSoundFileURL(state?.sightingSound) ?? switchSound;
- }, [state.sightingSound]);
+ }, [state.sightingSound, state.soundOptions]);
+
+ const { play: hotlistsound } = useSound(soundSrcHotlist, { volume: state.hotlistSoundVolume });
+ const { play: npedSound } = useSound(soundSrcNped, { volume: state.NPEDsoundVolume });
+ const { play: sightingSound } = useSound(soundSrc, { volume: state.sightingVolume });
+
+ const mostRecent = sightings[0] ?? null;
+
+ const currentRef = useRef(-1);
+ const lastValidTimestamp = useRef(Date.now());
function refetchInterval(query: Query) {
if (!query) return;
@@ -74,16 +85,36 @@ export function useSightingFeed(url: string | undefined) {
staleTime: 0,
});
- //use latestref instead of trigger to revert back
+ const playHotlistsound = useDebouncedCallback(() => {
+ hotlistsound();
+ }, 500);
- useSoundOnChange(soundSrc, trigger, {
- volume: state.sightingVolume,
- initial: false,
- });
+ const playNPEDHitSound = useDebouncedCallback(() => {
+ npedSound();
+ }, 500);
+
+ const playSightingHitSound = useDebouncedCallback(() => {
+ sightingSound();
+ }, 500);
useEffect(() => {
const data = query.data;
+
if (!data || data.ref === -1) return;
+ const isHotListHit = checkIsHotListHit(data);
+ const cat = getNPEDCategory(data);
+
+ const isNPEDHitA = cat === "A";
+ const isNPEDHitB = cat === "B";
+ const isNPEDHitC = cat === "C";
+
+ if ((isNPEDHitA && audioArmed) || (isNPEDHitB && audioArmed) || (isNPEDHitC && audioArmed)) {
+ playNPEDHitSound();
+ } else if (isHotListHit && audioArmed) {
+ playHotlistsound();
+ } else if (audioArmed) {
+ playSightingHitSound();
+ }
const now = Date.now();
diff --git a/src/types/types.ts b/src/types/types.ts
index 37afae9..45d8c79 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -286,12 +286,14 @@ export type FormValues = {
NPEDsound: SoundValue;
hotlists: Hotlist[];
hotlistSound: SoundValue;
+ soundOptions?: SoundUploadValue[];
};
export type SoundUploadValue = {
name: string;
soundFileName?: string;
soundFile?: File | null;
+ soundUrl?: string;
};
export type SoundState = {
@@ -315,6 +317,7 @@ type UpdateAction = {
NPEDsoundVolume: number;
hotlistSoundVolume: number;
hotlistSound: SoundValue;
+ soundOptions?: SoundUploadValue[];
};
};
@@ -368,3 +371,26 @@ export type ModemSettingsType = {
password: string;
authenticationType: string;
};
+
+export type HitKind = "NPED" | "HOTLIST";
+
+export type QueuedHit = {
+ id: number | string;
+ sighting: SightingType;
+ kind: HitKind;
+};
+
+export type DedupedSightings = ReducedSightingType[];
+
+export type NPEDSTATE = {
+ sessionStarted: boolean;
+ sessionList: ReducedSightingType[];
+ sessionPaused: boolean;
+ savedSightings: DedupedSightings;
+ npedUser: NPEDUser;
+};
+
+export type NPEDACTION = {
+ type: string;
+ payload: any;
+};
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 78c7b30..4f869e5 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -5,7 +5,7 @@ import beep from "../assets/sounds/ui/Beep.wav";
import warning from "../assets/sounds/ui/Warning.wav";
import ding from "../assets/sounds/ui/Ding.wav";
import shutter from "../assets/sounds/ui/shutter.mp3";
-
+import attention from "../assets/sounds/ui/Attention.wav";
import type { HotlistMatches, SightingType } from "../types/types";
export function getSoundFileURL(name: string) {
@@ -17,10 +17,15 @@ export function getSoundFileURL(name: string) {
warning: warning,
ding: ding,
shutter: shutter,
+ attention: attention,
};
return sounds[name] ?? null;
}
+export const showSoundURL = (url: URL | string | undefined) => {
+ console.log(url);
+};
+
const randomChars = () => {
const uppercaseAsciiStart = 65;
const letterIndex = Math.floor(Math.random() * 26);
@@ -143,11 +148,13 @@ export const checkIsHotListHit = (sigthing: SightingType | null) => {
};
export function getHotlistName(obj: HotlistMatches | undefined) {
- if (!obj || Object.values(obj).includes(false)) return;
+ if (!obj) return;
- const keys = Object.keys(obj);
- return keys;
+ const hotlistNames = Object.entries(obj)
+ .filter(([, value]) => value === true)
+ .map(([key]) => key);
+ return hotlistNames;
}
export const getNPEDCategory = (r?: SightingType | null) =>
- r?.metadata?.npedJSON?.["NPED CATEGORY"] as "A" | "B" | "C" | undefined;
+ r?.metadata?.npedJSON?.["NPED CATEGORY"] as "A" | "B" | "C" | "D" | undefined;
diff --git a/yarn.lock b/yarn.lock
index db3053e..42dbb1c 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2267,6 +2267,11 @@ uri-js@^4.2.2:
dependencies:
punycode "^2.1.0"
+use-debounce@^10.0.6:
+ version "10.0.6"
+ resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.6.tgz#e05060a5e561432ec740c653698f3eb162bd28ec"
+ integrity sha512-C5OtPyhAZgVoteO9heXMTdW7v/IbFI+8bSVKYCJrSmiWWCLsbUxiBSp4t9v0hNBTGY97bT72ydDIDyGSFWfwXg==
+
vite@^7.1.0:
version "7.1.2"
resolved "https://registry.npmjs.org/vite/-/vite-7.1.2.tgz"