diff --git a/.env b/.env index 81e1238..6c5ddb9 100644 --- a/.env +++ b/.env @@ -1,21 +1,9 @@ VITE_BASEURL=http://192.168.75.11/ -VITE_CAM_BASE=http://100.72.72.70:8080 +VITE_CAM_BASE=http://100.118.196.113:8080 VITE_FOLKESTONE_BASE=http://100.116.253.81 VITE_TESTURL=http://100.82.205.44/SightingListRear/sightingSummary?mostRecentRef=-1 VITE_OUTSIDE_BASEURL=http://100.82.205.44 VITE_FOLKESTONE_URL=http://100.116.253.81/mergedHistory/sightingSummary?mostRecentRef= VITE_MAV_URL=http://192.168.75.11/SightingListFront/sightingSummary?mostRecentRef= -VITE_AGX_BOX_FRONT_URL=http://192.168.0.90:8080/SightingListFront/sightingSummary?mostRecentRef= -VITE_AGX_BOX_REAR_URL=http://192.168.0.90:8080/SightingListRear/sightingSummary?mostRecentRef= - -VITE_AGX=http://100.72.72.70:8080/SightingListRear/sightingSummary?mostRecentRef= -VITE_AGX_FRONT=http://100.72.72.70:8080/SightingListFront/sightingSummary?mostRecentRef= - -VITE_AGX_FRONT_BASE=http://100.72.72.70:8080/ - -VITE_LOCAL=http://10.42.0.1:8080/SightingListRear/sightingSummary?mostRecentRef= -VITE_LOCAL_FRONT=http://10.42.0.1:8080/SightingListFront/sightingSummary?mostRecentRef= - -VITE_LOCAL_BASE=http://10.42.0.1:8080/ -VITE_LOCAL_BASE_NEW=http://100.113.222.39 \ No newline at end of file +VITE_AGX_BOX_URL=http://100.118.196.113:8080 diff --git a/src/App.tsx b/src/App.tsx index 67ea871..3d9f929 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -8,25 +8,28 @@ import Session from "./pages/Session"; import { NPEDUserProvider } from "./context/providers/NPEDUserContextProvider"; import { AlertHitProvider } from "./context/providers/AlertHitProvider"; import { SoundProvider } from "react-sounds"; +import SoundContextProvider from "./context/providers/SoundContextProvider"; function App() { return ( - - - - - }> - } /> - } /> - } /> - } /> - } /> - } /> - - - - - + + + + + + }> + } /> + } /> + } /> + } /> + } /> + } /> + + + + + + ); } diff --git a/src/assets/sounds/ui/notification.mp3 b/src/assets/sounds/ui/notification.mp3 new file mode 100644 index 0000000..6fa81e8 Binary files /dev/null and b/src/assets/sounds/ui/notification.mp3 differ diff --git a/src/components/CameraOverview/SnapshotContainer.tsx b/src/components/CameraOverview/SnapshotContainer.tsx index e5ad76b..87cb48f 100644 --- a/src/components/CameraOverview/SnapshotContainer.tsx +++ b/src/components/CameraOverview/SnapshotContainer.tsx @@ -1,46 +1,59 @@ import { useGetOverviewSnapshot } from "../../hooks/useGetOverviewSnapshot"; +import type { ZoomInOptions } from "../../types/types"; import NavigationArrow from "../UI/NavigationArrow"; +import { useCameraZoom } from "../../hooks/useCameraZoom"; +import { useEffect } from "react"; +import Loading from "../UI/Loading"; +import ErrorState from "../UI/ErrorState"; type SnapshotContainerProps = { side: string; settingsPage?: boolean; + zoomLevel?: number; + onZoomLevelChange?: (level: number) => void; }; export const SnapshotContainer = ({ side, settingsPage, + zoomLevel, + onZoomLevelChange, }: SnapshotContainerProps) => { - const { canvasRef, isError, isPending } = useGetOverviewSnapshot(); + const { canvasRef, isError, isPending } = useGetOverviewSnapshot(side); + const cameraControllerSide = + side === "CameraA" ? "CameraControllerA" : "CameraControllerB"; + const { mutation } = useCameraZoom({ camera: cameraControllerSide }); - if (isError) return

