code fixes and adding modal
This commit is contained in:
1
TODO.txt
1
TODO.txt
@@ -11,6 +11,7 @@ The selected sighting in the sighting stack seems a tad buggy. Sometimes multipl
|
||||
Can the selected sighting be shown in full detail. How this will look is still up for debate. Either as a pop up card as in AiQ Flexi, or in the OVerview card??
|
||||
How do you know if the time has sync? Make UTC red if not sync.
|
||||
Can the relative aspect ratio in SightingOverview.tsx be the ratio of image pixel size of the image to best take advantage of the space?
|
||||
obscure details on dashboard to a toggle
|
||||
|
||||
|
||||
FYI:
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<link rel="icon" type="image/svg+xml" href="/MAV-Blue.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MAV | In Car System</title>
|
||||
</head>
|
||||
|
||||
18
public/MAV-Blue.svg
Normal file
18
public/MAV-Blue.svg
Normal file
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg id="Layer_2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 231.27 52.63">
|
||||
<defs>
|
||||
<style>
|
||||
.cls-1 {
|
||||
fill: #20456f;
|
||||
}
|
||||
</style>
|
||||
</defs>
|
||||
<g id="Layer_2-2" data-name="Layer_2">
|
||||
<g>
|
||||
<g id="Layer_1-2">
|
||||
<path class="cls-1" d="M150.57,0h-40.57c-7.53,0-13.64,6.11-13.64,13.64v38.99h13.64v-13.68h40.57v13.68h13.64V13.64c0-7.53-6.11-13.64-13.64-13.64ZM110,28.55v-12.59c0-1.72,1.39-3.11,3.11-3.11h34.34c1.72,0,3.11,1.39,3.11,3.11v12.59h-40.57,0ZM88.45,13.64v38.99h-13.64V15.96c0-1.72-1.39-3.11-3.11-3.11h-17.5c-1.72,0-3.11,1.39-3.11,3.11v36.67h-13.73V15.96c0-1.72-1.39-3.11-3.11-3.11h-17.49c-1.72,0-3.11,1.39-3.11,3.11v36.67H0V13.64C0,6.11,6.11,0,13.64,0h23.55c2.72,0,5.18,1.05,7.03,2.76,1.85-1.71,4.32-2.76,7.03-2.76h23.55c7.53,0,13.64,6.11,13.64,13.64h.01ZM193.88,52.63c-1.19,0-2.28-.68-2.8-1.75L166.25,0h13.16c1.19,0,2.28.68,2.8,1.75,0,0,12.25,25.11,16.55,33.92,4.3-8.81,16.55-33.92,16.55-33.92.53-1.07,1.61-1.75,2.8-1.75h13.16l-24.83,50.88c-.52,1.07-1.61,1.75-2.8,1.75h-9.78.02Z"/>
|
||||
</g>
|
||||
<path class="cls-1" d="M222.79,48.39c0-2.36,1.9-4.24,4.24-4.24s4.24,1.88,4.24,4.24-1.88,4.24-4.24,4.24-4.24-1.9-4.24-4.24ZM223.45,48.39c0,1.96,1.6,3.58,3.58,3.58s3.56-1.62,3.56-3.58-1.58-3.56-3.56-3.56-3.58,1.56-3.58,3.56ZM228.17,50.83l-1.26-1.92h-.8v1.92h-.72v-4.86h1.98c.9,0,1.62.58,1.62,1.48,0,1.08-.96,1.44-1.24,1.44l1.3,1.94h-.88ZM226.11,46.57v1.72h1.26c.5,0,.88-.34.88-.84,0-.54-.38-.88-.88-.88h-1.26Z"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -5,13 +5,13 @@ import type {
|
||||
} from "../../types/types";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const CameraSettingFields = () => {
|
||||
const CameraSettingFields = ({ initialData, updateCameraConfig }) => {
|
||||
const initialValues: CameraSettingValues = {
|
||||
friendlyName: "",
|
||||
friendlyName: initialData?.propLEDDriverControlURI?.value,
|
||||
cameraAddress: "",
|
||||
userName: "",
|
||||
password: "",
|
||||
setupCamera: 1,
|
||||
id: initialData?.id,
|
||||
};
|
||||
|
||||
const validateValues = (values: CameraSettingValues) => {
|
||||
@@ -29,7 +29,7 @@ const CameraSettingFields = () => {
|
||||
|
||||
const handleSubmit = (values: CameraSettingValues) => {
|
||||
// post values to endpoint
|
||||
console.log(values);
|
||||
updateCameraConfig(values);
|
||||
toast("Settings Saved");
|
||||
};
|
||||
|
||||
|
||||
@@ -1,15 +1,28 @@
|
||||
import { useFetchCameraConfig } from "../../hooks/useCameraConfig";
|
||||
import Card from "../UI/Card";
|
||||
import CardHeader from "../UI/CardHeader";
|
||||
import CameraSettingFields from "./CameraSettingFields";
|
||||
import { faWrench } from "@fortawesome/free-solid-svg-icons";
|
||||
|
||||
const CameraSettings = ({ title }: { title: string }) => {
|
||||
const CameraSettings = ({ title, side }: { title: string; side: string }) => {
|
||||
const { data, isError, isPending, updateCameraConfig } =
|
||||
useFetchCameraConfig(side);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<div className="relative flex flex-col space-y-3 h-full">
|
||||
<CardHeader title={title} icon={faWrench} />
|
||||
<CameraSettingFields />
|
||||
</div>
|
||||
{isError && <>Cannot Fetch camera config</>}
|
||||
|
||||
{isPending ? (
|
||||
<>Loading</>
|
||||
) : (
|
||||
<div className="relative flex flex-col space-y-3 h-full">
|
||||
<CardHeader title={title} icon={faWrench} />
|
||||
<CameraSettingFields
|
||||
initialData={data}
|
||||
updateCameraConfig={updateCameraConfig}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,74 +1,88 @@
|
||||
export async function handleSystemSave(deviceName: string, sntpServer: string, sntpInterval: number, timeZone: string) {
|
||||
const payload = { // Build JSON
|
||||
id: "GLOBAL--Device",
|
||||
fields: [
|
||||
{ property: "propDeviceName", value: deviceName },
|
||||
{ property: "propSNTPServer", value: sntpServer },
|
||||
{ property: "propSNTPIntervalMinutes", value: Number(sntpInterval) },
|
||||
{ property: "propLocalTimeZone", value: timeZone }
|
||||
]
|
||||
};
|
||||
export async function handleSystemSave(
|
||||
deviceName: string,
|
||||
sntpServer: string,
|
||||
sntpInterval: number,
|
||||
timeZone: string
|
||||
) {
|
||||
const payload = {
|
||||
// Build JSON
|
||||
id: "GLOBAL--Device",
|
||||
fields: [
|
||||
{ property: "propDeviceName", value: deviceName },
|
||||
{ property: "propSNTPServer", value: sntpServer },
|
||||
{ property: "propSNTPIntervalMinutes", value: Number(sntpInterval) },
|
||||
{ property: "propLocalTimeZone", value: timeZone },
|
||||
],
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch("http://192.168.75.11/api/update-config", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json"
|
||||
},
|
||||
body: JSON.stringify(payload)
|
||||
});
|
||||
try {
|
||||
const response = await fetch("http://192.168.75.11/api/update-config", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
Accept: "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(`HTTP ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
|
||||
}
|
||||
|
||||
alert("System Settings Saved Successfully!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(
|
||||
`HTTP ${response.status} ${response.statusText}${
|
||||
text ? ` - ${text}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
alert("System Settings Saved Successfully!");
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
}
|
||||
}
|
||||
|
||||
export async function handleSystemRecall() {
|
||||
const url = "http://192.168.75.11/api/fetch-config?id=GLOBAL--Device";
|
||||
const url = "http://192.168.75.11/api/fetch-config?id=GLOBAL--Device";
|
||||
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 7000);
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 7000);
|
||||
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { "Accept": "application/json" },
|
||||
signal: controller.signal
|
||||
});
|
||||
try {
|
||||
const response = await fetch(url, {
|
||||
method: "GET",
|
||||
headers: { Accept: "application/json" },
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(`HTTP ${response.status} ${response.statusText}${text ? ` - ${text}` : ""}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const deviceName = data?.propDeviceName?.value ?? null;
|
||||
const sntpServer = data?.propSNTPServer?.value ?? null;
|
||||
const timeZone = data?.propLocalTimeZone?.value ?? null;
|
||||
|
||||
let sntpIntervalRaw = data?.propSNTPIntervalMinutes?.value;
|
||||
let sntpInterval =
|
||||
typeof sntpIntervalRaw === "number"
|
||||
? sntpIntervalRaw
|
||||
: Number.parseInt(String(sntpIntervalRaw).trim(), 10);
|
||||
|
||||
if (!Number.isFinite(sntpInterval)) {
|
||||
sntpInterval = 60;
|
||||
}
|
||||
|
||||
return { deviceName, sntpServer, sntpInterval, timeZone };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
if (!response.ok) {
|
||||
const text = await response.text().catch(() => "");
|
||||
throw new Error(
|
||||
`HTTP ${response.status} ${response.statusText}${
|
||||
text ? ` - ${text}` : ""
|
||||
}`
|
||||
);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
const deviceName = data?.propDeviceName?.value ?? null;
|
||||
const sntpServer = data?.propSNTPServer?.value ?? null;
|
||||
const timeZone = data?.propLocalTimeZone?.value ?? null;
|
||||
|
||||
const sntpIntervalRaw = data?.propSNTPIntervalMinutes?.value;
|
||||
let sntpInterval =
|
||||
typeof sntpIntervalRaw === "number"
|
||||
? sntpIntervalRaw
|
||||
: Number.parseInt(String(sntpIntervalRaw).trim(), 10);
|
||||
|
||||
if (!Number.isFinite(sntpInterval)) {
|
||||
sntpInterval = 60;
|
||||
}
|
||||
|
||||
return { deviceName, sntpServer, sntpInterval, timeZone };
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
return null;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
}
|
||||
32
src/components/SightingModal/SightingModal.tsx
Normal file
32
src/components/SightingModal/SightingModal.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import NumberPlate from "../PlateStack/NumberPlate";
|
||||
import ModalComponent from "../UI/ModalComponent";
|
||||
|
||||
type SightingModalProps = {
|
||||
isSightingModalOpen: boolean;
|
||||
handleClose: () => void;
|
||||
};
|
||||
|
||||
const SightingModal = ({
|
||||
isSightingModalOpen,
|
||||
handleClose,
|
||||
sighting,
|
||||
}: SightingModalProps) => {
|
||||
const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY";
|
||||
return (
|
||||
<ModalComponent isModalOpen={isSightingModalOpen} close={handleClose}>
|
||||
<button onClick={handleClose}>close</button>
|
||||
<div>
|
||||
<h2>{sighting?.vrm}</h2>
|
||||
<NumberPlate vrm={sighting?.vrm} motion={motionAway} />
|
||||
<img
|
||||
src={sighting?.plateUrlInfrared}
|
||||
height={48}
|
||||
alt="infrared patch"
|
||||
className={"opacity-60"}
|
||||
/>
|
||||
</div>
|
||||
</ModalComponent>
|
||||
);
|
||||
};
|
||||
|
||||
export default SightingModal;
|
||||
@@ -5,15 +5,15 @@ import { useOverviewOverlay } from "../../hooks/useOverviewOverlay";
|
||||
import { useSightingFeedContext } from "../../context/SightingFeedContext";
|
||||
import { useHiDPICanvas } from "../../hooks/useHiDPICanvas";
|
||||
import NavigationArrow from "../UI/NavigationArrow";
|
||||
import { useSwipeable } from "react-swipeable";
|
||||
import { useNavigate } from "react-router";
|
||||
// import { useSwipeable } from "react-swipeable";
|
||||
// import { useNavigate } from "react-router";
|
||||
|
||||
const SightingOverview = () => {
|
||||
const navigate = useNavigate();
|
||||
const handlers = useSwipeable({
|
||||
onSwipedRight: () => navigate("/front-camera-settings"),
|
||||
trackMouse: true,
|
||||
});
|
||||
// const navigate = useNavigate();
|
||||
// const handlers = useSwipeable({
|
||||
// onSwipedRight: () => navigate("/front-camera-settings"),
|
||||
// trackMouse: true,
|
||||
// });
|
||||
const [overlayMode, setOverlayMode] = useState<0 | 1 | 2>(0);
|
||||
|
||||
const imgRef = useRef<HTMLImageElement | null>(null);
|
||||
@@ -23,51 +23,51 @@ const SightingOverview = () => {
|
||||
setOverlayMode((m) => ((m + 1) % 3) as 0 | 1 | 2);
|
||||
}, []);
|
||||
|
||||
const { effectiveSelected, side, mostRecent, noSighting, isPending } =
|
||||
useSightingFeedContext();
|
||||
const { effectiveSelected, side, mostRecent } = useSightingFeedContext();
|
||||
|
||||
useOverviewOverlay(mostRecent, overlayMode, imgRef, canvasRef);
|
||||
|
||||
const { sync } = useHiDPICanvas(imgRef, canvasRef);
|
||||
|
||||
if (noSighting || isPending) return <p>loading</p>;
|
||||
// if (noSighting || isPending) return <p>loading</p>;
|
||||
|
||||
return (
|
||||
<div className="mt-2 grid gap-3">
|
||||
<div className="inline-block w-[90%] mx-auto" {...handlers}>
|
||||
<div className="flex flex-col">
|
||||
<div className="grid gap-3">
|
||||
<NavigationArrow side={side} />
|
||||
<div className="relative aspect-[1280/800]">
|
||||
<img
|
||||
ref={imgRef}
|
||||
onLoad={() => {
|
||||
sync();
|
||||
setOverlayMode((m) => m);
|
||||
}}
|
||||
src={mostRecent?.overviewUrl || BLANK_IMG}
|
||||
alt="overview"
|
||||
className="absolute inset-0 w-full h-full object-contain cursor-pointer z-10"
|
||||
onClick={onOverviewClick}
|
||||
style={{
|
||||
display: mostRecent?.overviewUrl ? "block" : "none",
|
||||
}}
|
||||
/>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 w-full h-full object-contain z-20 pointer-events-none"
|
||||
/>
|
||||
<div className="inline-block w-full mx-auto">
|
||||
<div className="relative aspect-[1280/800]">
|
||||
<img
|
||||
ref={imgRef}
|
||||
onLoad={() => {
|
||||
sync();
|
||||
setOverlayMode((m) => m);
|
||||
}}
|
||||
src={mostRecent?.overviewUrl || BLANK_IMG}
|
||||
alt="overview"
|
||||
className="absolute inset-0 w-full h-full object-contain cursor-pointer z-10 "
|
||||
onClick={onOverviewClick}
|
||||
style={{
|
||||
display: mostRecent?.overviewUrl ? "block" : "none",
|
||||
}}
|
||||
/>
|
||||
<canvas
|
||||
ref={canvasRef}
|
||||
className="absolute inset-0 w-full h-full object-contain z-20 pointer-events-none "
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-xs opacity-80">
|
||||
Overlay:{" "}
|
||||
{overlayMode === 0
|
||||
? "Off"
|
||||
: overlayMode === 1
|
||||
? "Plate box"
|
||||
: "Track + box"}{" "}
|
||||
(click image to toggle)
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<SightingWidgetDetails effectiveSelected={effectiveSelected} />
|
||||
|
||||
<div className="text-xs opacity-80">
|
||||
Overlay:{" "}
|
||||
{overlayMode === 0
|
||||
? "Off"
|
||||
: overlayMode === 1
|
||||
? "Plate box"
|
||||
: "Track + box"}{" "}
|
||||
(click image to toggle)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from "react";
|
||||
import type { SightingWidgetType } from "../../types/types";
|
||||
import type { SightingType, SightingWidgetType } from "../../types/types";
|
||||
import { BLANK_IMG, capitalize, formatAge } 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";
|
||||
|
||||
function useNow(tickMs = 1000) {
|
||||
const [, setNow] = useState(() => Date.now());
|
||||
@@ -29,97 +30,112 @@ export default function SightingHistoryWidget({
|
||||
className,
|
||||
}: SightingHistoryWidgetProps) {
|
||||
useNow(1000);
|
||||
const { sightings, selectedRef, setSelectedRef } = useSightingFeedContext();
|
||||
|
||||
const {
|
||||
sightings,
|
||||
setSelectedSighting,
|
||||
setSightingModalOpen,
|
||||
isSightingModalOpen,
|
||||
selectedSighting,
|
||||
} = useSightingFeedContext();
|
||||
|
||||
const onRowClick = useCallback(
|
||||
(ref: number) => {
|
||||
setSelectedRef(ref);
|
||||
(sighting: SightingType) => {
|
||||
if (!sighting) return;
|
||||
setSightingModalOpen(!isSightingModalOpen);
|
||||
setSelectedSighting(sighting);
|
||||
},
|
||||
[setSelectedRef]
|
||||
[isSightingModalOpen, setSelectedSighting, setSightingModalOpen]
|
||||
);
|
||||
|
||||
const rows = useMemo(
|
||||
() => sightings?.filter(Boolean) as SightingWidgetType[],
|
||||
[sightings]
|
||||
);
|
||||
|
||||
const handleClose = () => {
|
||||
setSightingModalOpen(false);
|
||||
};
|
||||
return (
|
||||
<Card className={clsx("overflow-y-auto h-100", className)}>
|
||||
<CardHeader title="Front Camera Sightings" />
|
||||
<div className="flex flex-col gap-3 ">
|
||||
{/* Rows */}
|
||||
<div className="flex flex-col">
|
||||
{rows?.map((obj, idx) => {
|
||||
const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 201;
|
||||
const isSelected = obj?.ref === selectedRef;
|
||||
const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY";
|
||||
const primaryIsColour = obj?.srcCam === 1;
|
||||
const secondaryMissing = (obj?.vrmSecondary ?? "") === "";
|
||||
return (
|
||||
<div
|
||||
key={idx}
|
||||
className={`border border-neutral-700 rounded-md mb-2 p-2 cursor-pointer ${
|
||||
isSelected ? "ring-2 ring-blue-400" : ""
|
||||
}`}
|
||||
onClick={() => onRowClick(obj.ref)}
|
||||
>
|
||||
{/* Info bar */}
|
||||
<div className="flex items-center gap-3 text-xs bg-neutral-900 px-2 py-1 rounded">
|
||||
<div className="min-w-14">
|
||||
CH: {obj ? obj.charHeight : "—"}
|
||||
</div>
|
||||
<div className="min-w-14">
|
||||
Seen: {obj ? obj.seenCount : "—"}
|
||||
</div>
|
||||
<div className="min-w-20">
|
||||
{obj ? capitalize(obj.motion) : "—"}
|
||||
</div>
|
||||
<div className="min-w-14 opacity-80">
|
||||
{obj ? formatAge(obj.timeStampMillis) : "—"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Patch row */}
|
||||
<>
|
||||
<Card className={clsx("overflow-y-auto h-100", className)}>
|
||||
<CardHeader title="Front Camera Sightings" />
|
||||
<div className="flex flex-col gap-3 ">
|
||||
{/* Rows */}
|
||||
<div className="flex flex-col">
|
||||
{rows?.map((obj, idx) => {
|
||||
const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 201;
|
||||
const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY";
|
||||
const primaryIsColour = obj?.srcCam === 1;
|
||||
const secondaryMissing = (obj?.vrmSecondary ?? "") === "";
|
||||
console.log(obj);
|
||||
return (
|
||||
<div
|
||||
className={`flex items-center gap-3 mt-2
|
||||
key={idx}
|
||||
className={`border border-neutral-700 rounded-md mb-2 p-2 cursor-pointer`}
|
||||
onClick={() => onRowClick(obj)}
|
||||
>
|
||||
{/* Info bar */}
|
||||
<div className="flex items-center gap-3 text-xs bg-neutral-900 px-2 py-1 rounded">
|
||||
<div className="min-w-14">
|
||||
CH: {obj ? obj.charHeight : "—"}
|
||||
</div>
|
||||
<div className="min-w-14">
|
||||
Seen: {obj ? obj.seenCount : "—"}
|
||||
</div>
|
||||
<div className="min-w-20">
|
||||
{obj ? capitalize(obj.motion) : "—"}
|
||||
</div>
|
||||
<div className="min-w-14 opacity-80">
|
||||
{obj ? formatAge(obj.timeStampMillis) : "—"}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Patch row */}
|
||||
<div
|
||||
className={`flex items-center gap-3 mt-2
|
||||
${isNPEDHit ? "border border-red-600" : ""}
|
||||
`}
|
||||
>
|
||||
<div
|
||||
className={`border p-1 ${
|
||||
primaryIsColour ? "" : "ring-2 ring-lime-400"
|
||||
} ${!obj ? "opacity-30" : ""}`}
|
||||
>
|
||||
<img
|
||||
src={obj?.plateUrlInfrared || BLANK_IMG}
|
||||
height={48}
|
||||
alt="infrared patch"
|
||||
className={!primaryIsColour ? "" : "opacity-60"}
|
||||
/>
|
||||
<div
|
||||
className={`border p-1 ${
|
||||
primaryIsColour ? "" : "ring-2 ring-lime-400"
|
||||
} ${!obj ? "opacity-30" : ""}`}
|
||||
>
|
||||
<img
|
||||
src={obj?.plateUrlInfrared || BLANK_IMG}
|
||||
height={48}
|
||||
alt="infrared patch"
|
||||
className={!primaryIsColour ? "" : "opacity-60"}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className={`border p-1 ${
|
||||
primaryIsColour ? "ring-2 ring-lime-400" : ""
|
||||
} ${
|
||||
secondaryMissing && primaryIsColour
|
||||
? "opacity-30 grayscale"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={obj?.plateUrlColour || BLANK_IMG}
|
||||
height={48}
|
||||
alt="colour patch"
|
||||
className={primaryIsColour ? "" : "opacity-60"}
|
||||
/>
|
||||
</div>
|
||||
<NumberPlate motion={motionAway} vrm={obj?.vrm} />
|
||||
</div>
|
||||
<div
|
||||
className={`border p-1 ${
|
||||
primaryIsColour ? "ring-2 ring-lime-400" : ""
|
||||
} ${
|
||||
secondaryMissing && primaryIsColour
|
||||
? "opacity-30 grayscale"
|
||||
: ""
|
||||
}`}
|
||||
>
|
||||
<img
|
||||
src={obj?.plateUrlColour || BLANK_IMG}
|
||||
height={48}
|
||||
alt="colour patch"
|
||||
className={primaryIsColour ? "" : "opacity-60"}
|
||||
/>
|
||||
</div>
|
||||
<NumberPlate motion={motionAway} vrm={obj?.vrm} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
</Card>
|
||||
<SightingModal
|
||||
isSightingModalOpen={isSightingModalOpen}
|
||||
handleClose={handleClose}
|
||||
sighting={selectedSighting}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { SightingWidgetType } from "../../types/types";
|
||||
import { useState } from "react";
|
||||
|
||||
type SightingWidgetDetailsProps = {
|
||||
effectiveSelected: SightingWidgetType | null;
|
||||
@@ -7,72 +8,88 @@ type SightingWidgetDetailsProps = {
|
||||
const SightingWidgetDetails = ({
|
||||
effectiveSelected,
|
||||
}: SightingWidgetDetailsProps) => {
|
||||
const [advancedDetailsEnabled, setAdvancedDetailsEnabled] = useState(false);
|
||||
|
||||
const handleDetailsClick = () =>
|
||||
setAdvancedDetailsEnabled(!advancedDetailsEnabled);
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm">
|
||||
<div>
|
||||
VRM:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.vrm ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Timestamp:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.timeStamp ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Make:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.make ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Model:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.model ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Country:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.countryCode ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Seen:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.seenCount ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Colour:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.color ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Category:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.category ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Char Ht:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.charHeight ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Plate Size:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.plateSize ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Overview Size:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.overviewSize ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
{effectiveSelected?.detailsUrl ? (
|
||||
<div className="col-span-half">
|
||||
<a
|
||||
href={effectiveSelected.detailsUrl}
|
||||
target="_blank"
|
||||
className="underline text-blue-300"
|
||||
>
|
||||
Sighting Details
|
||||
</a>
|
||||
<>
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-2 text-sm">
|
||||
<div>
|
||||
VRM:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.vrm ?? "—"}</span>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
<div>
|
||||
Make:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.make ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Model:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.model ?? "—"}</span>
|
||||
</div>
|
||||
<div>
|
||||
Colour:{" "}
|
||||
<span className="opacity-90">{effectiveSelected?.color ?? "—"}</span>
|
||||
</div>
|
||||
<div className="col-span-4">
|
||||
Timestamp:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.timeStamp ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
{advancedDetailsEnabled && (
|
||||
<>
|
||||
{" "}
|
||||
<div>
|
||||
Country:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.countryCode ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Seen:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.seenCount ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Category:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.category ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Char Ht:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.charHeight ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Plate Size:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.plateSize ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
Overview Size:{" "}
|
||||
<span className="opacity-90">
|
||||
{effectiveSelected?.overviewSize ?? "—"}
|
||||
</span>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className="col-span-half">
|
||||
<p
|
||||
onClick={handleDetailsClick}
|
||||
className="underline text-blue-300 hover:cursor-pointer"
|
||||
>
|
||||
Sighting Details
|
||||
</p>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -5,10 +5,17 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faGear, faListCheck } from "@fortawesome/free-solid-svg-icons";
|
||||
import type { VersionFieldType } from "../../types/types";
|
||||
|
||||
async function fetchVersions(signal?: AbortSignal): Promise<VersionFieldType> {
|
||||
const res = await fetch("http://192.168.75.11/api/versions", { signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
async function fetchVersions(
|
||||
signal?: AbortSignal
|
||||
): Promise<VersionFieldType | undefined> {
|
||||
try {
|
||||
const res = await fetch("http://192.168.75.11/api/versions", { signal });
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
||||
return res.json();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
const pad = (n: number) => String(n).padStart(2, "0");
|
||||
@@ -33,10 +40,14 @@ export default function Header() {
|
||||
const ac = new AbortController();
|
||||
fetchVersions(ac.signal)
|
||||
.then((data) => {
|
||||
const serverMs = normalizeToMs(data.timeStamp);
|
||||
if (!data) throw new Error("No data");
|
||||
const serverMs = normalizeToMs(data?.timeStamp);
|
||||
setOffsetMs(serverMs - Date.now());
|
||||
})
|
||||
return () => ac.abort();
|
||||
.catch((err) => {
|
||||
console.log(err);
|
||||
});
|
||||
return () => ac.abort("failed");
|
||||
}, []);
|
||||
|
||||
React.useEffect(() => {
|
||||
@@ -69,13 +80,14 @@ export default function Header() {
|
||||
<h2>Local: {localStr}</h2>
|
||||
<h2>UTC: {utcStr}</h2>
|
||||
</div>
|
||||
|
||||
<Link to={"/session-settings"}>
|
||||
<FontAwesomeIcon className="text-white" icon={faListCheck} />
|
||||
</Link>
|
||||
<Link to={"/system-settings"}>
|
||||
<FontAwesomeIcon className="text-white" icon={faGear} />
|
||||
</Link>
|
||||
<div className="flex flex-row space-x-2">
|
||||
<Link to={"/session-settings"}>
|
||||
<FontAwesomeIcon className="text-white" icon={faListCheck} />
|
||||
</Link>
|
||||
<Link to={"/system-settings"}>
|
||||
<FontAwesomeIcon className="text-white" icon={faGear} />
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
26
src/components/UI/ModalComponent.tsx
Normal file
26
src/components/UI/ModalComponent.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type React from "react";
|
||||
import Modal from "react-modal";
|
||||
|
||||
type ModalComponentProps = {
|
||||
isModalOpen: boolean;
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const ModalComponent = ({
|
||||
isModalOpen,
|
||||
children,
|
||||
close,
|
||||
}: ModalComponentProps) => {
|
||||
return (
|
||||
<Modal
|
||||
isOpen={isModalOpen}
|
||||
onRequestClose={close}
|
||||
className="bg-[#1e2a38] p-6 rounded-lg shadow-lg max-w-[65%] mx-auto mt-20 w-full h-[75%] z-100"
|
||||
overlayClassName="fixed inset-0 bg-[#1e2a38]/70 flex justify-center items-start z-100"
|
||||
>
|
||||
{children}
|
||||
</Modal>
|
||||
);
|
||||
};
|
||||
|
||||
export default ModalComponent;
|
||||
@@ -29,13 +29,13 @@ const NavigationArrow = ({ side, settingsPage }: NavigationArrowProps) => {
|
||||
{side === "CameraFront" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce z-30"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowLeft}
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce z-30"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
)}
|
||||
@@ -47,13 +47,13 @@ const NavigationArrow = ({ side, settingsPage }: NavigationArrowProps) => {
|
||||
{side === "Front" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowLeft}
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
className="absolute top-[50%] left-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce z-30"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
) : (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce"
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce z-30"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
import { createContext, useContext } from "react";
|
||||
import type { SightingWidgetType } from "../types/types";
|
||||
import type { SightingType, SightingWidgetType } from "../types/types";
|
||||
|
||||
type SightingFeedContextType = {
|
||||
sightings: (SightingWidgetType | null | undefined)[];
|
||||
selectedRef: number | null;
|
||||
setSelectedRef: (ref: number | null) => void;
|
||||
effectiveSelected: SightingWidgetType | null;
|
||||
// effectiveSelected: SightingWidgetType | null;
|
||||
mostRecent: SightingWidgetType | null;
|
||||
side: string;
|
||||
isPending: boolean;
|
||||
noSighting: boolean;
|
||||
selectedSighting: SightingType | null;
|
||||
setSelectedSighting: (sighting: SightingType | null) => void;
|
||||
setSightingModalOpen: (isSightingModalOpen: boolean) => void;
|
||||
isSightingModalOpen: boolean;
|
||||
};
|
||||
|
||||
export const SightingFeedContext = createContext<
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { ReactNode } from "react";
|
||||
import { useState, type ReactNode } from "react";
|
||||
import { useSightingFeed } from "../../hooks/useSightingFeed";
|
||||
import { SightingFeedContext } from "../SightingFeedContext";
|
||||
|
||||
@@ -17,23 +17,25 @@ export const SightingFeedProvider = ({
|
||||
sightings,
|
||||
selectedRef,
|
||||
setSelectedRef,
|
||||
effectiveSelected,
|
||||
// effectiveSelected,
|
||||
setSelectedSighting,
|
||||
selectedSighting,
|
||||
mostRecent,
|
||||
isPending,
|
||||
noSighting,
|
||||
} = useSightingFeed(url);
|
||||
|
||||
const [isSightingModalOpen, setSightingModalOpen] = useState(false);
|
||||
return (
|
||||
<SightingFeedContext.Provider
|
||||
value={{
|
||||
sightings,
|
||||
selectedRef,
|
||||
setSelectedRef,
|
||||
effectiveSelected,
|
||||
setSelectedSighting,
|
||||
selectedSighting,
|
||||
setSightingModalOpen,
|
||||
isSightingModalOpen,
|
||||
// effectiveSelected,
|
||||
mostRecent,
|
||||
side,
|
||||
isPending,
|
||||
noSighting,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
52
src/hooks/useCameraConfig.ts
Normal file
52
src/hooks/useCameraConfig.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// Used to fetch and load the configs for the camera side
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
|
||||
const base_url = import.meta.env.VITE_OUTSIDE_BASEURL;
|
||||
|
||||
const fetchCameraSideConfig = async ({ queryKey }) => {
|
||||
const [, cameraSide] = queryKey;
|
||||
const fetchUrl = `${base_url}/fetch-config?id=${cameraSide}`;
|
||||
const response = await fetch(fetchUrl);
|
||||
if (!response.ok) throw new Error("cannot react cameraSide ");
|
||||
return response.json();
|
||||
};
|
||||
|
||||
const updateCamerasideConfig = async (data) => {
|
||||
const updateUrl = `${base_url}/update-config?id=${data.id}`;
|
||||
|
||||
const updateConfigPayload = {
|
||||
id: data.id,
|
||||
fields: [
|
||||
{
|
||||
property: "propLEDDriverControlURI",
|
||||
value: data.friendlyName,
|
||||
},
|
||||
],
|
||||
};
|
||||
console.log(updateConfigPayload);
|
||||
const response = await fetch(updateUrl, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(updateConfigPayload),
|
||||
});
|
||||
if (!response.ok) throw new Error("Cannot reach update camera endpoint");
|
||||
};
|
||||
|
||||
export const useFetchCameraConfig = (cameraSide: string) => {
|
||||
const fetchedConfigQuery = useQuery({
|
||||
queryKey: ["cameraSideConfig", cameraSide],
|
||||
queryFn: fetchCameraSideConfig,
|
||||
});
|
||||
|
||||
const updateConfigMutation = useMutation({
|
||||
mutationKey: ["cameraSideConfigUpdate"],
|
||||
mutationFn: updateCamerasideConfig,
|
||||
});
|
||||
|
||||
return {
|
||||
data: fetchedConfigQuery.data,
|
||||
isPending: fetchedConfigQuery.isPending,
|
||||
isError: fetchedConfigQuery.isError,
|
||||
updateCameraConfig: updateConfigMutation.mutate,
|
||||
};
|
||||
};
|
||||
@@ -7,6 +7,7 @@ export const useGetConfigs = () => {
|
||||
async function getConfigs() {
|
||||
try {
|
||||
const response = await fetch(`${apiUrl}/api/config-ids`);
|
||||
|
||||
if (!response.ok) {
|
||||
console.log("failed fetching");
|
||||
}
|
||||
|
||||
@@ -5,12 +5,13 @@ const apiUrl = import.meta.env.VITE_BASEURL;
|
||||
|
||||
async function fetchSnapshot(cameraSide: string) {
|
||||
const response = await fetch(
|
||||
// `http://100.116.253.81/Colour-preview`
|
||||
`${apiUrl}/${cameraSide}-preview`
|
||||
`http://100.116.253.81/Colour-preview`
|
||||
// `${apiUrl}/${cameraSide}-preview`
|
||||
);
|
||||
if (!response.ok) {
|
||||
throw new Error("Cannot reach endpoint");
|
||||
}
|
||||
|
||||
return await response.blob();
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ async function signIn(loginDetails: NPEDFieldType) {
|
||||
{ property: "propClientID", value: clientId },
|
||||
],
|
||||
};
|
||||
|
||||
console.log(frontId);
|
||||
const frontCameraResponse = await fetch(NPEDLoginURLFront, {
|
||||
method: "POST",
|
||||
body: JSON.stringify(frontCameraPayload),
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { SightingWidgetType } from "../types/types";
|
||||
import type { SightingType, SightingWidgetType } from "../types/types";
|
||||
|
||||
async function fetchSighting(url: string, ref: number): Promise<SightingWidgetType> {
|
||||
async function fetchSighting(
|
||||
url: string,
|
||||
ref: number
|
||||
): Promise<SightingWidgetType> {
|
||||
const res = await fetch(`${url}${ref}`);
|
||||
if (!res.ok) throw new Error(String(res.status));
|
||||
return await res.json();
|
||||
@@ -11,6 +14,9 @@ export function useSightingFeed(url: string) {
|
||||
const [sightings, setSightings] = useState<SightingWidgetType[]>([]);
|
||||
const [selectedRef, setSelectedRef] = useState<number | null>(null);
|
||||
const [mostRecent, setMostRecent] = useState<SightingWidgetType | null>(null);
|
||||
const [selectedSighting, setSelectedSighting] = useState<SightingType | null>(
|
||||
null
|
||||
);
|
||||
|
||||
const currentRef = useRef<number>(-1);
|
||||
const pollingTimeout = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||
@@ -35,7 +41,7 @@ export function useSightingFeed(url: string) {
|
||||
currentRef.current = data.ref;
|
||||
lastValidTimestamp.current = now;
|
||||
|
||||
setSightings(prev => {
|
||||
setSightings((prev) => {
|
||||
const updated = [data, ...prev].slice(0, 7);
|
||||
return updated;
|
||||
});
|
||||
@@ -58,13 +64,15 @@ export function useSightingFeed(url: string) {
|
||||
};
|
||||
}, [url]);
|
||||
|
||||
const selected = sightings.find(s => s?.ref === selectedRef) ?? mostRecent;
|
||||
// const selected = sightings.find(s => s?.ref === selectedRef) ?? mostRecent;
|
||||
|
||||
return {
|
||||
sightings,
|
||||
selectedRef,
|
||||
setSelectedRef,
|
||||
mostRecent,
|
||||
effectiveSelected: selected,
|
||||
setSelectedSighting,
|
||||
selectedSighting,
|
||||
// effectiveSelected: selected,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,24 +1,31 @@
|
||||
import FrontCameraOverviewCard from "../components/FrontCameraOverview/FrontCameraOverviewCard";
|
||||
import RearCameraOverviewCard from "../components/RearCameraOverview/RearCameraOverviewCard";
|
||||
|
||||
import SightingHistoryWidget from "../components/SightingsWidget/SightingWidget";
|
||||
import ModalComponent from "../components/UI/ModalComponent";
|
||||
import { SightingFeedProvider } from "../context/providers/SightingFeedProvider";
|
||||
|
||||
const Dashboard = () => {
|
||||
return (
|
||||
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-1 sm:px-2 lg:px-0 w-full">
|
||||
<div className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-2 px-1 sm:px-2 lg:px-0 w-full">
|
||||
<SightingFeedProvider
|
||||
url={
|
||||
"http://192.168.75.11/SightingListFront/sightingSummary?mostRecentRef="
|
||||
"http://100.116.253.81/mergedHistory/sightingSummary?mostRecentRef="
|
||||
// "http://192.168.75.11/SightingListFront/sightingSummary?mostRecentRef="
|
||||
}
|
||||
side="Front"
|
||||
>
|
||||
<FrontCameraOverviewCard className="order-1" />
|
||||
<SightingHistoryWidget className="order-3" />
|
||||
<SightingHistoryWidget className="order-5" />
|
||||
<ModalComponent>
|
||||
<div className="text-black">Hello</div>
|
||||
</ModalComponent>
|
||||
</SightingFeedProvider>
|
||||
|
||||
<SightingFeedProvider
|
||||
url="http://192.168.75.11/SightingListRear/sightingSummary?mostRecentRef="
|
||||
url={
|
||||
"http://100.116.253.81/mergedHistory/sightingSummary?mostRecentRef="
|
||||
// "http://192.168.75.11/SightingListRear/sightingSummary?mostRecentRef="
|
||||
}
|
||||
side="Rear"
|
||||
>
|
||||
<RearCameraOverviewCard className="order-2" />
|
||||
|
||||
@@ -21,7 +21,7 @@ const FrontCamera = () => {
|
||||
side="CameraFront"
|
||||
settingsPage={true}
|
||||
/>
|
||||
<CameraSettings title="Front Camera Settings" />
|
||||
<CameraSettings title="Front Camera Settings" side="CameraFront" />
|
||||
<Toaster />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -16,7 +16,7 @@ const RearCamera = () => {
|
||||
className="mx-auto grid grid-cols-1 sm:grid-cols-1 lg:grid-cols-2 gap-4 px-2 sm:px-4 lg:px-0 w-full order-first"
|
||||
{...handlers}
|
||||
>
|
||||
<CameraSettings title="Rear Camera Settings" />
|
||||
<CameraSettings title="Rear Camera Settings" side={"CameraRear"} />
|
||||
<OverviewVideoContainer
|
||||
title={"Rear Camera"}
|
||||
side={"CameraRear"}
|
||||
|
||||
@@ -34,7 +34,7 @@ export type CameraSettingValues = {
|
||||
cameraAddress: string;
|
||||
userName: string;
|
||||
password: string;
|
||||
setupCamera: number;
|
||||
id: number;
|
||||
};
|
||||
|
||||
export type CameraSettingErrorValues = Partial<
|
||||
@@ -113,7 +113,6 @@ export type VersionFieldType = {
|
||||
"Model No.": string;
|
||||
};
|
||||
|
||||
|
||||
export type Metadata = {
|
||||
npedJSON: NpedJSON;
|
||||
"hotlist-matches": HotlistMatches;
|
||||
|
||||
Reference in New Issue
Block a user