diff --git a/src/components/CameraSettings/CameraSettingFields.tsx b/src/components/CameraSettings/CameraSettingFields.tsx index 4654d2d..8eec8d0 100644 --- a/src/components/CameraSettings/CameraSettingFields.tsx +++ b/src/components/CameraSettings/CameraSettingFields.tsx @@ -3,7 +3,6 @@ import type { CameraSettingErrorValues, CameraSettingValues, } from "../../types/types"; -import { toast } from "sonner"; const CameraSettingFields = ({ initialData, updateCameraConfig }) => { const initialValues: CameraSettingValues = { @@ -16,9 +15,6 @@ const CameraSettingFields = ({ initialData, updateCameraConfig }) => { const validateValues = (values: CameraSettingValues) => { const errors: CameraSettingErrorValues = {}; - // if (Object.keys(errors).length === 0) { - // toast.error("Please fill in required fields"); - // } if (!values.friendlyName) errors.friendlyName = "Required"; if (!values.cameraAddress) errors.cameraAddress = "Required"; if (!values.userName) errors.userName = "Required"; @@ -28,9 +24,8 @@ const CameraSettingFields = ({ initialData, updateCameraConfig }) => { }; const handleSubmit = (values: CameraSettingValues) => { - // post values to endpoint updateCameraConfig(values); - toast("Settings Saved"); + return; }; return ( diff --git a/src/components/CameraSettings/CameraSettings.tsx b/src/components/CameraSettings/CameraSettings.tsx index cdbf4f7..3fcfab7 100644 --- a/src/components/CameraSettings/CameraSettings.tsx +++ b/src/components/CameraSettings/CameraSettings.tsx @@ -5,9 +5,14 @@ import CameraSettingFields from "./CameraSettingFields"; import { faWrench } from "@fortawesome/free-solid-svg-icons"; const CameraSettings = ({ title, side }: { title: string; side: string }) => { - const { data, isError, isPending, updateCameraConfig } = - useFetchCameraConfig(side); - + const { + data, + isError, + isPending, + updateCameraConfig, + updateCameraConfigError, + } = useFetchCameraConfig(side); + console.log(updateCameraConfigError); return ( {isError && <>Cannot Fetch camera config} diff --git a/src/components/SettingForms/NPED/NPEDFields.tsx b/src/components/SettingForms/NPED/NPEDFields.tsx index 5ad1a22..9c3c10a 100644 --- a/src/components/SettingForms/NPED/NPEDFields.tsx +++ b/src/components/SettingForms/NPED/NPEDFields.tsx @@ -6,6 +6,7 @@ import { toast } from "sonner"; const NPEDFields = () => { const { signIn, user, signOut } = useNPEDAuth(); + const initialValues = user ? { username: user.propUsername.value, diff --git a/src/components/SettingForms/NPED/NPEDHotlist.tsx b/src/components/SettingForms/NPED/NPEDHotlist.tsx index 7e07e8d..0296eae 100644 --- a/src/components/SettingForms/NPED/NPEDHotlist.tsx +++ b/src/components/SettingForms/NPED/NPEDHotlist.tsx @@ -7,7 +7,7 @@ const NPEDHotlist = () => { }; const handleSubmit = (values: HotlistUploadType) => console.log(values.file); - + // upload/hotlist-upload/2 return ( {({ setFieldValue, setErrors, errors }) => { diff --git a/src/components/SettingForms/System/SettingSaveRecall.tsx b/src/components/SettingForms/System/SettingSaveRecall.tsx index beb2b68..79f8f2a 100644 --- a/src/components/SettingForms/System/SettingSaveRecall.tsx +++ b/src/components/SettingForms/System/SettingSaveRecall.tsx @@ -1,17 +1,17 @@ -export async function handleSystemSave( - deviceName: string, - sntpServer: string, - sntpInterval: number, - timeZone: string -) { +import type { SystemValues } from "../../../types/types"; + +export async function handleSystemSave(values: SystemValues) { 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 }, + { property: "propDeviceName", value: values.deviceName }, + { property: "propSNTPServer", value: values.sntpServer }, + { + property: "propSNTPIntervalMinutes", + value: Number(values.sntpInterval), + }, + { property: "propLocalTimeZone", value: values.timeZone }, ], }; diff --git a/src/components/SettingForms/System/SystemCard.tsx b/src/components/SettingForms/System/SystemCard.tsx index ed7752d..a2f6554 100644 --- a/src/components/SettingForms/System/SystemCard.tsx +++ b/src/components/SettingForms/System/SystemCard.tsx @@ -1,54 +1,31 @@ import React from "react"; -import { useEffect } from "react" import Card from "../../UI/Card"; import CardHeader from "../../UI/CardHeader"; -import FormGroup from "../components/FormGroup"; import { sendBlobFileUpload } from "./Upload"; -import { handleSoftReboot, handleHardReboot } from "./Reboots.tsx"; -import { handleSystemRecall, handleSystemSave } from "./SettingSaveRecall.tsx"; +import SystemConfigFields from "./SystemConfigFields.tsx"; const SystemCard = () => { - const [deviceName, setDeviceName] = React.useState(""); - const [timeZone, setTimeZone] = React.useState("Europe/London (UTC+00:00"); - const [sntpServer, setSntpServer] = React.useState("1.uk.pool.ntp.org"); - const [sntpInterval, setSntpInterval] = React.useState(60); const [selectedFile, setSelectedFile] = React.useState(null); const [error, setError] = React.useState(""); - useEffect(() => { - (async () => { - const result = await handleSystemRecall(); // returns { deviceName, sntpServer, sntpInterval, timeZone } | null - if (result) { - const { - deviceName: dn, - sntpServer: ss, - sntpInterval: si, - timeZone: tz - } = result; + // useEffect(() => { + // (async () => { + // const result = await handleSystemRecall(); // returns { deviceName, sntpServer, sntpInterval, timeZone } | null + // if (result) { + // const { + // deviceName: dn, + // sntpServer: ss, + // sntpInterval: si, + // timeZone: tz, + // } = result; - setDeviceName(dn ?? ""); - setSntpServer(ss ?? ""); - setSntpInterval(Number.isFinite(si) ? si : 60); - setTimeZone(tz ?? "UTC (UTC-00)"); - } - })(); - }, []); - - const handleFileChange = (e: React.ChangeEvent) => { - const file = e.target.files?.[0] ?? null; - setSelectedFile(file); - if (!file) { - setError("No file selected."); - return; - } - - if (file.size > 8 * 1024 * 1024) { - setError("File is too large (max 8MB)."); - setSelectedFile(null); - return - }; - setError(""); - } + // setDeviceName(dn ?? ""); + // setSntpServer(ss ?? ""); + // setSntpInterval(Number.isFinite(si) ? si : 60); + // setTimeZone(tz ?? "UTC (UTC-00)"); + // } + // })(); + // }, []); const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); // prevent full page reload @@ -58,160 +35,27 @@ const SystemCard = () => { } setError(""); - const result = await sendBlobFileUpload( selectedFile, { + const result = await sendBlobFileUpload(selectedFile, { timeoutMs: 30000, fieldName: "upload", }); // The helper returns a string (either success body or formatted error) // You can decide how to distinguish. Here, we show it optimistically and let the text speak. - if (result.startsWith("Server returned") || result.startsWith("Timeout") || result.startsWith("HTTP error") || result.startsWith("Unexpected error")) { + if ( + result.startsWith("Server returned") || + result.startsWith("Timeout") || + result.startsWith("HTTP error") || + result.startsWith("Unexpected error") + ) { setError(result); } }; return ( - - -
- - -
- setDeviceName(e.target.value)} - /> -
-
- - -
- -
-
- - -
- setSntpServer(e.target.value)} - /> -
-
- - -
- setSntpInterval(Number(e.target.value))} - /> -
-
- -
-
- -
- -
-
- - {error &&

{error}

} -
-
- - -
+ + + ); }; diff --git a/src/components/SettingForms/System/SystemConfigFields.tsx b/src/components/SettingForms/System/SystemConfigFields.tsx new file mode 100644 index 0000000..c29d0c5 --- /dev/null +++ b/src/components/SettingForms/System/SystemConfigFields.tsx @@ -0,0 +1,151 @@ +import { Formik, Field, Form } from "formik"; +import FormGroup from "../components/FormGroup"; +import { handleSoftReboot, handleHardReboot } from "./Reboots"; +import { handleSystemSave } from "./SettingSaveRecall"; +import { timezones } from "./timezones"; +import SystemFileUpload from "./SystemFileUpload"; +import type { SystemValues, SystemValuesErrors } from "../../../types/types"; + +const SystemConfigFields = () => { + const initialvalues: SystemValues = { + deviceName: "", + timeZone: "", + sntpServer: "", + sntpInterval: 60, + softwareUpdate: null, + }; + + const handleSubmit = (values: SystemValues) => handleSystemSave(values); + const validateValues = (values: SystemValues) => { + const errors: SystemValuesErrors = {}; + const interval = Number(values.sntpInterval); + if (!values.deviceName) errors.deviceName = "Required"; + if (!values.timeZone) errors.timeZone = "Required"; + if (isNaN(interval) || interval <= 0) + errors.sntpInterval = "Cannot be less than 0"; + if (!values.sntpServer) errors.sntpServer = "Required"; + return errors; + }; + + return ( + + {({ values, errors, touched }) => ( +
+ + + {touched.deviceName && errors.deviceName && ( + + {errors.deviceName} + + )} + + + + + {touched.timeZone && errors.timeZone && ( + + {errors.timeZone} + + )} + + {timezones.map((timezone) => ( + + ))} + + + + + {touched.sntpServer && errors.sntpServer && ( + + {errors.sntpServer} + + )} + + + + + {touched.sntpInterval && errors.sntpInterval && ( + + {errors.sntpInterval} + + )} + + + + + + + + )} +
+ ); +}; + +export default SystemConfigFields; diff --git a/src/components/SettingForms/System/SystemFileUpload.tsx b/src/components/SettingForms/System/SystemFileUpload.tsx new file mode 100644 index 0000000..da310b0 --- /dev/null +++ b/src/components/SettingForms/System/SystemFileUpload.tsx @@ -0,0 +1,49 @@ +import { useFormikContext } from "formik"; + +import FormGroup from "../components/FormGroup"; +import { toast } from "sonner"; + +type SystemFileUploadProps = { + name: string; + selectedFile: File | null | undefined; +}; + +const SystemFileUpload = ({ name, selectedFile }: SystemFileUploadProps) => { + const { setFieldValue } = useFormikContext(); + const handleFileUploadClick = () => console.log(selectedFile); + return ( +
+ +
+ { + const file = event.currentTarget.files?.[0]; + if (!file) { + toast.error("No File selected"); + return; + } + + if (file?.size > 8 * 1024 * 1024) + toast.error("File is too large (max 8MB)."); + setFieldValue(name, file); + }} + /> +
+
+ +
+ ); +}; + +export default SystemFileUpload; diff --git a/src/components/SettingForms/System/timezones.ts b/src/components/SettingForms/System/timezones.ts new file mode 100644 index 0000000..7ba2911 --- /dev/null +++ b/src/components/SettingForms/System/timezones.ts @@ -0,0 +1,58 @@ +export const timezones = [ + { value: "Europe/London (UTC+00)", label: "Select Time Zone" }, + { value: "Europe/London (UTC+00)", label: "UTC (UTC+00)" }, + { value: "Africa/Cairo (UTC+02)", label: "Africa/Cairo (UTC+02)" }, + { + value: "Africa/Johannesburg (UTC+02)", + label: "Africa/Johannesburg (UTC+02)", + }, + { value: "Africa/Lagos (UTC+01)", label: "Africa/Lagos (UTC+01)" }, + { value: "Africa/Monrousing (UTC+00)", label: "Africa/Monrousing (UTC+00)" }, + { value: "America/Anchorage (UTC-09)", label: "America/Anchorage (UTC-09)" }, + { value: "America/Chicago (UTC-06)", label: "America/Chicago (UTC-06)" }, + { value: "America/Denver (UTC-07)", label: "America/Denver (UTC-07)" }, + { value: "America/Edmonton (UTC-07)", label: "America/Edmonton (UTC-07)" }, + { value: "America/Jamaica (UTC-05)", label: "America/Jamaica (UTC-05)" }, + { + value: "America/Los Angeles (UTC-08)", + label: "America/Los Angeles (UTC-08)", + }, + { + value: "America/Mexico City (UTC-06)", + label: "America/Mexico City (UTC-06)", + }, + { value: "America/Montreal (UTC-05)", label: "America/Montreal (UTC-05)" }, + { value: "America/New York (UTC-05)", label: "America/New York (UTC-05)" }, + { value: "America/Phoenix (UTC-07)", label: "America/Phoenix (UTC-07)" }, + { + value: "America/Puerto Rico (UTC-04)", + label: "America/Puerto Rico (UTC-04)", + }, + { value: "America/Sao Paulo (UTC-03)", label: "America/Sao Paulo (UTC-03)" }, + { value: "America/Toronto (UTC-05)", label: "America/Toronto (UTC-05)" }, + { value: "America/Vancouver (UTC-08)", label: "America/Vancouver (UTC-08)" }, + { value: "Asia/Hong Kong (UTC+08)", label: "Asia/Hong Kong (UTC+08)" }, + { value: "Asia/Jerusalem (UTC+02)", label: "Asia/Jerusalem (UTC+02)" }, + { value: "Asia/Manila (UTC+08)", label: "Asia/Manila (UTC+08)" }, + { value: "Asia/Seoul (UTC+09)", label: "Asia/Seoul (UTC+09)" }, + { value: "Asia/Tokyo (UTC+09)", label: "Asia/Tokyo (UTC+09)" }, + { + value: "Atlantic/Reykjavik (UTC+00)", + label: "Atlantic/Reykjavik (UTC+00)", + }, + { value: "Australia/Perth (UTC+08)", label: "Australia/Perth (UTC+08)" }, + { value: "Australia/Sydney (UTC+10)", label: "Australia/Sydney (UTC+10)" }, + { value: "Europe/Athens (UTC+02)", label: "Europe/Athens (UTC+02)" }, + { value: "Europe/Berlin (UTC+01)", label: "Europe/Berlin (UTC+01)" }, + { value: "Europe/Brussels (UTC+01)", label: "Europe/Brussels (UTC+01)" }, + { value: "Europe/Copenhagen (UTC+01)", label: "Europe/Copenhagen (UTC+01)" }, + { value: "Europe/London (UTC+00)", label: "Europe/London (UTC+00)" }, + { value: "Europe/Madrid (UTC+01)", label: "Europe/Madrid (UTC+01)" }, + { value: "Europe/Moscow (UTC+04)", label: "Europe/Moscow (UTC+04)" }, + { value: "Europe/Paris (UTC+01)", label: "Europe/Paris (UTC+01)" }, + { value: "Europe/Prague (UTC+01)", label: "Europe/Prague (UTC+01)" }, + { value: "Europe/Rome (UTC+01)", label: "Europe/Rome (UTC+01)" }, + { value: "Europe/Warsaw (UTC+01)", label: "Europe/Warsaw (UTC+01)" }, + { value: "Pacific/Guam (UTC+10)", label: "Pacific/Guam (UTC+10)" }, + { value: "Pacific/Honolulu (UTC-10)", label: "Pacific/Honolulu (UTC-10)" }, +]; diff --git a/src/components/SightingModal/SightingModal.tsx b/src/components/SightingModal/SightingModal.tsx index 59e19b1..2abd51a 100644 --- a/src/components/SightingModal/SightingModal.tsx +++ b/src/components/SightingModal/SightingModal.tsx @@ -1,9 +1,11 @@ +import type { SightingType } from "../../types/types"; import NumberPlate from "../PlateStack/NumberPlate"; import ModalComponent from "../UI/ModalComponent"; type SightingModalProps = { isSightingModalOpen: boolean; handleClose: () => void; + sighting: SightingType | null; }; const SightingModal = ({ @@ -14,16 +16,30 @@ const SightingModal = ({ const motionAway = (sighting?.motion ?? "").toUpperCase() === "AWAY"; return ( +
+

Sighting Details

+
-

{sighting?.vrm}

- - infrared patch +
+ +
+
+ overview patch +
+
+ plate patch + infrared patch +
); diff --git a/src/components/SightingOverview/SightingOverview.tsx b/src/components/SightingOverview/SightingOverview.tsx index ceca05d..5aa6993 100644 --- a/src/components/SightingOverview/SightingOverview.tsx +++ b/src/components/SightingOverview/SightingOverview.tsx @@ -5,15 +5,8 @@ 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"; const SightingOverview = () => { - // const navigate = useNavigate(); - // const handlers = useSwipeable({ - // onSwipedRight: () => navigate("/front-camera-settings"), - // trackMouse: true, - // }); const [overlayMode, setOverlayMode] = useState<0 | 1 | 2>(0); const imgRef = useRef(null); @@ -23,7 +16,7 @@ const SightingOverview = () => { setOverlayMode((m) => ((m + 1) % 3) as 0 | 1 | 2); }, []); - const { effectiveSelected, side, mostRecent } = useSightingFeedContext(); + const { side, mostRecent } = useSightingFeedContext(); useOverviewOverlay(mostRecent, overlayMode, imgRef, canvasRef); @@ -67,7 +60,7 @@ const SightingOverview = () => { (click image to toggle) - + ); }; diff --git a/src/components/SightingsWidget/SightingWidget.tsx b/src/components/SightingsWidget/SightingWidget.tsx index 7ba2ef8..a20cf08 100644 --- a/src/components/SightingsWidget/SightingWidget.tsx +++ b/src/components/SightingsWidget/SightingWidget.tsx @@ -62,11 +62,10 @@ export default function SightingHistoryWidget({ {/* Rows */}
{rows?.map((obj, idx) => { - const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 201; + const isNPEDHit = obj?.metadata?.npedJSON?.status_code === 404; const motionAway = (obj?.motion ?? "").toUpperCase() === "AWAY"; const primaryIsColour = obj?.srcCam === 1; const secondaryMissing = (obj?.vrmSecondary ?? "") === ""; - console.log(obj); return (
void; }; const ModalComponent = ({ diff --git a/src/components/UI/NavigationArrow.tsx b/src/components/UI/NavigationArrow.tsx index 6b7a4dc..dd3612f 100644 --- a/src/components/UI/NavigationArrow.tsx +++ b/src/components/UI/NavigationArrow.tsx @@ -29,7 +29,7 @@ const NavigationArrow = ({ side, settingsPage }: NavigationArrowProps) => { {side === "CameraFront" ? ( navigationDest(side)} /> ) : ( diff --git a/src/context/providers/SightingFeedProvider.tsx b/src/context/providers/SightingFeedProvider.tsx index e689f5d..071bb4c 100644 --- a/src/context/providers/SightingFeedProvider.tsx +++ b/src/context/providers/SightingFeedProvider.tsx @@ -23,6 +23,7 @@ export const SightingFeedProvider = ({ mostRecent, } = useSightingFeed(url); const [isSightingModalOpen, setSightingModalOpen] = useState(false); + return ( { +const fetchCameraSideConfig = async ({ queryKey }: { queryKey: string[] }) => { const [, cameraSide] = queryKey; const fetchUrl = `${base_url}/fetch-config?id=${cameraSide}`; const response = await fetch(fetchUrl); @@ -12,7 +13,10 @@ const fetchCameraSideConfig = async ({ queryKey }) => { return response.json(); }; -const updateCamerasideConfig = async (data) => { +const updateCamerasideConfig = async (data: { + id: string; + friendlyName: string; +}) => { const updateUrl = `${base_url}/update-config?id=${data.id}`; const updateConfigPayload = { @@ -24,7 +28,6 @@ const updateCamerasideConfig = async (data) => { }, ], }; - console.log(updateConfigPayload); const response = await fetch(updateUrl, { method: "POST", body: JSON.stringify(updateConfigPayload), @@ -41,6 +44,8 @@ export const useFetchCameraConfig = (cameraSide: string) => { const updateConfigMutation = useMutation({ mutationKey: ["cameraSideConfigUpdate"], mutationFn: updateCamerasideConfig, + onError: (error) => toast.error(error.message), + onSuccess: () => toast("Settings Successfully saved"), }); return { @@ -48,5 +53,6 @@ export const useFetchCameraConfig = (cameraSide: string) => { isPending: fetchedConfigQuery.isPending, isError: fetchedConfigQuery.isError, updateCameraConfig: updateConfigMutation.mutate, + updateCameraConfigError: updateConfigMutation.error, }; }; diff --git a/src/hooks/useNPEDAuth.ts b/src/hooks/useNPEDAuth.ts index f173a84..84dfb43 100644 --- a/src/hooks/useNPEDAuth.ts +++ b/src/hooks/useNPEDAuth.ts @@ -35,7 +35,6 @@ async function signIn(loginDetails: NPEDFieldType) { { property: "propClientID", value: clientId }, ], }; - console.log(frontId); const frontCameraResponse = await fetch(NPEDLoginURLFront, { method: "POST", body: JSON.stringify(frontCameraPayload), diff --git a/src/pages/Dashboard.tsx b/src/pages/Dashboard.tsx index 029ac9c..ef03b61 100644 --- a/src/pages/Dashboard.tsx +++ b/src/pages/Dashboard.tsx @@ -1,7 +1,7 @@ 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 = () => { @@ -16,9 +16,6 @@ const Dashboard = () => { > - -
Hello
-