refactored system settings
This commit is contained in:
@@ -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 (
|
||||
|
||||
@@ -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 (
|
||||
<Card>
|
||||
{isError && <>Cannot Fetch camera config</>}
|
||||
|
||||
@@ -6,6 +6,7 @@ import { toast } from "sonner";
|
||||
|
||||
const NPEDFields = () => {
|
||||
const { signIn, user, signOut } = useNPEDAuth();
|
||||
|
||||
const initialValues = user
|
||||
? {
|
||||
username: user.propUsername.value,
|
||||
|
||||
@@ -7,7 +7,7 @@ const NPEDHotlist = () => {
|
||||
};
|
||||
|
||||
const handleSubmit = (values: HotlistUploadType) => console.log(values.file);
|
||||
|
||||
// upload/hotlist-upload/2
|
||||
return (
|
||||
<Formik initialValues={initialValue} onSubmit={handleSubmit}>
|
||||
{({ setFieldValue, setErrors, errors }) => {
|
||||
|
||||
@@ -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 },
|
||||
],
|
||||
};
|
||||
|
||||
|
||||
@@ -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<File | null>(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<HTMLInputElement>) => {
|
||||
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<HTMLFormElement>) => {
|
||||
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 (
|
||||
<Card className="flex flex-col items-center justify-center">
|
||||
<CardHeader title={"System Config"} />
|
||||
<div className="flex flex-col gap-4 w-full items-left max-w-md">
|
||||
<FormGroup>
|
||||
<label htmlFor="deviceName" className="font-medium whitespace-nowrap md:w-1/2 text-left">Device Name</label>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<input
|
||||
id="deviceName"
|
||||
name="deviceName"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
placeholder="Enter device name"
|
||||
value={deviceName}
|
||||
onChange={e => setDeviceName(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="timeZone" className="font-medium whitespace-nowrap md:w-1/2 text-left">Local Time Zone</label>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<select
|
||||
id="timeZone"
|
||||
name="timeZone"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] w-full max-w-xs"
|
||||
value={timeZone}
|
||||
onChange={e => setTimeZone(e.target.value)}
|
||||
>
|
||||
<option value="">Select Time Zone</option>
|
||||
<option value="Europe/London (UTC+00)">UTC (UTC+00)</option>
|
||||
<option value="Africa/Cairo (UTC+02)">Africa/Cairo (UTC+02)</option>
|
||||
<option value="Africa/Johannesburg (UTC+02)">Africa/Johannesburg (UTC+02)</option>
|
||||
<option value="Africa/Lagos (UTC+01)">Africa/Lagos (UTC+01)</option>
|
||||
<option value="Africa/Monrousing (UTC+00)">Africa/Monrousing (UTC+00)</option>
|
||||
<option value="America/Anchorage (UTC-09)">America/Anchorage (UTC-09)</option>
|
||||
<option value="America/Chicago (UTC-06)">America/Chicago (UTC-06)</option>
|
||||
<option value="America/Denver (UTC-07)">America/Denver (UTC-07)</option>
|
||||
<option value="America/Edmonton (UTC-07)">America/Edmonton (UTC-07)</option>
|
||||
<option value="America/Jamaica (UTC-05)">America/Jamaica (UTC-05)</option>
|
||||
<option value="America/Los Angeles (UTC-08)">America/Los Angeles (UTC-08)</option>
|
||||
<option value="America/Mexico City (UTC-06)">America/Mexico City (UTC-06)</option>
|
||||
<option value="America/Montreal (UTC-05)">America/Montreal (UTC-05)</option>
|
||||
<option value="America/New York (UTC-05)">America/New York (UTC-05)</option>
|
||||
<option value="America/Phoenix (UTC-07)">America/Phoenix (UTC-07)</option>
|
||||
<option value="America/Puerto Rico (UTC-04)">America/Puerto Rico (UTC-04)</option>
|
||||
<option value="America/Sao Paulo (UTC-03)">America/Sao Paulo (UTC-03)</option>
|
||||
<option value="America/Toronto (UTC-05)">America/Toronto (UTC-05)</option>
|
||||
<option value="America/Vancouver (UTC-08)">America/Vancouver (UTC-08)</option>
|
||||
<option value="Asia/Hong Kong (UTC+08)">Asia/Hong Kong (UTC+08)</option>
|
||||
<option value="Asia/Jerusalem (UTC+02)">Asia/Jerusalem (UTC+02)</option>
|
||||
<option value="Asia/Manila (UTC+08)">Asia/Manila (UTC+08)</option>
|
||||
<option value="Asia/Seoul (UTC+09)">Asia/Seoul (UTC+09)</option>
|
||||
<option value="Asia/Tokyo (UTC+09)">Asia/Tokyo (UTC+09)</option>
|
||||
<option value="Atlantic/Reykjavik (UTC+00)">Atlantic/Reykjavik (UTC+00)</option>
|
||||
<option value="Australia/Perth (UTC+08)">Australia/Perth (UTC+08)</option>
|
||||
<option value="Australia/Sydney (UTC+10)">Australia/Sydney (UTC+10)</option>
|
||||
<option value="Europe/Athens (UTC+02)">Europe/Athens (UTC+02)</option>
|
||||
<option value="Europe/Berlin (UTC+01)">Europe/Berlin (UTC+01)</option>
|
||||
<option value="Europe/Brussels (UTC+01)">Europe/Brussels (UTC+01)</option>
|
||||
<option value="Europe/Copenhagen (UTC+01)">Europe/Copenhagen (UTC+01)</option>
|
||||
<option value="Europe/London (UTC+00)">Europe/London (UTC+00)</option>
|
||||
<option value="Europe/Madrid (UTC+01)">Europe/Madrid (UTC+01)</option>
|
||||
<option value="Europe/Moscow (UTC+04)">Europe/Moscow (UTC+04)</option>
|
||||
<option value="Europe/Paris (UTC+01)">Europe/Paris (UTC+01)</option>
|
||||
<option value="Europe/Prague (UTC+01)">Europe/Prague (UTC+01)</option>
|
||||
<option value="Europe/Rome (UTC+01)">Europe/Rome (UTC+01)</option>
|
||||
<option value="Europe/Warsaw (UTC+01)">Europe/Warsaw (UTC+01)</option>
|
||||
<option value="Pacific/Guam (UTC+10)">Pacific/Guam (UTC+10)</option>
|
||||
<option value="Pacific/Honolulu (UTC-10)">Pacific/Honolulu (UTC-10)</option>
|
||||
</select>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="sntpServer" className="font-medium whitespace-nowrap md:w-1/2 text-left">SNTP Server</label>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<input
|
||||
id="sntpServer"
|
||||
name="sntpServer"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
placeholder="Enter SNTP server address"
|
||||
value={sntpServer}
|
||||
onChange={e => setSntpServer(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label htmlFor="sntpInterval" className="font-medium whitespace-nowrap md:w-1/2 text-left">SNTP Interval minutes</label>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<input
|
||||
id="sntpInterval"
|
||||
name="sntpInterval"
|
||||
type="number"
|
||||
min={1}
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
value={sntpInterval}
|
||||
onChange={e => setSntpInterval(Number(e.target.value))}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<button
|
||||
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-full max-w-md"
|
||||
onClick={() => handleSystemSave(deviceName, sntpServer, sntpInterval, timeZone)}
|
||||
>
|
||||
Save System Settings
|
||||
</button>
|
||||
<div className="py-8 w-full">
|
||||
<form onSubmit={handleSubmit} className="flex flex-col items-center gap-2 w-full">
|
||||
<FormGroup>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<input
|
||||
type="file"
|
||||
name="softwareUpdate"
|
||||
id="softwareUpdate"
|
||||
className="file:px-10 file:border file:border-gray-500 file:rounded-lg file:bg-blue-800 file:mr-5 w-full max-w-xs"
|
||||
onChange={handleFileChange}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<button
|
||||
type="submit"
|
||||
className="w-full max-w-md text-white bg-[#26B170] hover:bg-green-700 font-small rounded-lg text-sm px-2 py-2.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={!selectedFile}
|
||||
>
|
||||
Upload Software Update
|
||||
</button>
|
||||
{error && <p className="text-red-500 text-sm">{error}</p>}
|
||||
</form>
|
||||
</div>
|
||||
<button
|
||||
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition w-full max-w-md"
|
||||
onClick={handleSoftReboot}
|
||||
>
|
||||
Software Reboot
|
||||
</button>
|
||||
<button
|
||||
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition w-full max-w-md"
|
||||
onClick={handleHardReboot}
|
||||
>
|
||||
Hardware Reboot
|
||||
</button>
|
||||
</div>
|
||||
<Card>
|
||||
<CardHeader title={"System Config"} />
|
||||
<SystemConfigFields />
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
151
src/components/SettingForms/System/SystemConfigFields.tsx
Normal file
151
src/components/SettingForms/System/SystemConfigFields.tsx
Normal file
@@ -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 (
|
||||
<Formik
|
||||
initialValues={initialvalues}
|
||||
onSubmit={handleSubmit}
|
||||
validate={validateValues}
|
||||
>
|
||||
{({ values, errors, touched }) => (
|
||||
<Form className="flex flex-col space-y-5">
|
||||
<FormGroup>
|
||||
<label
|
||||
htmlFor="deviceName"
|
||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
||||
>
|
||||
Device Name
|
||||
</label>
|
||||
{touched.deviceName && errors.deviceName && (
|
||||
<small className="absolute right-0 -top-5 text-red-500">
|
||||
{errors.deviceName}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
id="deviceName"
|
||||
name="deviceName"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
placeholder="Enter device name"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label
|
||||
htmlFor="timeZone"
|
||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
||||
>
|
||||
Local Time Zone
|
||||
</label>
|
||||
{touched.timeZone && errors.timeZone && (
|
||||
<small className="absolute right-0 -top-5 text-red-500">
|
||||
{errors.timeZone}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
id="timeZone"
|
||||
name="timeZone"
|
||||
as="select"
|
||||
className="p-2 border border-gray-400 rounded-lg text-white bg-[#253445] w-full max-w-xs"
|
||||
>
|
||||
{timezones.map((timezone) => (
|
||||
<option value={timezone.value} key={timezone.label}>
|
||||
{timezone.label}
|
||||
</option>
|
||||
))}
|
||||
</Field>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label
|
||||
htmlFor="sntpServer"
|
||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
||||
>
|
||||
SNTP Server
|
||||
</label>
|
||||
{touched.sntpServer && errors.sntpServer && (
|
||||
<small className="absolute right-0 -top-5 text-red-500">
|
||||
{errors.sntpServer}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
id="sntpServer"
|
||||
name="sntpServer"
|
||||
type="text"
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
placeholder="Enter SNTP server address"
|
||||
/>
|
||||
</FormGroup>
|
||||
<FormGroup>
|
||||
<label
|
||||
htmlFor="sntpInterval"
|
||||
className="font-medium whitespace-nowrap md:w-1/2 text-left"
|
||||
>
|
||||
SNTP Interval minutes
|
||||
</label>
|
||||
{touched.sntpInterval && errors.sntpInterval && (
|
||||
<small className="absolute right-0 -top-5 text-red-500">
|
||||
{errors.sntpInterval}
|
||||
</small>
|
||||
)}
|
||||
<Field
|
||||
id="sntpInterval"
|
||||
name="sntpInterval"
|
||||
type="number"
|
||||
min={1}
|
||||
className="p-2 border border-gray-400 rounded-lg w-full max-w-xs"
|
||||
/>
|
||||
</FormGroup>
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-[#26B170] text-white px-4 py-2 rounded hover:bg-green-700 transition w-full max-w-md"
|
||||
>
|
||||
Save System Settings
|
||||
</button>
|
||||
<SystemFileUpload
|
||||
name={"softwareUpdate"}
|
||||
selectedFile={values.softwareUpdate}
|
||||
/>
|
||||
<button
|
||||
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition w-full max-w-md"
|
||||
onClick={handleSoftReboot}
|
||||
>
|
||||
Software Reboot
|
||||
</button>
|
||||
<button
|
||||
className="bg-red-600 text-white px-4 py-2 rounded hover:bg-red-700 transition w-full max-w-md"
|
||||
onClick={handleHardReboot}
|
||||
>
|
||||
Hardware Reboot
|
||||
</button>
|
||||
</Form>
|
||||
)}
|
||||
</Formik>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemConfigFields;
|
||||
49
src/components/SettingForms/System/SystemFileUpload.tsx
Normal file
49
src/components/SettingForms/System/SystemFileUpload.tsx
Normal file
@@ -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 (
|
||||
<div className="py-8 w-full">
|
||||
<FormGroup>
|
||||
<div className="flex-1 flex justify-end md:w-2/3">
|
||||
<input
|
||||
type="file"
|
||||
name="softwareUpdate"
|
||||
id="softwareUpdate"
|
||||
className="file:px-10 file:border file:border-gray-500 file:rounded-lg file:bg-blue-800 file:mr-5 w-full max-w-xs"
|
||||
onChange={(event) => {
|
||||
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);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</FormGroup>
|
||||
<button
|
||||
type="button"
|
||||
className="w-full max-w-md text-white bg-[#26B170] hover:bg-green-700 font-small rounded-lg text-sm px-2 py-2.5 disabled:opacity-50 disabled:cursor-not-allowed"
|
||||
disabled={!selectedFile}
|
||||
onClick={handleFileUploadClick}
|
||||
>
|
||||
Upload Software Update
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SystemFileUpload;
|
||||
58
src/components/SettingForms/System/timezones.ts
Normal file
58
src/components/SettingForms/System/timezones.ts
Normal file
@@ -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)" },
|
||||
];
|
||||
@@ -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 (
|
||||
<ModalComponent isModalOpen={isSightingModalOpen} close={handleClose}>
|
||||
<div>
|
||||
<h2>Sighting Details</h2>
|
||||
</div>
|
||||
<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>
|
||||
<NumberPlate vrm={sighting?.vrm} motion={motionAway} />
|
||||
</div>
|
||||
<div>
|
||||
<img
|
||||
src={sighting?.overviewUrl}
|
||||
alt="overview patch"
|
||||
className="h-[50%] w-[50%]"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<img src={sighting?.plateUrlColour} alt="plate patch" height={48} />
|
||||
<img
|
||||
src={sighting?.plateUrlInfrared}
|
||||
height={48}
|
||||
alt="infrared patch"
|
||||
className={"opacity-60"}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</ModalComponent>
|
||||
);
|
||||
|
||||
@@ -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<HTMLImageElement | null>(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)
|
||||
</div>
|
||||
</div>
|
||||
<SightingWidgetDetails effectiveSelected={effectiveSelected} />
|
||||
<SightingWidgetDetails effectiveSelected={mostRecent} />
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -62,11 +62,10 @@ export default function SightingHistoryWidget({
|
||||
{/* Rows */}
|
||||
<div className="flex flex-col">
|
||||
{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 (
|
||||
<div
|
||||
key={idx}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Modal from "react-modal";
|
||||
type ModalComponentProps = {
|
||||
isModalOpen: boolean;
|
||||
children: React.ReactNode;
|
||||
close: () => void;
|
||||
};
|
||||
|
||||
const ModalComponent = ({
|
||||
|
||||
@@ -29,7 +29,7 @@ const NavigationArrow = ({ side, settingsPage }: NavigationArrowProps) => {
|
||||
{side === "CameraFront" ? (
|
||||
<FontAwesomeIcon
|
||||
icon={faArrowRight}
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-md hover:cursor-pointer animate-bounce z-30"
|
||||
className="absolute top-[50%] right-[2%] backdrop-blur-lg hover:cursor-pointer animate-bounce z-30"
|
||||
onClick={() => navigationDest(side)}
|
||||
/>
|
||||
) : (
|
||||
|
||||
@@ -23,6 +23,7 @@ export const SightingFeedProvider = ({
|
||||
mostRecent,
|
||||
} = useSightingFeed(url);
|
||||
const [isSightingModalOpen, setSightingModalOpen] = useState(false);
|
||||
|
||||
return (
|
||||
<SightingFeedContext.Provider
|
||||
value={{
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
// Used to fetch and load the configs for the camera side
|
||||
|
||||
import { useMutation, useQuery } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
|
||||
const base_url = import.meta.env.VITE_OUTSIDE_BASEURL;
|
||||
|
||||
const fetchCameraSideConfig = async ({ queryKey }) => {
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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 = () => {
|
||||
>
|
||||
<FrontCameraOverviewCard className="order-1" />
|
||||
<SightingHistoryWidget className="order-5" />
|
||||
<ModalComponent>
|
||||
<div className="text-black">Hello</div>
|
||||
</ModalComponent>
|
||||
</SightingFeedProvider>
|
||||
|
||||
<SightingFeedProvider
|
||||
|
||||
@@ -159,3 +159,19 @@ export interface Prop {
|
||||
value: string;
|
||||
datatype: string;
|
||||
}
|
||||
|
||||
export type SystemValues = {
|
||||
deviceName: string;
|
||||
sntpServer: string;
|
||||
sntpInterval: number;
|
||||
timeZone: string;
|
||||
softwareUpdate?: File | null;
|
||||
};
|
||||
|
||||
export type SystemValuesErrors = {
|
||||
deviceName?: string | undefined;
|
||||
sntpServer?: string | undefined;
|
||||
sntpInterval?: string | undefined;
|
||||
timeZone?: string | undefined;
|
||||
softwareUpdate?: File | null;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user