diff --git a/package.json b/package.json index a56f708..520161d 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "bayiq-ui", "private": true, - "version": "1.0.1", + "version": "1.0.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src/app/config/cameraConfig.ts b/src/app/config/cameraConfig.ts new file mode 100644 index 0000000..3ace17d --- /dev/null +++ b/src/app/config/cameraConfig.ts @@ -0,0 +1,12 @@ +// Camera configuration - add more cameras here as needed +export const CAMERA_IDS = ["A", "B", "C"] as const; + +export type CameraID = (typeof CAMERA_IDS)[number]; + +export const DEFAULT_REGIONS = [ + { name: "Bay 1", brushColour: "#ff0000" }, + { name: "Bay 2", brushColour: "#00ff00" }, + { name: "Bay 3", brushColour: "#0400ff" }, + { name: "Bay 4", brushColour: "#ffff00" }, + { name: "Bay 5", brushColour: "#fc35db" }, +]; diff --git a/src/app/context/WebSocketContext.ts b/src/app/context/WebSocketContext.ts index 551e006..a73516b 100644 --- a/src/app/context/WebSocketContext.ts +++ b/src/app/context/WebSocketContext.ts @@ -17,9 +17,7 @@ type CameraSocketState = { export type WebSocketConextValue = { info: InfoSocketState; - cameraFeedA: CameraSocketState; - cameraFeedB: CameraSocketState; - cameraFeedC: CameraSocketState; + cameraFeed: CameraSocketState; }; export const WebsocketContext = createContext(null); @@ -31,6 +29,4 @@ const useWebSocketContext = () => { }; export const useInfoSocket = () => useWebSocketContext().info; -export const useCameraFeedASocket = () => useWebSocketContext().cameraFeedA; -export const useCameraFeedBSocket = () => useWebSocketContext().cameraFeedB; -export const useCameraFeedCSocket = () => useWebSocketContext().cameraFeedC; +export const useCameraFeedSocket = () => useWebSocketContext().cameraFeed; diff --git a/src/app/providers/CameraFeedProvider.tsx b/src/app/providers/CameraFeedProvider.tsx index 5c0b39e..16eee91 100644 --- a/src/app/providers/CameraFeedProvider.tsx +++ b/src/app/providers/CameraFeedProvider.tsx @@ -3,13 +3,12 @@ import { CameraFeedContext } from "../context/CameraFeedContext"; import { initialState, reducer } from "../reducers/cameraFeedReducer"; import { useBlackBoard } from "../../hooks/useBlackBoard"; import type { CameraFeedState } from "../../types/types"; -import { useCameraZoom } from "../../features/cameras/hooks/useCameraZoom"; + +import { CAMERA_IDS } from "../config/cameraConfig"; +import CameraZoomFetcher from "./CameraZoomFetcher"; export const CameraFeedProvider = ({ children }: { children: ReactNode }) => { const { blackboardMutation } = useBlackBoard(); - const { cameraZoomQuery: cameraZoomQueryA } = useCameraZoom("A"); - const { cameraZoomQuery: cameraZoomQueryB } = useCameraZoom("B"); - const { cameraZoomQuery: cameraZoomQueryC } = useCameraZoom("C"); const [state, dispatch] = useReducer(reducer, initialState); @@ -23,52 +22,25 @@ export const CameraFeedProvider = ({ children }: { children: ReactNode }) => { const cameraFeedData: CameraFeedState = result.result; const recontructedState = { ...cameraFeedData, - paintedCells: { - A: new Map(cameraFeedData.paintedCells.A), - B: new Map(cameraFeedData.paintedCells.B), - C: new Map(cameraFeedData.paintedCells.C), - }, + paintedCells: CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = new Map(cameraFeedData.paintedCells[id]); + return acc; + }, + {} as typeof cameraFeedData.paintedCells, + ), }; dispatch({ type: "SET_CAMERA_FEED_DATA", cameraState: recontructedState }); }; fetchBlackBoardData(); }, []); - useEffect(() => { - const fetchZoomLevels = async () => { - const [resultA, resultB, resultC] = await Promise.all([ - cameraZoomQueryA.refetch(), - cameraZoomQueryB.refetch(), - cameraZoomQueryC.refetch(), - ]); - - const zoomLevelAnumber = parseFloat(resultA.data?.propPhysCurrent?.value); - const zoomLevelBnumber = parseFloat(resultB.data?.propPhysCurrent?.value); - const zoomLevelCnumber = parseFloat(resultC.data?.propPhysCurrent?.value); - - if (resultA.data) { - dispatch({ - type: "SET_ZOOM_LEVEL", - payload: { cameraFeedID: "A", zoomLevel: zoomLevelAnumber }, - }); - } - - if (resultB.data) { - dispatch({ - type: "SET_ZOOM_LEVEL", - payload: { cameraFeedID: "B", zoomLevel: zoomLevelBnumber }, - }); - } - - if (resultC.data) { - dispatch({ - type: "SET_ZOOM_LEVEL", - payload: { cameraFeedID: "C", zoomLevel: zoomLevelCnumber }, - }); - } - }; - fetchZoomLevels(); - }, []); - - return {children}; + return ( + + {CAMERA_IDS.map((cameraId) => ( + + ))} + {children} + + ); }; diff --git a/src/app/providers/CameraZoomFetcher.tsx b/src/app/providers/CameraZoomFetcher.tsx new file mode 100644 index 0000000..e0c2157 --- /dev/null +++ b/src/app/providers/CameraZoomFetcher.tsx @@ -0,0 +1,31 @@ +import { useEffect } from "react"; +import { useCameraZoom } from "../../features/cameras/hooks/useCameraZoom"; +import type { CameraFeedAction } from "../../types/types"; +import type { CameraID } from "../config/cameraConfig"; + +type CameraZoomFetcherProps = { + cameraId: CameraID; + dispatch: (action: CameraFeedAction) => void; +}; + +const CameraZoomFetcher = ({ cameraId, dispatch }: CameraZoomFetcherProps) => { + const { cameraZoomQuery } = useCameraZoom(cameraId); + + useEffect(() => { + const fetchZoomLevel = async () => { + const result = await cameraZoomQuery.refetch(); + if (result.data && typeof result.data.zoomLevel === "number") { + dispatch({ + type: "SET_ZOOM_LEVEL", + payload: { cameraFeedID: cameraId, zoomLevel: result.data.zoomLevel }, + }); + } + }; + + fetchZoomLevel(); + }, [cameraId, cameraZoomQuery, dispatch]); + + return null; +}; + +export default CameraZoomFetcher; diff --git a/src/app/providers/WebSocketProvider.tsx b/src/app/providers/WebSocketProvider.tsx index 66ce180..b2aa0c1 100644 --- a/src/app/providers/WebSocketProvider.tsx +++ b/src/app/providers/WebSocketProvider.tsx @@ -3,18 +3,28 @@ import { WebsocketContext, type WebSocketConextValue } from "../context/WebSocke import useWebSocket from "react-use-websocket"; import { wsConfig } from "../config/wsconfig"; import type { CameraZoomData, InfoBarData } from "../../types/types"; +import { CAMERA_IDS } from "../config/cameraConfig"; +import { CAMBASE_WS } from "../../utils/config"; +import { useCameraFeedContext } from "../context/CameraFeedContext"; type WebSocketProviderProps = { children: ReactNode; }; export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { + const { state } = useCameraFeedContext(); const [systemData, setSystemData] = useState(null); const [socketData, setSocketData] = useState(null); const infoSocket = useWebSocket(wsConfig.infoBar, { share: true, shouldReconnect: () => true }); - const cameraFeedASocket = useWebSocket(wsConfig.cameraFeedA, { share: true, shouldReconnect: () => true }); - const cameraFeedBSocket = useWebSocket(wsConfig.cameraFeedB, { share: true, shouldReconnect: () => true }); - const cameraFeedCSocket = useWebSocket(wsConfig.cameraFeedC, { share: true, shouldReconnect: () => true }); + const sockets = CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = `${CAMBASE_WS}/websocket-Camera${id}-live-video`; + return acc; + }, + {} as Record, + ); + const cameraFeedID = state.cameraFeedID; + const cameraFeed = useWebSocket(sockets[cameraFeedID], { share: true, shouldReconnect: () => true }); useEffect(() => { async function parseData() { @@ -23,20 +33,15 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { const data = JSON.parse(text); setSystemData(data); } - if (cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage) { - const message = cameraFeedASocket.lastMessage || cameraFeedBSocket.lastMessage || cameraFeedCSocket.lastMessage; + if (cameraFeed.lastMessage) { + const message = cameraFeed.lastMessage; const data = await message?.data.text(); const parsedData: CameraZoomData = JSON.parse(data || ""); setSocketData(parsedData); } } parseData(); - }, [ - cameraFeedASocket.lastMessage, - cameraFeedBSocket.lastMessage, - cameraFeedCSocket.lastMessage, - infoSocket.lastMessage, - ]); + }, [cameraFeed.lastMessage, infoSocket.lastMessage]); const value = useMemo( () => ({ @@ -45,32 +50,16 @@ export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { readyState: infoSocket.readyState, sendJson: infoSocket.sendJsonMessage, }, - cameraFeedA: { + cameraFeed: { data: socketData, - readyState: cameraFeedASocket.readyState, + readyState: cameraFeed.readyState, - send: cameraFeedASocket.sendMessage, - }, - cameraFeedB: { - data: socketData, - readyState: cameraFeedBSocket.readyState, - - send: cameraFeedBSocket.sendMessage, - }, - cameraFeedC: { - data: socketData, - readyState: cameraFeedCSocket.readyState, - - send: cameraFeedCSocket.sendMessage, + send: cameraFeed.sendMessage, }, }), [ - cameraFeedASocket.readyState, - cameraFeedASocket.sendMessage, - cameraFeedBSocket.readyState, - cameraFeedBSocket.sendMessage, - cameraFeedCSocket.readyState, - cameraFeedCSocket.sendMessage, + cameraFeed.readyState, + cameraFeed.sendMessage, infoSocket.readyState, infoSocket.sendJsonMessage, socketData, diff --git a/src/app/reducers/cameraFeedReducer.ts b/src/app/reducers/cameraFeedReducer.ts index 898435b..9903f51 100644 --- a/src/app/reducers/cameraFeedReducer.ts +++ b/src/app/reducers/cameraFeedReducer.ts @@ -1,47 +1,38 @@ import type { CameraFeedAction, CameraFeedState, PaintedCell } from "../../types/types"; +import { CAMERA_IDS, DEFAULT_REGIONS, type CameraID } from "../config/cameraConfig"; export const initialState: CameraFeedState = { - cameraFeedID: "A", - paintedCells: { - A: new Map(), - B: new Map(), - C: new Map(), - }, - regionsByCamera: { - A: [ - { name: "Bay 1", brushColour: "#ff0000" }, - { name: "Bay 2", brushColour: "#00ff00" }, - { name: "Bay 3", brushColour: "#0400ff" }, - { name: "Bay 4", brushColour: "#ffff00" }, - { name: "Bay 5", brushColour: "#fc35db" }, - ], - B: [ - { name: "Bay 1", brushColour: "#ff0000" }, - { name: "Bay 2", brushColour: "#00ff00" }, - { name: "Bay 3", brushColour: "#0400ff" }, - { name: "Bay 4", brushColour: "#ffff00" }, - { name: "Bay 5", brushColour: "#fc35db" }, - ], - C: [ - { name: "Bay 1", brushColour: "#ff0000" }, - { name: "Bay 2", brushColour: "#00ff00" }, - { name: "Bay 3", brushColour: "#0400ff" }, - { name: "Bay 4", brushColour: "#ffff00" }, - { name: "Bay 5", brushColour: "#fc35db" }, - ], - }, + cameraFeedID: CAMERA_IDS[0], + paintedCells: CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = new Map(); + return acc; + }, + {} as Record>, + ), + regionsByCamera: CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = DEFAULT_REGIONS; + return acc; + }, + {} as Record, + ), selectedRegionIndex: 0, - modeByCamera: { - A: "painter", - B: "painter", - C: "painter", - }, - zoomLevel: { - A: 1, - B: 1, - C: 1, - }, + modeByCamera: CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = "painter"; + return acc; + }, + {} as Record, + ), + zoomLevel: CAMERA_IDS.reduce( + (acc, id) => { + acc[id] = 1; + return acc; + }, + {} as Record, + ), }; export function reducer(state: CameraFeedState, action: CameraFeedAction) { diff --git a/src/features/cameras/components/CameraSettings/CameraPanel.tsx b/src/features/cameras/components/CameraSettings/CameraPanel.tsx index d1899bc..7a4f779 100644 --- a/src/features/cameras/components/CameraSettings/CameraPanel.tsx +++ b/src/features/cameras/components/CameraSettings/CameraPanel.tsx @@ -28,6 +28,7 @@ const CameraPanel = ({ tabIndex, isResetAllModalOpen, handleClose, setIsResetMod return "B"; case 2: return "C"; + //Add more cases if more cameras are added default: return "A"; } diff --git a/src/features/cameras/components/CameraSettings/CameraSettings.tsx b/src/features/cameras/components/CameraSettings/CameraSettings.tsx index 55242ba..5ff3bd9 100644 --- a/src/features/cameras/components/CameraSettings/CameraSettings.tsx +++ b/src/features/cameras/components/CameraSettings/CameraSettings.tsx @@ -2,6 +2,7 @@ import Card from "../../../../ui/Card"; import { Tab, Tabs, TabList, TabPanel } from "react-tabs"; import "react-tabs/style/react-tabs.css"; import CameraPanel from "./CameraPanel"; +import { CAMERA_IDS } from "../../../../app/config/cameraConfig"; type CameraSettingsProps = { setTabIndex: (tabIndex: number) => void; @@ -12,7 +13,6 @@ type CameraSettingsProps = { }; const CameraSettings = ({ - tabIndex, setTabIndex, isResetAllModalOpen, handleClose, @@ -26,34 +26,20 @@ const CameraSettings = ({ onSelect={(index) => setTabIndex(index)} > - Camera A - Camera B - Camera C + {CAMERA_IDS.map((id) => ( + Camera {id} + ))} - - - - - - - - - + {CAMERA_IDS.map((id, index) => ( + + + + ))} ); diff --git a/src/features/cameras/components/CameraSettings/RegionSelector.tsx b/src/features/cameras/components/CameraSettings/RegionSelector.tsx index cdb6656..99058fc 100644 --- a/src/features/cameras/components/CameraSettings/RegionSelector.tsx +++ b/src/features/cameras/components/CameraSettings/RegionSelector.tsx @@ -4,17 +4,14 @@ import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext" import { useColourDectection } from "../../hooks/useColourDetection"; import { useBlackBoard } from "../../../../hooks/useBlackBoard"; import { toast } from "sonner"; -import { - useCameraFeedASocket, - useCameraFeedBSocket, - useCameraFeedCSocket, -} from "../../../../app/context/WebSocketContext"; +import { useCameraFeedSocket } from "../../../../app/context/WebSocketContext"; +import type { CameraID } from "../../../../app/config/cameraConfig"; type RegionSelectorProps = { regions: Region[]; selectedRegionIndex: number; mode: string; - cameraFeedID: "A" | "B" | "C"; + cameraFeedID: CameraID; isResetAllModalOpen: boolean; handleClose: () => void; setIsResetModalOpen: React.Dispatch>; @@ -33,27 +30,24 @@ const RegionSelector = ({ const { state, dispatch } = useCameraFeedContext(); const { blackboardMutation } = useBlackBoard(); const paintedCells = state.paintedCells[cameraFeedID]; - const cameraASocket = useCameraFeedASocket(); - const cameraBSocket = useCameraFeedBSocket(); - const cameraCSocket = useCameraFeedCSocket(); + const cameraSocket = useCameraFeedSocket(); const getCurrentSocket = () => { switch (cameraFeedID) { case "A": - return cameraASocket; + return cameraSocket; case "B": - return cameraBSocket; + return cameraSocket; case "C": - return cameraCSocket; + return cameraSocket; } }; const socket = getCurrentSocket(); const getMagnificationLevel = () => { - const test = socket.data; - if (!socket.data) return null; - + const test = socket?.data; + if (!socket?.data) return null; if (!test || !test.magnificationLevel) return "1x"; return test?.magnificationLevel; }; @@ -108,12 +102,12 @@ const RegionSelector = ({ const handleSaveclick = () => { const regions: ColourData[] = []; - const test = Array.from(paintedCells.entries()); - const region1 = test.filter(([, cell]) => cell.region.name === "Bay 1"); - const region2 = test.filter(([, cell]) => cell.region.name === "Bay 2"); - const region3 = test.filter(([, cell]) => cell.region.name === "Bay 3"); - const region4 = test.filter(([, cell]) => cell.region.name === "Bay 4"); - const region5 = test.filter(([, cell]) => cell.region.name === "Bay 5"); + const paintedCellsArray = Array.from(paintedCells.entries()); + const region1 = paintedCellsArray.filter(([, cell]) => cell.region.name === "Bay 1"); + const region2 = paintedCellsArray.filter(([, cell]) => cell.region.name === "Bay 2"); + const region3 = paintedCellsArray.filter(([, cell]) => cell.region.name === "Bay 3"); + const region4 = paintedCellsArray.filter(([, cell]) => cell.region.name === "Bay 4"); + const region5 = paintedCellsArray.filter(([, cell]) => cell.region.name === "Bay 5"); const region1Data = { id: 1, cells: region1.map(([key]) => [parseInt(key.split("-")[1]), parseInt(key.split("-")[0])]), diff --git a/src/features/cameras/components/CameraSettings/cameraControls/CameraControls.tsx b/src/features/cameras/components/CameraSettings/cameraControls/CameraControls.tsx index dcfb408..0e4187d 100644 --- a/src/features/cameras/components/CameraSettings/cameraControls/CameraControls.tsx +++ b/src/features/cameras/components/CameraSettings/cameraControls/CameraControls.tsx @@ -1,10 +1,11 @@ +import type { CameraID } from "../../../../../app/config/cameraConfig"; import { useCameraFeedContext } from "../../../../../app/context/CameraFeedContext"; import SliderComponent from "../../../../../ui/SliderComponent"; import { useCameraZoom } from "../../../hooks/useCameraZoom"; import { useDebouncedCallback } from "use-debounce"; type CameraControlsProps = { - cameraFeedID: "A" | "B" | "C"; + cameraFeedID: CameraID; }; const CameraControls = ({ cameraFeedID }: CameraControlsProps) => { diff --git a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx index 8d65a8e..7386495 100644 --- a/src/features/cameras/components/Video/VideoFeedGridPainter.tsx +++ b/src/features/cameras/components/Video/VideoFeedGridPainter.tsx @@ -3,13 +3,10 @@ import { Stage, Layer, Image, Shape } from "react-konva"; import type { KonvaEventObject } from "konva/lib/Node"; import { useCreateVideoSnapshot } from "../../hooks/useGetvideoSnapshots"; import { useCameraFeedContext } from "../../../../app/context/CameraFeedContext"; -import { - useCameraFeedASocket, - useCameraFeedBSocket, - useCameraFeedCSocket, -} from "../../../../app/context/WebSocketContext"; +import { useCameraFeedSocket } from "../../../../app/context/WebSocketContext"; import { ReadyState } from "react-use-websocket"; import { toast } from "sonner"; +import type { CameraID } from "../../../../app/config/cameraConfig"; const BACKEND_WIDTH = 640; const BACKEND_HEIGHT = 360; @@ -41,22 +38,20 @@ const VideoFeedGridPainter = () => { const currentScale = stageSize.width / BACKEND_WIDTH; const size = BACKEND_CELL_SIZE * currentScale; - const cameraASocket = useCameraFeedASocket(); - const cameraBSocket = useCameraFeedBSocket(); - const cameraCSocket = useCameraFeedCSocket(); + const cameraSocket = useCameraFeedSocket(); const getCurrentSocket = () => { switch (cameraFeedID) { case "A": - return cameraASocket; + return cameraSocket; case "B": - return cameraBSocket; + return cameraSocket; case "C": - return cameraCSocket; + return cameraSocket; } }; - const handleZoomClick = (e: KonvaEventObject, cameraFeedID: "A" | "B" | "C") => { + const handleZoomClick = (e: KonvaEventObject, cameraFeedID: CameraID) => { if (mode !== "zoom") return; const socket = getCurrentSocket(); const stage = e.target.getStage(); diff --git a/src/features/cameras/hooks/useCameraZoom.ts b/src/features/cameras/hooks/useCameraZoom.ts index 9235f8e..56a166c 100644 --- a/src/features/cameras/hooks/useCameraZoom.ts +++ b/src/features/cameras/hooks/useCameraZoom.ts @@ -1,6 +1,7 @@ import { useQuery, useMutation } from "@tanstack/react-query"; import { CAMBASE } from "../../../utils/config"; import type { CameraZoomConfig } from "../../../types/types"; +import type { CameraID } from "../../../app/config/cameraConfig"; const fetchZoomLevel = async (cameraFeedID: string) => { const response = await fetch(`${CAMBASE}/api/fetch-config?id=Camera${cameraFeedID}-onvif-controller`); @@ -22,7 +23,6 @@ const postZoomLevel = async (zoomConfig: CameraZoomConfig) => { id: `Camera${zoomConfig.cameraFeedID}-onvif-controller`, fields, }; - console.log(zoomPayload); const response = await fetch(`${CAMBASE}/api/update-config`, { method: "POST", body: JSON.stringify(zoomPayload), @@ -34,7 +34,7 @@ const postZoomLevel = async (zoomConfig: CameraZoomConfig) => { return response.json(); }; -export const useCameraZoom = (cameraFeedID: "A" | "B" | "C") => { +export const useCameraZoom = (cameraFeedID: CameraID) => { const cameraZoomQuery = useQuery({ queryKey: ["cameraZoom", cameraFeedID], queryFn: () => fetchZoomLevel(cameraFeedID), diff --git a/src/features/cameras/hooks/useGetVideoFeed.ts b/src/features/cameras/hooks/useGetVideoFeed.ts index cbee4ac..d228e26 100644 --- a/src/features/cameras/hooks/useGetVideoFeed.ts +++ b/src/features/cameras/hooks/useGetVideoFeed.ts @@ -1,7 +1,8 @@ import { useQuery } from "@tanstack/react-query"; import { CAMBASE } from "../../../utils/config"; +import type { CameraID } from "../../../app/config/cameraConfig"; -const targetDectionFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { +const targetDectionFeed = async (cameraFeedID: CameraID | null) => { const response = await fetch(`${CAMBASE}/TargetDetectionColour${cameraFeedID}-preview`, { signal: AbortSignal.timeout(300000), cache: "no-store", @@ -12,7 +13,7 @@ const targetDectionFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { return response.blob(); }; -const getVideoFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { +const getVideoFeed = async (cameraFeedID: CameraID | null) => { const response = await fetch(`${CAMBASE}/Camera${cameraFeedID}-preview`, { signal: AbortSignal.timeout(300000), cache: "no-store", @@ -23,7 +24,7 @@ const getVideoFeed = async (cameraFeedID: "A" | "B" | "C" | null) => { return response.blob(); }; -export const useGetVideoFeed = (cameraFeedID: "A" | "B" | "C" | null, mode: string) => { +export const useGetVideoFeed = (cameraFeedID: CameraID | null, mode: string) => { const targetDetectionQuery = useQuery({ queryKey: ["getfeed", cameraFeedID], queryFn: () => targetDectionFeed(cameraFeedID), diff --git a/src/features/dashboard/components/DashboardGrid.tsx b/src/features/dashboard/components/DashboardGrid.tsx index c5c5635..e434421 100644 --- a/src/features/dashboard/components/DashboardGrid.tsx +++ b/src/features/dashboard/components/DashboardGrid.tsx @@ -3,6 +3,7 @@ import { useGetSystemHealth } from "../hooks/useGetSystemHealth"; import CameraStatus from "./cameraStatus/CameraStatus"; import SystemHealthCard from "./systemHealth/SystemHealthCard"; import SystemStatusCard from "./systemStatus/SystemStatusCard"; +import { CAMERA_IDS } from "../../../app/config/cameraConfig"; const DashboardGrid = () => { const { query } = useGetSystemHealth(); @@ -26,30 +27,35 @@ const DashboardGrid = () => { channelA: [], channelB: [], channelC: [], + // todo: check if more cameras will be added later default: [], }, ); - const categoryA = statusCategories?.channelA ?? []; - const categoryB = statusCategories?.channelB ?? []; - const categoryC = statusCategories?.channelC ?? []; - return (
- - -
- - - +
+ + +
+ +
+ {CAMERA_IDS.map((cameraID) => ( + + ))}
); diff --git a/src/features/dashboard/components/cameraStatus/CameraStatus.tsx b/src/features/dashboard/components/cameraStatus/CameraStatus.tsx index bfbdfb5..6686ba8 100644 --- a/src/features/dashboard/components/cameraStatus/CameraStatus.tsx +++ b/src/features/dashboard/components/cameraStatus/CameraStatus.tsx @@ -11,10 +11,14 @@ type CameraStatusProps = { }; const CameraStatus = ({ title, category, isError }: CameraStatusProps) => { - const isAllGood = category && category.length > 0 && category.every((status) => status.tags.includes("RUNNING")); - // check if some are down - // check if all are down - //check if offline + const isAllGood = + category && + category.length > 0 && + category.every((status) => { + const allowedTags = ["RUNNING", "VIDEO-PLAYING"]; + return status.tags.every((tag) => allowedTags.includes(tag)); + }); + return (
diff --git a/src/features/dashboard/components/cameraStatus/CameraStatusGridItem.tsx b/src/features/dashboard/components/cameraStatus/CameraStatusGridItem.tsx index a75ae3d..e401e6a 100644 --- a/src/features/dashboard/components/cameraStatus/CameraStatusGridItem.tsx +++ b/src/features/dashboard/components/cameraStatus/CameraStatusGridItem.tsx @@ -1,7 +1,8 @@ import { useState } from "react"; import type { SystemHealthStatus } from "../../../../types/types"; -import { capitalize } from "../../../../utils/utils"; + import SystemHealthModal from "../systemHealth/systemHealthModal/SystemHealthModal"; +import Badge from "../../../../ui/Badge"; type CameraStatusGridItemProps = { title: string; @@ -10,7 +11,14 @@ type CameraStatusGridItemProps = { const CameraStatusGridItem = ({ title, statusCategory }: CameraStatusGridItemProps) => { const [isOpen, setIsOpen] = useState(false); - const isAllGood = statusCategory?.every((status) => status.tags.includes("RUNNING")); + const isAllGood = statusCategory?.every((status) => { + const allowedTags = ["RUNNING", "VIDEO-PLAYING"]; + return status.tags.every((tag) => allowedTags.includes(tag)); + }); + + const downItems = statusCategory?.filter((status) => { + return status.tags.some((tag) => tag !== "RUNNING" && tag !== "VIDEO-PLAYING"); + }); const handleClick = () => { setIsOpen(false); @@ -21,8 +29,21 @@ const CameraStatusGridItem = ({ title, statusCategory }: CameraStatusGridItemPro className="flex flex-col border border-gray-600 p-4 rounded-lg mr-4 hover:bg-[#233241] hover:cursor-pointer m-2 h-70" onClick={() => setIsOpen(true)} > -

{capitalize(title)}

-

{isAllGood ? "Click to view module status" : "Some systems down"}

+

+ {isAllGood ? ( + "Click to view module status" + ) : ( + <> +

    + {downItems.map((item) => ( +
  • + {item.id} +
  • + ))} +
+ + )} +

{ return ( - + { ); } return ( - + {stats ? (
diff --git a/src/features/output/components/OSDFieldToggle.tsx b/src/features/output/components/OSD/OSDFieldToggle.tsx similarity index 100% rename from src/features/output/components/OSDFieldToggle.tsx rename to src/features/output/components/OSD/OSDFieldToggle.tsx diff --git a/src/features/output/components/OSDFields.tsx b/src/features/output/components/OSD/OSDFields.tsx similarity index 84% rename from src/features/output/components/OSDFields.tsx rename to src/features/output/components/OSD/OSDFields.tsx index c4a3928..7534608 100644 --- a/src/features/output/components/OSDFields.tsx +++ b/src/features/output/components/OSD/OSDFields.tsx @@ -1,7 +1,7 @@ import { Field, useFormikContext } from "formik"; -import { useOSDConfig } from "../hooks/useOSDConfig"; +import { useOSDConfig } from "../../hooks/useOSDConfig"; import OSDFieldToggle from "./OSDFieldToggle"; -import type { OSDConfigFields } from "../../../types/types"; +import type { OSDConfigFields } from "../../../../types/types"; import { toast } from "sonner"; type OSDFieldsProps = { @@ -12,7 +12,16 @@ const OSDFields = ({ isOSDLoading }: OSDFieldsProps) => { const { osdMutation } = useOSDConfig(); const { values } = useFormikContext(); - const includeKeys = Object.keys(values as OSDConfigFields).filter((value) => value.includes("include")); + const validOSDKeys: Array = [ + "includeVRM", + "includeMotion", + "includeTimeStamp", + "includeCameraName", + "overlayPosition", + "OSDTimestampFormat", + ]; + + const includeKeys = validOSDKeys.filter((key) => key.includes("include") && typeof values[key] === "boolean"); const handleSubmit = async (values: OSDConfigFields) => { const result = await osdMutation.mutateAsync(values); diff --git a/src/features/output/components/OSDOptionsCard.tsx b/src/features/output/components/OSD/OSDOptionsCard.tsx similarity index 74% rename from src/features/output/components/OSDOptionsCard.tsx rename to src/features/output/components/OSD/OSDOptionsCard.tsx index dc0f924..6888a3b 100644 --- a/src/features/output/components/OSDOptionsCard.tsx +++ b/src/features/output/components/OSD/OSDOptionsCard.tsx @@ -1,5 +1,6 @@ -import Card from "../../../ui/Card"; -import CardHeader from "../../../ui/CardHeader"; +import Card from "../../../../ui/Card"; +import CardHeader from "../../../../ui/CardHeader"; +import PayloadOptions from "../PayloadOptions/PayloadOptions"; import OSDFields from "./OSDFields"; import { Tab, TabList, TabPanel, Tabs } from "react-tabs"; import "react-tabs/style/react-tabs.css"; @@ -15,13 +16,13 @@ const OSDOptionsCard = ({ isOSDLoading }: OSDOptionsCardProps) => { OSD Settings - payload Settings + Payload Settings -
payload settings
+
diff --git a/src/features/output/components/OutputForms.tsx b/src/features/output/components/OutputForms.tsx index 58faa66..2dd9119 100644 --- a/src/features/output/components/OutputForms.tsx +++ b/src/features/output/components/OutputForms.tsx @@ -6,7 +6,7 @@ import { usePostBearerConfig } from "../hooks/useBearer"; import { useDispatcherConfig } from "../hooks/useDispatcherConfig"; import { useOptionalConstants } from "../hooks/useOptionalConstants"; import { useCustomFields } from "../hooks/useCustomFields"; -import OSDOptionsCard from "./OSDOptionsCard"; +import OSDOptionsCard from "./OSD/OSDOptionsCard"; import { useOSDConfig } from "../hooks/useOSDConfig"; const OutputForms = () => { @@ -89,6 +89,39 @@ const OutputForms = () => { includeCameraName: includeCameraName ?? false, overlayPosition: overlayPosition ?? "Top", OSDTimestampFormat: OSDTimestampFormat ?? "UTC", + + // payload ooptions + includeMac: false, + includeSaFID: false, + includeCharHeight: false, + includeConfidence: false, + includeCorrectSpacing: false, + includeDecodeID: false, + includeDirection: false, + includeFrameHeight: false, + includeFrameID: false, + includeFrameTimeRef: false, + includeFrameWidth: false, + includeHorizSlew: false, + inclduePlate: false, + includeNightModeAction: false, + includeOverview: false, + includePlateSecondary: false, + includePlateTrack: false, + includePlateTrackSecondary: false, + includePreferredCountry: false, + includeRawReads: false, + includeRawREADSSecondary: false, + includeRef: false, + includeSeenCount: false, + includeRepeatedPlate: false, + includeSerialCount: false, + includeTraceCount: false, + includeTrack: false, + includeTrackSecondary: false, + includeVertSlew: false, + includeVRMSecondary: false, + includeHotListMatches: false, }; const handleSubmit = async (values: FormTypes) => { diff --git a/src/features/output/components/PayloadOptions/PayloadOptions.tsx b/src/features/output/components/PayloadOptions/PayloadOptions.tsx new file mode 100644 index 0000000..dc1c834 --- /dev/null +++ b/src/features/output/components/PayloadOptions/PayloadOptions.tsx @@ -0,0 +1,56 @@ +import { useFormikContext } from "formik"; +import type { PayloadConfigFields } from "../../../../types/types"; +import PayloadOptionsToggle from "./PayloadOptionsToggle"; + +const PayloadOptions = () => { + const { values } = useFormikContext(); + + const validPayloadKeys: Array = [ + "includeMac", + "includeSaFID", + "includeCharHeight", + "includeConfidence", + "includeCorrectSpacing", + "includeDecodeID", + "includeDirection", + "includeFrameHeight", + "includeFrameID", + "includeFrameTimeRef", + "includeFrameWidth", + "includeHorizSlew", + "inclduePlate", + "includeNightModeAction", + "includeOverview", + "includePlateSecondary", + "includePlateTrack", + ]; + + const includeKeys = validPayloadKeys.filter((key) => key.includes("include") && typeof values[key] === "boolean"); + + const handleSubmit = async (values: PayloadConfigFields) => { + console.log("Payload Config Submitted:", values); + }; + + return ( +
+
+
+
+ {includeKeys.map((key, index) => ( + + ))} +
+ +
+
+
+ ); +}; + +export default PayloadOptions; diff --git a/src/features/output/components/PayloadOptions/PayloadOptionsToggle.tsx b/src/features/output/components/PayloadOptions/PayloadOptionsToggle.tsx new file mode 100644 index 0000000..8101f8c --- /dev/null +++ b/src/features/output/components/PayloadOptions/PayloadOptionsToggle.tsx @@ -0,0 +1,22 @@ +import { Field } from "formik"; + +type PayloadOptionsToggleProps = { + label: string; + value: string; +}; + +const PayloadOptionsToggle = ({ label, value }: PayloadOptionsToggleProps) => { + return ( +