-
);
diff --git a/src/features/dashboard/SystemOverview/PlatesProcessed.tsx b/src/features/dashboard/SystemOverview/PlatesProcessed.tsx
new file mode 100644
index 0000000..83866c0
--- /dev/null
+++ b/src/features/dashboard/SystemOverview/PlatesProcessed.tsx
@@ -0,0 +1,36 @@
+import type { StoreData } from "../../../utils/types";
+
+type PlatesProcessedProps = {
+ platesProcessed: StoreData;
+};
+
+const PlatesProcessed = ({ platesProcessed }: PlatesProcessedProps) => {
+ const totalPending = platesProcessed?.totalPending || 0;
+ const totalActive = platesProcessed?.totalActive || 0;
+ const totalFailed = platesProcessed?.totalLost || 0;
+ const toastReceived = platesProcessed?.totalReceived || 0;
+
+ return (
+
+
+
Total Active
+ {totalActive}
+
+
+
Total Received
+ {toastReceived}
+
+
+
Total Pending
+ {totalPending}
+
+
+
+
Total Failed
+ {totalFailed}
+
+
+ );
+};
+
+export default PlatesProcessed;
diff --git a/src/features/dashboard/SystemOverview/SystemOverview.tsx b/src/features/dashboard/SystemOverview/SystemOverview.tsx
new file mode 100644
index 0000000..63d1e0f
--- /dev/null
+++ b/src/features/dashboard/SystemOverview/SystemOverview.tsx
@@ -0,0 +1,43 @@
+import Card from "../../../components/ui/Card";
+import { useGetStore } from "../hooks/useGetStore";
+
+import { useGetSystemHealth } from "../hooks/useGetSystemHealth";
+import PlatesProcessed from "./PlatesProcessed";
+import SystemStatusContent from "./SystemStatusContent";
+
+const SystemOverview = () => {
+ const { systemHealthQuery } = useGetSystemHealth();
+ const { storeQuery } = useGetStore();
+
+ const platesProcessed = storeQuery?.data || {};
+ const upTime = systemHealthQuery?.data?.UptimeHumane || 0;
+ const startTime = systemHealthQuery?.data?.StartTimeHumane || "";
+ const cameraStatus = systemHealthQuery?.data?.Status || [];
+ const isError = systemHealthQuery?.isError || false;
+
+ if (systemHealthQuery.isLoading) {
+ return
Loading system overview...
;
+ }
+ return (
+
+
+
+
+
+
+ Active Sightings
+
+
+ Reads
+
+
+
+ Up Time
{upTime}
+ Start Time
{startTime}
+
+
+
+ );
+};
+
+export default SystemOverview;
diff --git a/src/features/dashboard/SystemOverview/SystemStatusContent.tsx b/src/features/dashboard/SystemOverview/SystemStatusContent.tsx
new file mode 100644
index 0000000..0209098
--- /dev/null
+++ b/src/features/dashboard/SystemOverview/SystemStatusContent.tsx
@@ -0,0 +1,62 @@
+import StatusIndicator from "../../../components/ui/StatusIndicator";
+import type { CameraStatus } from "../../../utils/types";
+import { useState } from "react";
+import SystemStatusModal from "./systemStatusModal/SystemStatusModal";
+
+type SystemStatusContentProps = {
+ status?: CameraStatus[];
+ isError: boolean;
+};
+
+const SystemStatusContent = ({ status, isError }: SystemStatusContentProps) => {
+ const [isSystemModalOpen, setIsSystemModalOpen] = useState(false);
+ const isAllCamerasOperational = status?.every((camera) => {
+ const allowedTags = ["RUNNING", "VIDEO-PLAYING", "CAMERA-CONTROLLER-READY"];
+ return camera.tags.every((tag) => allowedTags.includes(tag));
+ });
+ const openModal = () => setIsSystemModalOpen(true);
+
+ return (
+ <>
+
+
+ {isError ? (
+
+ ) : isAllCamerasOperational ? (
+
+ ) : (
+
+ )}
+
System Status
+
+
+ {isError ? (
+
Some systems are experiencing issues.
+ ) : (
+ <>
+ {isAllCamerasOperational ? (
+
All systems are operational.
+ ) : (
+
Some systems have issues.
+ )}
+ {!isError && !isAllCamerasOperational && (
+
+ Click to view more details.
+
+ )}
+ >
+ )}
+
+
+
setIsSystemModalOpen(false)}
+ status={status}
+ isError={isError}
+ />
+ >
+ );
+};
+
+export default SystemStatusContent;
diff --git a/src/features/dashboard/SystemOverview/systemStatusModal/SystemStatusModal.tsx b/src/features/dashboard/SystemOverview/systemStatusModal/SystemStatusModal.tsx
new file mode 100644
index 0000000..bb085c0
--- /dev/null
+++ b/src/features/dashboard/SystemOverview/systemStatusModal/SystemStatusModal.tsx
@@ -0,0 +1,38 @@
+import CardHeader from "../../../../components/CardHeader";
+import Badge from "../../../../components/ui/Badge";
+import ModalComponent from "../../../../components/ui/ModalComponent";
+import type { CameraStatus } from "../../../../utils/types";
+
+type SystemStatusModalProps = {
+ title: string;
+ isOpen: boolean;
+ close: () => void;
+ status?: CameraStatus[];
+ isError: boolean;
+};
+
+const SystemStatusModal = ({ isOpen, close, status, title }: SystemStatusModalProps) => {
+ return (
+
+
+
+
+ {status?.map((camera) => (
+ -
+
+
{camera.id}
+
+ {camera.tags.map((tag) => (
+
+ ))}
+
+
+
+ ))}
+
+
+
+ );
+};
+
+export default SystemStatusModal;
diff --git a/src/features/dashboard/components/videoFeed/VideoFeed.tsx b/src/features/dashboard/components/videoFeed/VideoFeed.tsx
index dadc359..aaa44b7 100644
--- a/src/features/dashboard/components/videoFeed/VideoFeed.tsx
+++ b/src/features/dashboard/components/videoFeed/VideoFeed.tsx
@@ -33,7 +33,7 @@ const VideoFeed = ({ mostRecentSighting, isLoading, size, modeSetting, isModal =
useEffect(() => {
const updateSize = () => {
- const width = window.innerWidth * 0.48;
+ const width = window.innerWidth * 0.57;
const height = (width * 2) / 3;
dispatch({ type: "SET_IMAGE_SIZE", payload: { width, height } });
};
diff --git a/src/features/dashboard/hooks/useGetStore.ts b/src/features/dashboard/hooks/useGetStore.ts
new file mode 100644
index 0000000..496bcd5
--- /dev/null
+++ b/src/features/dashboard/hooks/useGetStore.ts
@@ -0,0 +1,16 @@
+import { useQuery } from "@tanstack/react-query";
+import { cambase } from "../../../app/config";
+const fetchStore = async () => {
+ const response = await fetch(`${cambase}/Store0/diagnostics-json`);
+ if (!response.ok) throw new Error("Network response was not ok");
+ return response.json();
+};
+
+export const useGetStore = () => {
+ const storeQuery = useQuery({
+ queryKey: ["storeData"],
+ queryFn: fetchStore,
+ refetchInterval: 5000,
+ });
+ return { storeQuery };
+};
diff --git a/src/features/dashboard/hooks/useGetSystemHealth.ts b/src/features/dashboard/hooks/useGetSystemHealth.ts
new file mode 100644
index 0000000..fb9c475
--- /dev/null
+++ b/src/features/dashboard/hooks/useGetSystemHealth.ts
@@ -0,0 +1,21 @@
+import { useQuery } from "@tanstack/react-query";
+import { cambase } from "../../../app/config";
+
+const fetchSystemHealth = async () => {
+ const response = await fetch(`${cambase}/api/system-health`);
+ if (!response.ok) throw new Error("Network response was not ok");
+ return response.json();
+};
+
+export const useGetSystemHealth = () => {
+ const systemHealthQuery = useQuery({
+ queryKey: ["systemHealth"],
+ queryFn: fetchSystemHealth,
+ refetchInterval: 60000,
+ refetchOnWindowFocus: false,
+ retry: false,
+ staleTime: 30000,
+ });
+
+ return { systemHealthQuery };
+};
diff --git a/src/utils/types.ts b/src/utils/types.ts
index 7b6ed6f..9e5c09f 100644
--- a/src/utils/types.ts
+++ b/src/utils/types.ts
@@ -65,3 +65,25 @@ export type CameraSettingsAction =
type: "SET_IMAGE_SIZE";
payload: { width: number; height: number };
};
+
+export type CameraStatus = {
+ id: string;
+ groupID: string;
+ tags: string[];
+};
+
+export type SystemHealth = {
+ UptimeHumane: string;
+ StartTimeHumane: string;
+ Status: CameraStatus[];
+};
+
+export type StoreData = {
+ totalPending: number;
+ totalActive: number;
+ totalSent: number;
+ totalReceived: number;
+ totalLost: number;
+ sanityCheck: boolean;
+ sanityCheckFormula: string;
+};
diff --git a/src/utils/utils.ts b/src/utils/utils.ts
index 6fa3103..8c14d9a 100644
--- a/src/utils/utils.ts
+++ b/src/utils/utils.ts
@@ -22,3 +22,7 @@ export const timeAgo = (timestampmili: number | null) => {
return `${diffHours === 1 ? "1 hour" : diffHours + " hours"} ago`;
}
};
+
+export function capitalize(s?: string) {
+ return s ? s.charAt(0).toUpperCase() + s.slice(1) : "";
+}