+ {hotlistNames.map((hotlistName, index) => (
+
{hotlistName ? hotlistName?.replace(/\.csv$/i, "") : "-"}
diff --git a/src/components/SightingsWidget/SightingWidget.tsx b/src/components/SightingsWidget/SightingWidget.tsx
index a520c63..3f59679 100644
--- a/src/components/SightingsWidget/SightingWidget.tsx
+++ b/src/components/SightingsWidget/SightingWidget.tsx
@@ -15,7 +15,7 @@ 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 { useIntegrationsContext } from "../../context/IntegrationsContext";
import { useSoundContext } from "../../context/SoundContext";
import Loading from "../UI/Loading";
import { checkIsHotListHit, getNPEDCategory } from "../../utils/utils";
@@ -72,7 +72,9 @@ 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());
@@ -97,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) => {
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/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/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/types/types.ts b/src/types/types.ts
index 504ae90..45d8c79 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -379,3 +379,18 @@ export type QueuedHit = {
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;
+};