An error occurred

; - if (isPending) return

Loading...

; + const handleZoomClick = () => { + const baseLevel = zoomLevel ?? 1; + const newLevel = baseLevel >= 8 ? 1 : baseLevel * 2; - const handleZoomClick = (event: React.MouseEvent) => { - const bounds = canvasRef.current?.getBoundingClientRect(); - if (!bounds) return; - const left = bounds.left; - const top = bounds.top; - const x = event.pageX; - const y = event.pageY; - const cw = canvasRef.current?.clientWidth; - const ch = canvasRef.current?.clientHeight; - if (!cw || !ch) return; - const px = x / cw; - const py = y / ch; - console.log({ - left, - top, - x, - y, - px, - py, - }); + if (onZoomLevelChange) onZoomLevelChange(newLevel); + + if (!zoomLevel) return; }; + useEffect(() => { + if (zoomLevel) { + const zoomInOptions: ZoomInOptions = { + camera: cameraControllerSide, + multiplier: zoomLevel, + }; + mutation.mutate(zoomInOptions); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [zoomLevel]); + return (
+ {isError && } + {isPending && ( +
+ +
+ )} Promise | void; + zoomLevel?: number; + onZoomLevelChange?: (level: number) => void; + updateCameraConfigError: null | Error; }; const CameraSettingFields = ({ initialData, updateCameraConfig, + zoomLevel, + onZoomLevelChange, + updateCameraConfigError, }: CameraSettingsProps) => { const [showPwd, setShowPwd] = useState(false); + const cameraControllerSide = + initialData?.id === "CameraA" ? "CameraControllerA" : "CameraControllerB"; + const { mutation, query } = useCameraZoom({ camera: cameraControllerSide }); + const zoomOptions = [1, 2, 4, 8]; + + const parsed = parseRTSPUrl(initialData?.propURI?.value); + + useEffect(() => { + if (!query?.data) return; + const apiZoom = getZoomLevel(query.data); + onZoomLevelChange?.(apiZoom); + }, [query?.data, onZoomLevelChange]); + + const getZoomLevel = (levelstring: string | undefined) => { + switch (levelstring) { + case "1x": + return 1; + + case "2x": + return 2; + + case "4x": + return 4; + + case "8x": + return 8; + default: + return 1; + } + }; const initialValues = useMemo( () => ({ friendlyName: initialData?.id ?? "", cameraAddress: initialData?.propURI?.value ?? "", - userName: "", - password: "", + userName: parsed?.username ?? "", + password: parsed?.password ?? "", id: initialData?.id, + + zoom: zoomLevel, }), - [initialData?.id, initialData?.propURI?.value] + // eslint-disable-next-line react-hooks/exhaustive-deps + [initialData?.id, initialData?.propURI?.value, zoomLevel] ); const validateValues = (values: CameraSettingValues) => { const errors: CameraSettingErrorValues = {}; if (!values.friendlyName) errors.friendlyName = "Required"; if (!values.cameraAddress) errors.cameraAddress = "Required"; - if (!values.userName) errors.userName = "Required"; - if (!values.password) errors.password = "Required"; - return errors; }; @@ -44,6 +84,18 @@ const CameraSettingFields = ({ updateCameraConfig(values); }; + const handleRadioButtonChange = async (levelNumber: number) => { + if (!onZoomLevelChange || !zoomLevel) return; + onZoomLevelChange(levelNumber); + + const zoomInOptions: ZoomInOptions = { + camera: cameraControllerSide, + multiplier: levelNumber, + }; + + mutation.mutate(zoomInOptions); + }; + const selectedZoom = zoomLevel ?? 1; return ( - {({ errors, touched }) => ( + {({ errors, touched, isSubmitting }) => (
@@ -111,7 +163,7 @@ const CameraSettingFields = ({ {errors.password} )} -
+
- - +
+ +
+ {zoomOptions.map((zoom) => ( +
+ handleRadioButtonChange(zoom)} + /> + +
+ ))} +
+
+
+ {updateCameraConfigError ? ( + + ) : ( + + )} +
)} diff --git a/src/components/CameraSettings/CameraSettings.tsx b/src/components/CameraSettings/CameraSettings.tsx index 19f1484..2a0a68e 100644 --- a/src/components/CameraSettings/CameraSettings.tsx +++ b/src/components/CameraSettings/CameraSettings.tsx @@ -4,26 +4,34 @@ import CardHeader from "../UI/CardHeader"; 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, - updateCameraConfigError, - } = useFetchCameraConfig(side); +const CameraSettings = ({ + title, + side, + zoomLevel, + onZoomLevelChange, +}: { + title: string; + side: string; + zoomLevel?: number; + onZoomLevelChange?: (level: number) => void; +}) => { + const { data, updateCameraConfig, updateCameraConfigError } = + useFetchCameraConfig(side); console.log(updateCameraConfigError); - return ( - {isPending && <>Loading camera config} - {isError && <>Error fetching camera config}
- + + { + + }
); diff --git a/src/components/FrontCameraOverview/FrontCameraOverviewCard.tsx b/src/components/FrontCameraOverview/FrontCameraOverviewCard.tsx index 6c449dd..75503b1 100644 --- a/src/components/FrontCameraOverview/FrontCameraOverviewCard.tsx +++ b/src/components/FrontCameraOverview/FrontCameraOverviewCard.tsx @@ -2,14 +2,14 @@ import clsx from "clsx"; import Card from "../UI/Card"; import { useSwipeable } from "react-swipeable"; import { useNavigate } from "react-router"; -import { useOverviewVideo } from "../../hooks/useOverviewVideo"; + import SightingOverview from "../SightingOverview/SightingOverview"; const FrontCameraOverviewCard = () => { - useOverviewVideo(); const navigate = useNavigate(); const handlers = useSwipeable({ onSwipedRight: () => navigate("/camera-settings"), + onSwipedLeft: () => navigate("/rear-camera-settings"), trackMouse: true, }); diff --git a/src/components/FrontCameraSettings/OverviewVideoContainer.tsx b/src/components/FrontCameraSettings/OverviewVideoContainer.tsx index 7ed4c87..1a0b7a8 100644 --- a/src/components/FrontCameraSettings/OverviewVideoContainer.tsx +++ b/src/components/FrontCameraSettings/OverviewVideoContainer.tsx @@ -1,20 +1,32 @@ import clsx from "clsx"; import { SnapshotContainer } from "../CameraOverview/SnapshotContainer"; import Card from "../UI/Card"; -import { useNavigate } from "react-router"; +import { useNavigate, useLocation } from "react-router"; import { useSwipeable } from "react-swipeable"; const OverviewVideoContainer = ({ side, settingsPage, + zoomLevel, + onZoomLevelChange, }: { title: string; side: string; settingsPage?: boolean; + zoomLevel?: number; + onZoomLevelChange?: (level: number) => void; }) => { const navigate = useNavigate(); + const location = useLocation(); const handlers = useSwipeable({ - onSwipedLeft: () => navigate("/"), + onSwipedLeft: () => { + if (location.pathname === "/rear-camera-settings") return; + navigate("/"); + }, + onSwipedRight: () => { + if (location.pathname === "/camera-settings") return; + navigate("/"); + }, trackMouse: true, }); return ( @@ -24,7 +36,12 @@ const OverviewVideoContainer = ({ )} >
- +
); diff --git a/src/components/HistoryList/AlertItem.tsx b/src/components/HistoryList/AlertItem.tsx index ccdcb26..89838a2 100644 --- a/src/components/HistoryList/AlertItem.tsx +++ b/src/components/HistoryList/AlertItem.tsx @@ -15,7 +15,7 @@ type AlertItemProps = { const AlertItem = ({ item }: AlertItemProps) => { const [isModalOpen, setIsModalOpen] = useState(false); - const { dispatch } = useAlertHitContext(); + const { dispatch, isError } = useAlertHitContext(); // const {d} = useCameraBlackboard(); const motionAway = (item?.motion ?? "").toUpperCase() === "AWAY"; @@ -24,6 +24,7 @@ const AlertItem = ({ item }: AlertItemProps) => { const isNPEDHitB = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "B"; const isNPEDHitC = item?.metadata?.npedJSON?.["NPED CATEGORY"] === "C"; + console.log(isError); const handleClick = () => { setIsModalOpen(true); }; diff --git a/src/components/SettingForms/BearerType/BearerTypeFields.tsx b/src/components/SettingForms/BearerType/BearerTypeFields.tsx index db276b4..b06dde3 100644 --- a/src/components/SettingForms/BearerType/BearerTypeFields.tsx +++ b/src/components/SettingForms/BearerType/BearerTypeFields.tsx @@ -1,32 +1,75 @@ -import { Field, useFormikContext } from "formik"; +import { Field, Form, Formik } from "formik"; import FormToggle from "../components/FormToggle"; +import { useCameraOutput } from "../../../hooks/useCameraOutput"; +import { cleanArray } from "../../../utils/utils"; +import FormGroup from "../components/FormGroup"; +import type { BearerTypeFieldType } from "../../../types/types"; export const ValuesComponent = () => { return null; }; const BearerTypeFields = () => { - useFormikContext(); + const { dispatcherQuery, dispatcherMutation } = useCameraOutput(); + + const format = dispatcherQuery?.data?.propFormat?.value; + const rawOptions = dispatcherQuery?.data?.propFormat?.accepted; + const enabled = dispatcherQuery?.data?.propEnabled?.value; + const verbose = dispatcherQuery?.data?.propVerbose?.value; + const options = cleanArray(rawOptions); + + const initialValues: BearerTypeFieldType = { + format: format ?? "JSON", + enabled: enabled === "true", + verbose: verbose === "true", + }; + + const handleSubmit = async (values: BearerTypeFieldType) => { + await dispatcherMutation.mutateAsync(values); + }; return ( -
-
- - - - - -
-
- - -
-
+ + {({ isSubmitting }) => ( +
+
+ + + + {options?.map((option: string) => ( + + ))} + + + +
+ + +
+
+ +
+
+ )} +
); }; diff --git a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx index 7ed45a5..387eec0 100644 --- a/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx +++ b/src/components/SettingForms/Channel1-JSON/ChannelFields.tsx @@ -1,72 +1,185 @@ -import { Field, useFormikContext } from "formik"; +import { Field, Form, Formik, useFormikContext } from "formik"; import FormGroup from "../components/FormGroup"; -import { useState } from "react"; +import { useEffect, useState } from "react"; import { faEyeSlash, faEye } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { useCameraOutput } from "../../../hooks/useCameraOutput"; +import type { + InitialValuesForm, + InitialValuesFormErrors, +} from "../../../types/types"; +import { toast } from "sonner"; const ChannelFields = () => { - useFormikContext(); const [showPwd, setShowPwd] = useState(false); + const { backOfficeQuery, backOfficeMutation } = useCameraOutput(); + + const backOfficeURL = backOfficeQuery?.data?.propBackofficeURL?.value; + const username = backOfficeQuery?.data?.propUsername?.value; + const password = backOfficeQuery?.data?.propPassword?.value; + const connectTimeoutSeconds = + backOfficeQuery?.data?.propConnectTimeoutSeconds?.value; + const readTimeoutSeconds = + backOfficeQuery?.data?.propReadTimeoutSeconds?.value; + + const initialValues: InitialValuesForm = { + backOfficeURL: backOfficeURL ?? "", + username: username ?? "", + password: password ?? "", + connectTimeoutSeconds: Number(connectTimeoutSeconds), + readTimeoutSeconds: Number(readTimeoutSeconds), + }; + + const handleSubmit = async (values: InitialValuesForm) => { + await backOfficeMutation.mutateAsync(values); + }; + + const ValidationToastOnce = () => { + const { submitCount, isValid } = useFormikContext(); + useEffect(() => { + if (submitCount > 0 && !isValid) { + toast.error("Check fields are filled in"); + } + }, [submitCount, isValid]); + return null; + }; + + const validateValues = ( + values: InitialValuesForm + ): InitialValuesFormErrors => { + const errors: InitialValuesFormErrors = {}; + + const url = values.backOfficeURL?.trim(); + const username = values.username?.trim(); + const password = values.password?.trim(); + + if (!url) { + errors.backOfficeURL = "Required"; + } + + if (!username) errors.username = "Required"; + if (!password) errors.password = "Required"; + + const read = Number(values.readTimeoutSeconds); + if (!Number.isFinite(read)) { + errors.readTimeoutSeconds = "Must be a number"; + } else if (read < 0) { + errors.readTimeoutSeconds = "Must be ≥ 0"; + } + + const connect = Number(values.connectTimeoutSeconds); + if (!Number.isFinite(connect)) { + errors.connectTimeoutSeconds = "Must be a number"; + } else if (connect < 0) { + errors.connectTimeoutSeconds = "Must be ≥ 0"; + } + + return errors; + }; + return ( -
- - - - - - - - - - - - setShowPwd((s) => !s)} - icon={showPwd ? faEyeSlash : faEye} - /> - - - - - - - - - -
+ + {({ errors, touched, isSubmitting }) => ( +
+
+ + + + + + + + + + + + + setShowPwd((s) => !s)} + icon={showPwd ? faEyeSlash : faEye} + /> + + + + + + + + + +
+ + + + )} +
); }; diff --git a/src/components/SettingForms/NPED/NPEDFields.tsx b/src/components/SettingForms/NPED/NPEDFields.tsx index 0288916..ee0c9ee 100644 --- a/src/components/SettingForms/NPED/NPEDFields.tsx +++ b/src/components/SettingForms/NPED/NPEDFields.tsx @@ -27,12 +27,11 @@ const NPEDFields = () => { rearId: "NPED", }; - const handleSubmit = (values: NPEDFieldType) => { + const handleSubmit = async (values: NPEDFieldType) => { const valuesToSend = { ...values, }; - signIn(valuesToSend); - toast.success("Signed into NPED Successfully"); + await signIn(valuesToSend); }; const validateValues = (values: NPEDFieldType) => { @@ -55,7 +54,7 @@ const NPEDFields = () => { validate={validateValues} enableReinitialize > - {({ errors, touched }) => ( + {({ errors, touched, isSubmitting }) => (
@@ -113,7 +112,7 @@ const NPEDFields = () => { type="submit" className="w-1/4 text-white bg-green-700 hover:bg-green-800 font-small rounded-lg text-sm px-2 py-2.5 hover:cursor-pointer" > - Login + {isSubmitting ? "Logging in..." : "Login"} ) : ( - - +
+ + +
); }; diff --git a/src/components/SettingForms/SightingData/SightingDataFields.tsx b/src/components/SettingForms/SightingData/SightingDataFields.tsx index 262d694..0259714 100644 --- a/src/components/SettingForms/SightingData/SightingDataFields.tsx +++ b/src/components/SettingForms/SightingData/SightingDataFields.tsx @@ -1,10 +1,8 @@ -import { Field, useFormikContext } from "formik"; +import { Field } from "formik"; import FormGroup from "../components/FormGroup"; import FormToggle from "../components/FormToggle"; const SightingDataFields = () => { - useFormikContext(); - return (
diff --git a/src/components/SettingForms/Sound/SoundSettingsCard.tsx b/src/components/SettingForms/Sound/SoundSettingsCard.tsx new file mode 100644 index 0000000..e2b904c --- /dev/null +++ b/src/components/SettingForms/Sound/SoundSettingsCard.tsx @@ -0,0 +1,14 @@ +import Card from "../../UI/Card"; +import CardHeader from "../../UI/CardHeader"; +import SoundSettingsFields from "./SoundSettingsFields"; + +const SoundSettingsCard = () => { + return ( + + + + + ); +}; + +export default SoundSettingsCard; diff --git a/src/components/SettingForms/Sound/SoundSettingsFields.tsx b/src/components/SettingForms/Sound/SoundSettingsFields.tsx new file mode 100644 index 0000000..af5e8f1 --- /dev/null +++ b/src/components/SettingForms/Sound/SoundSettingsFields.tsx @@ -0,0 +1,117 @@ +import { Field, FieldArray, Form, Formik } from "formik"; +import FormGroup from "../components/FormGroup"; +import type { FormValues, Hotlist } from "../../../types/types"; +import { useSoundContext } from "../../../context/SoundContext"; +import { toast } from "sonner"; + +const SoundSettingsFields = () => { + const { state, dispatch } = useSoundContext(); + const hotlists: Hotlist[] = [ + { name: "hotlist0", sound: "" }, + { name: "hotlist1", sound: "" }, + { name: "hotlist2", sound: "" }, + ]; + + const soundOptions = state?.soundOptions?.map((soundOption) => ({ + value: soundOption?.name, + label: soundOption?.name, + })); + + const initialValues: FormValues = { + sightingSound: state.sightingSound ?? "switch", + NPEDsound: state.NPEDsound ?? "popup", + hotlists, + }; + + const handleSubmit = (values: FormValues) => { + dispatch({ type: "UPDATE", payload: values }); + toast.success("Sound settings updated"); + }; + return ( + + {({ values }) => ( +
+ + + + {soundOptions?.map(({ value, label }) => { + return ( + + ); + })} + + + + + + {soundOptions?.map(({ value, label }) => ( + + ))} + + +
+

Hotlist Sounds

+ + ( +
+ {values.hotlists.length > 0 ? ( + values.hotlists.map((hotlist, index) => ( +
+ + + {soundOptions?.map(({ value, label }) => ( + + ))} + +
+ )) + ) : ( +

No hotlists yet, Add one

+ )} +
+ )} + /> +
+
+ +
+ )} +
+ ); +}; + +export default SoundSettingsFields; diff --git a/src/components/SettingForms/Sound/SoundUpload.tsx b/src/components/SettingForms/Sound/SoundUpload.tsx new file mode 100644 index 0000000..4326746 --- /dev/null +++ b/src/components/SettingForms/Sound/SoundUpload.tsx @@ -0,0 +1,68 @@ +import { Form, Formik } from "formik"; +import FormGroup from "../components/FormGroup"; +import type { SoundUploadValue } from "../../../types/types"; +import { useSoundContext } from "../../../context/SoundContext"; +import { toast } from "sonner"; + +const SoundUpload = () => { + const { dispatch } = useSoundContext(); + const initialValues: SoundUploadValue = { + name: "", + soundFile: null, + }; + + const handleSubmit = (values: SoundUploadValue) => { + if (!values.soundFile) { + toast.warning("Please select an audio file"); + } else { + dispatch({ type: "ADD", payload: values }); + toast.success("Sound file upload successfully"); + } + }; + + return ( + + {({ setFieldValue, errors, setFieldError }) => ( +
+ + + { + if ( + e.target?.files && + e.target?.files[0]?.type === "audio/mpeg" + ) { + setFieldValue("name", e.target.files[0].name); + setFieldValue("soundFile", e.target.files[0]); + } else { + setFieldError("soundFile", "Not an mp3 file"); + toast.error("Not an mp3 file"); + } + }} + /> + + {errors.soundFile && ( +

Not an mp3 file

+ )} + +
+ )} +
+ ); +}; + +export default SoundUpload; diff --git a/src/components/SettingForms/Sound/SoundUploadCard.tsx b/src/components/SettingForms/Sound/SoundUploadCard.tsx new file mode 100644 index 0000000..e751959 --- /dev/null +++ b/src/components/SettingForms/Sound/SoundUploadCard.tsx @@ -0,0 +1,14 @@ +import Card from "../../UI/Card"; +import CardHeader from "../../UI/CardHeader"; +import SoundUpload from "./SoundUpload"; + +const SoundUploadCard = () => { + return ( + + + + + ); +}; + +export default SoundUploadCard; diff --git a/src/components/SettingForms/System/Reboots.tsx b/src/components/SettingForms/System/Reboots.tsx deleted file mode 100644 index 7b6d680..0000000 --- a/src/components/SettingForms/System/Reboots.tsx +++ /dev/null @@ -1,15 +0,0 @@ -export async function handleSoftReboot() { -const response = await fetch( - `http://192.168.75.11/api/restart-flexiai` -); -if (!response.ok) throw new Error("Failed to Software Reboot"); -else alert("Software reboot triggered!"); -} - -export async function handleHardReboot() { -const response = await fetch( - `http://192.168.75.11/api/restart-hardware` -); -if (!response.ok) throw new Error("Failed to Hardware Reboot"); -else alert("Hardware reboot triggered!"); -} \ No newline at end of file diff --git a/src/components/SettingForms/System/SettingSaveRecall.tsx b/src/components/SettingForms/System/SettingSaveRecall.tsx index 83511b9..8d9b3e7 100644 --- a/src/components/SettingForms/System/SettingSaveRecall.tsx +++ b/src/components/SettingForms/System/SettingSaveRecall.tsx @@ -1,3 +1,4 @@ +import { toast } from "sonner"; import type { SystemValues } from "../../../types/types"; import { CAM_BASE } from "../../../utils/config"; @@ -35,7 +36,13 @@ export async function handleSystemSave(values: SystemValues) { ); } } catch (err) { - console.error(err); + if (err instanceof Error) { + toast.error(`Failed to save system settings: ${err.message}`); + console.error(err); + } else { + toast.error("An unexpected error occurred while saving."); + console.error("Unknown error:", err); + } } } @@ -79,7 +86,12 @@ export async function handleSystemRecall() { return { deviceName, sntpServer, sntpInterval, timeZone }; } catch (err) { - console.error(err); + if (err instanceof Error) { + toast.error(`Error: ${err.message}`); + } else { + toast.error("An unexpected error occurred"); + } + return null; } finally { clearTimeout(timeoutId); diff --git a/src/components/SettingForms/System/SystemConfigFields.tsx b/src/components/SettingForms/System/SystemConfigFields.tsx index cdc3f2e..e36fe73 100644 --- a/src/components/SettingForms/System/SystemConfigFields.tsx +++ b/src/components/SettingForms/System/SystemConfigFields.tsx @@ -1,13 +1,16 @@ import { Formik, Field, Form } from "formik"; import FormGroup from "../components/FormGroup"; -import { handleSoftReboot, handleHardReboot } from "./Reboots"; +import { useReboots } from "../../../hooks/useReboots"; import { timezones } from "./timezones"; import SystemFileUpload from "./SystemFileUpload"; import type { SystemValues, SystemValuesErrors } from "../../../types/types"; import { useSystemConfig } from "../../../hooks/useSystemConfig"; const SystemConfigFields = () => { - const { saveSystemSettings, systemSettingsData } = useSystemConfig(); + const { saveSystemSettings, systemSettingsData, saveSystemSettingsLoading } = + useSystemConfig(); + const { softRebootMutation, hardRebootMutation } = useReboots(); + const initialvalues: SystemValues = { deviceName: systemSettingsData?.deviceName ?? "", timeZone: systemSettingsData?.timeZone ?? "", @@ -17,6 +20,7 @@ const SystemConfigFields = () => { }; const handleSubmit = (values: SystemValues) => saveSystemSettings(values); + const validateValues = (values: SystemValues) => { const errors: SystemValuesErrors = {}; const interval = Number(values.sntpInterval); @@ -28,6 +32,14 @@ const SystemConfigFields = () => { return errors; }; + const handleSoftReboot = async () => { + await softRebootMutation.mutate(); + }; + + const handleHardReboot = async () => { + await hardRebootMutation.mutate(); + }; + return ( { validateOnChange validateOnBlur > - {({ values, errors, touched }) => ( + {({ values, errors, touched, isSubmitting }) => (