From ab3c12b89ef75d85b2395c94164f2a83d9de9081 Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Mon, 17 Nov 2025 11:34:28 -0600 Subject: [PATCH] rename component for clarity CameraSettingsView is now CameraReviewSettingsView --- web/src/pages/Settings.tsx | 4 +- .../settings/CameraReviewSettingsView.tsx | 738 ++++++++++++++++ web/src/views/settings/CameraSettingsView.tsx | 794 ------------------ 3 files changed, 740 insertions(+), 796 deletions(-) create mode 100644 web/src/views/settings/CameraReviewSettingsView.tsx delete mode 100644 web/src/views/settings/CameraSettingsView.tsx diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 918d492a3..a490a046a 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -26,7 +26,7 @@ import useSWR from "swr"; import FilterSwitch from "@/components/filter/FilterSwitch"; import { ZoneMaskFilterButton } from "@/components/filter/ZoneMaskFilter"; import { PolygonType } from "@/types/canvas"; -import CameraSettingsView from "@/views/settings/CameraSettingsView"; +import CameraReviewSettingsView from "@/views/settings/CameraReviewSettingsView"; import CameraManagementView from "@/views/settings/CameraManagementView"; import MotionTunerView from "@/views/settings/MotionTunerView"; import MasksAndZonesView from "@/views/settings/MasksAndZonesView"; @@ -93,7 +93,7 @@ const settingsGroups = [ label: "cameras", items: [ { key: "cameraManagement", component: CameraManagementView }, - { key: "cameraReview", component: CameraSettingsView }, + { key: "cameraReview", component: CameraReviewSettingsView }, { key: "masksAndZones", component: MasksAndZonesView }, { key: "motionTuner", component: MotionTunerView }, ], diff --git a/web/src/views/settings/CameraReviewSettingsView.tsx b/web/src/views/settings/CameraReviewSettingsView.tsx new file mode 100644 index 000000000..47ea5c22a --- /dev/null +++ b/web/src/views/settings/CameraReviewSettingsView.tsx @@ -0,0 +1,738 @@ +import Heading from "@/components/ui/heading"; +import { useCallback, useContext, useEffect, useMemo, useState } from "react"; +import { Toaster, toast } from "sonner"; +import { + Form, + FormControl, + FormDescription, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useForm } from "react-hook-form"; +import { z } from "zod"; +import { Separator } from "@/components/ui/separator"; +import { Button } from "@/components/ui/button"; +import useSWR from "swr"; +import { FrigateConfig } from "@/types/frigateConfig"; +import { Checkbox } from "@/components/ui/checkbox"; +import ActivityIndicator from "@/components/indicators/activity-indicator"; +import { StatusBarMessagesContext } from "@/context/statusbar-provider"; +import axios from "axios"; +import { Link } from "react-router-dom"; +import { LuExternalLink } from "react-icons/lu"; +import { MdCircle } from "react-icons/md"; +import { cn } from "@/lib/utils"; +import { Trans, useTranslation } from "react-i18next"; +import { Switch } from "@/components/ui/switch"; +import { Label } from "@/components/ui/label"; +import { useDocDomain } from "@/hooks/use-doc-domain"; +import { getTranslatedLabel } from "@/utils/i18n"; +import { + useAlertsState, + useDetectionsState, + useObjectDescriptionState, + useReviewDescriptionState, +} from "@/api/ws"; +import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; +import { resolveZoneName } from "@/hooks/use-zone-friendly-name"; +import { formatList } from "@/utils/stringUtil"; + +type CameraReviewSettingsViewProps = { + selectedCamera: string; + setUnsavedChanges: React.Dispatch>; +}; + +type CameraReviewSettingsValueType = { + alerts_zones: string[]; + detections_zones: string[]; +}; + +export default function CameraReviewSettingsView({ + selectedCamera, + setUnsavedChanges, +}: CameraReviewSettingsViewProps) { + const { t } = useTranslation(["views/settings"]); + const { getLocaleDocUrl } = useDocDomain(); + + const { data: config, mutate: updateConfig } = + useSWR("config"); + + const cameraConfig = useMemo(() => { + if (config && selectedCamera) { + return config.cameras[selectedCamera]; + } + }, [config, selectedCamera]); + + const [changedValue, setChangedValue] = useState(false); + const [isLoading, setIsLoading] = useState(false); + const [selectDetections, setSelectDetections] = useState(false); + + const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!; + + const selectCameraName = useCameraFriendlyName(selectedCamera); + + // zones and labels + + const getZoneName = useCallback( + (zoneId: string, cameraId?: string) => + resolveZoneName(config, zoneId, cameraId), + [config], + ); + + const zones = useMemo(() => { + if (cameraConfig) { + return Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({ + camera: cameraConfig.name, + name, + friendly_name: cameraConfig.zones[name].friendly_name, + objects: zoneData.objects, + color: zoneData.color, + })); + } + }, [cameraConfig]); + + const alertsLabels = useMemo(() => { + return cameraConfig?.review.alerts.labels + ? formatList( + cameraConfig.review.alerts.labels.map((label) => + getTranslatedLabel( + label, + cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", + ), + ), + ) + : ""; + }, [cameraConfig]); + + const detectionsLabels = useMemo(() => { + return cameraConfig?.review.detections.labels + ? formatList( + cameraConfig.review.detections.labels.map((label) => + getTranslatedLabel( + label, + cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", + ), + ), + ) + : ""; + }, [cameraConfig]); + + // form + + const formSchema = z.object({ + alerts_zones: z.array(z.string()), + detections_zones: z.array(z.string()), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + mode: "onChange", + defaultValues: { + alerts_zones: cameraConfig?.review.alerts.required_zones || [], + detections_zones: cameraConfig?.review.detections.required_zones || [], + }, + }); + + const watchedAlertsZones = form.watch("alerts_zones"); + const watchedDetectionsZones = form.watch("detections_zones"); + + const { payload: alertsState, send: sendAlerts } = + useAlertsState(selectedCamera); + const { payload: detectionsState, send: sendDetections } = + useDetectionsState(selectedCamera); + + const { payload: objDescState, send: sendObjDesc } = + useObjectDescriptionState(selectedCamera); + const { payload: revDescState, send: sendRevDesc } = + useReviewDescriptionState(selectedCamera); + + const handleCheckedChange = useCallback( + (isChecked: boolean) => { + if (!isChecked) { + form.reset({ + alerts_zones: watchedAlertsZones, + detections_zones: [], + }); + } + setChangedValue(true); + setSelectDetections(isChecked as boolean); + }, + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps + [watchedAlertsZones], + ); + + const saveToConfig = useCallback( + async ( + { alerts_zones, detections_zones }: CameraReviewSettingsValueType, // values submitted via the form + ) => { + const createQuery = (zones: string[], type: "alerts" | "detections") => + zones.length + ? zones + .map( + (zone) => + `&cameras.${selectedCamera}.review.${type}.required_zones=${zone}`, + ) + .join("") + : cameraConfig?.review[type]?.required_zones && + cameraConfig?.review[type]?.required_zones.length > 0 + ? `&cameras.${selectedCamera}.review.${type}.required_zones` + : ""; + + const alertQueries = createQuery(alerts_zones, "alerts"); + const detectionQueries = createQuery(detections_zones, "detections"); + + axios + .put(`config/set?${alertQueries}${detectionQueries}`, { + requires_restart: 0, + }) + .then((res) => { + if (res.status === 200) { + toast.success( + t("cameraReview.reviewClassification.toast.success"), + { + position: "top-center", + }, + ); + updateConfig(); + } else { + toast.error( + t("toast.save.error.title", { + errorMessage: res.statusText, + ns: "common", + }), + { + position: "top-center", + }, + ); + } + }) + .catch((error) => { + const errorMessage = + error.response?.data?.message || + error.response?.data?.detail || + "Unknown error"; + toast.error( + t("toast.save.error.title", { + errorMessage, + ns: "common", + }), + { + position: "top-center", + }, + ); + }) + .finally(() => { + setIsLoading(false); + }); + }, + [updateConfig, setIsLoading, selectedCamera, cameraConfig, t], + ); + + const onCancel = useCallback(() => { + if (!cameraConfig) { + return; + } + + setChangedValue(false); + setUnsavedChanges(false); + removeMessage( + "camera_settings", + `review_classification_settings_${selectedCamera}`, + ); + form.reset({ + alerts_zones: cameraConfig?.review.alerts.required_zones ?? [], + detections_zones: cameraConfig?.review.detections.required_zones || [], + }); + setSelectDetections( + !!cameraConfig?.review.detections.required_zones?.length, + ); + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [removeMessage, selectedCamera, setUnsavedChanges, cameraConfig]); + + useEffect(() => { + onCancel(); + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedCamera]); + + useEffect(() => { + if (changedValue) { + addMessage( + "camera_settings", + t("cameraReview.reviewClassification.unsavedChanges", { + camera: selectedCamera, + }), + undefined, + `review_classification_settings_${selectedCamera}`, + ); + } else { + removeMessage( + "camera_settings", + `review_classification_settings_${selectedCamera}`, + ); + } + // we know that these deps are correct + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [changedValue, selectedCamera]); + + function onSubmit(values: z.infer) { + setIsLoading(true); + + saveToConfig(values as CameraReviewSettingsValueType); + } + + useEffect(() => { + document.title = t("documentTitle.cameraReview"); + }, [t]); + + if (!cameraConfig && !selectedCamera) { + return ; + } + + return ( + <> +
+ +
+ + {t("cameraReview.title")} + + + + cameraReview.review.title + + +
+
+ { + sendAlerts(isChecked ? "ON" : "OFF"); + }} + /> +
+ +
+
+
+
+ { + sendDetections(isChecked ? "ON" : "OFF"); + }} + /> +
+ +
+
+
+ cameraReview.review.desc +
+
+
+ {cameraConfig?.objects?.genai?.enabled_in_config && ( + <> + + + + + cameraReview.object_descriptions.title + + + +
+
+ { + sendObjDesc(isChecked ? "ON" : "OFF"); + }} + /> +
+ +
+
+
+ + cameraReview.object_descriptions.desc + +
+
+ + )} + + {cameraConfig?.review?.genai?.enabled_in_config && ( + <> + + + + + cameraReview.review_descriptions.title + + + +
+
+ { + sendRevDesc(isChecked ? "ON" : "OFF"); + }} + /> +
+ +
+
+
+ + cameraReview.review_descriptions.desc + +
+
+ + )} + + + + + + cameraReview.reviewClassification.title + + + +
+
+

+ + cameraReview.reviewClassification.desc + +

+
+ + {t("readTheDocumentation", { ns: "common" })} + + +
+
+
+ +
+ +
0 && + "grid items-start gap-5 md:grid-cols-2", + )} + > + ( + + {zones && zones?.length > 0 ? ( + <> +
+ + + camera.review.alerts + + + + + + cameraReview.reviewClassification.selectAlertsZones + + +
+
+ {zones?.map((zone) => ( + ( + + + { + setChangedValue(true); + return checked + ? field.onChange([ + ...field.value, + zone.name, + ]) + : field.onChange( + field.value?.filter( + (value) => + value !== zone.name, + ), + ); + }} + /> + + + {zone.friendly_name || zone.name} + + + )} + /> + ))} +
+ + ) : ( +
+ + cameraReview.reviewClassification.noDefinedZones + +
+ )} + +
+ {watchedAlertsZones && watchedAlertsZones.length > 0 + ? t( + "cameraReview.reviewClassification.zoneObjectAlertsTips", + { + alertsLabels, + zone: formatList( + watchedAlertsZones.map((zone) => + getZoneName(zone), + ), + ), + cameraName: selectCameraName, + }, + ) + : t( + "cameraReview.reviewClassification.objectAlertsTips", + { + alertsLabels, + cameraName: selectCameraName, + }, + )} +
+
+ )} + /> + + ( + + {zones && zones?.length > 0 && ( + <> +
+ + + camera.review.detections + + + + {selectDetections && ( + + + cameraReview.reviewClassification.selectDetectionsZones + + + )} +
+ + {selectDetections && ( +
+ {zones?.map((zone) => ( + ( + + + { + return checked + ? field.onChange([ + ...field.value, + zone.name, + ]) + : field.onChange( + field.value?.filter( + (value) => + value !== zone.name, + ), + ); + }} + /> + + + {zone.friendly_name || zone.name} + + + )} + /> + ))} +
+ )} + + +
+ +
+ +
+
+ + )} + +
+ {watchedDetectionsZones && + watchedDetectionsZones.length > 0 ? ( + !selectDetections ? ( + + getZoneName(zone), + ), + ), + cameraName: selectCameraName, + }} + ns="views/settings" + /> + ) : ( + + getZoneName(zone), + ), + ), + cameraName: selectCameraName, + }} + ns="views/settings" + /> + ) + ) : ( + + )} +
+
+ )} + /> +
+ + +
+ + +
+ + +
+
+ + ); +} diff --git a/web/src/views/settings/CameraSettingsView.tsx b/web/src/views/settings/CameraSettingsView.tsx deleted file mode 100644 index e102dd75d..000000000 --- a/web/src/views/settings/CameraSettingsView.tsx +++ /dev/null @@ -1,794 +0,0 @@ -import Heading from "@/components/ui/heading"; -import { useCallback, useContext, useEffect, useMemo, useState } from "react"; -import { Toaster, toast } from "sonner"; -import { - Form, - FormControl, - FormDescription, - FormField, - FormItem, - FormLabel, - FormMessage, -} from "@/components/ui/form"; -import { zodResolver } from "@hookform/resolvers/zod"; -import { useForm } from "react-hook-form"; -import { z } from "zod"; -import { Separator } from "@/components/ui/separator"; -import { Button } from "@/components/ui/button"; -import useSWR from "swr"; -import { FrigateConfig } from "@/types/frigateConfig"; -import { Checkbox } from "@/components/ui/checkbox"; -import ActivityIndicator from "@/components/indicators/activity-indicator"; -import { StatusBarMessagesContext } from "@/context/statusbar-provider"; -import axios from "axios"; -import { Link } from "react-router-dom"; -import { LuExternalLink } from "react-icons/lu"; -import { MdCircle } from "react-icons/md"; -import { cn } from "@/lib/utils"; -import { Trans, useTranslation } from "react-i18next"; -import { Switch } from "@/components/ui/switch"; -import { Label } from "@/components/ui/label"; -import { useDocDomain } from "@/hooks/use-doc-domain"; -import { getTranslatedLabel } from "@/utils/i18n"; -import { - useAlertsState, - useDetectionsState, - useObjectDescriptionState, - useReviewDescriptionState, -} from "@/api/ws"; -import CameraEditForm from "@/components/settings/CameraEditForm"; -import CameraWizardDialog from "@/components/settings/CameraWizardDialog"; -import { IoMdArrowRoundBack } from "react-icons/io"; -import { isDesktop } from "react-device-detect"; -import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; -import { resolveZoneName } from "@/hooks/use-zone-friendly-name"; -import { formatList } from "@/utils/stringUtil"; - -type CameraSettingsViewProps = { - selectedCamera: string; - setUnsavedChanges: React.Dispatch>; -}; - -type CameraReviewSettingsValueType = { - alerts_zones: string[]; - detections_zones: string[]; -}; - -export default function CameraSettingsView({ - selectedCamera, - setUnsavedChanges, -}: CameraSettingsViewProps) { - const { t } = useTranslation(["views/settings"]); - const { getLocaleDocUrl } = useDocDomain(); - - const { data: config, mutate: updateConfig } = - useSWR("config"); - - const cameraConfig = useMemo(() => { - if (config && selectedCamera) { - return config.cameras[selectedCamera]; - } - }, [config, selectedCamera]); - - const [changedValue, setChangedValue] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [selectDetections, setSelectDetections] = useState(false); - const [viewMode, setViewMode] = useState<"settings" | "add" | "edit">( - "settings", - ); // Control view state - const [editCameraName, setEditCameraName] = useState( - undefined, - ); // Track camera being edited - const [showWizard, setShowWizard] = useState(false); - - const { addMessage, removeMessage } = useContext(StatusBarMessagesContext)!; - - const selectCameraName = useCameraFriendlyName(selectedCamera); - - // zones and labels - - const getZoneName = useCallback( - (zoneId: string, cameraId?: string) => - resolveZoneName(config, zoneId, cameraId), - [config], - ); - - const zones = useMemo(() => { - if (cameraConfig) { - return Object.entries(cameraConfig.zones).map(([name, zoneData]) => ({ - camera: cameraConfig.name, - name, - friendly_name: cameraConfig.zones[name].friendly_name, - objects: zoneData.objects, - color: zoneData.color, - })); - } - }, [cameraConfig]); - - const alertsLabels = useMemo(() => { - return cameraConfig?.review.alerts.labels - ? formatList( - cameraConfig.review.alerts.labels.map((label) => - getTranslatedLabel( - label, - cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", - ), - ), - ) - : ""; - }, [cameraConfig]); - - const detectionsLabels = useMemo(() => { - return cameraConfig?.review.detections.labels - ? formatList( - cameraConfig.review.detections.labels.map((label) => - getTranslatedLabel( - label, - cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", - ), - ), - ) - : ""; - }, [cameraConfig]); - - // form - - const formSchema = z.object({ - alerts_zones: z.array(z.string()), - detections_zones: z.array(z.string()), - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - mode: "onChange", - defaultValues: { - alerts_zones: cameraConfig?.review.alerts.required_zones || [], - detections_zones: cameraConfig?.review.detections.required_zones || [], - }, - }); - - const watchedAlertsZones = form.watch("alerts_zones"); - const watchedDetectionsZones = form.watch("detections_zones"); - - const { payload: alertsState, send: sendAlerts } = - useAlertsState(selectedCamera); - const { payload: detectionsState, send: sendDetections } = - useDetectionsState(selectedCamera); - - const { payload: objDescState, send: sendObjDesc } = - useObjectDescriptionState(selectedCamera); - const { payload: revDescState, send: sendRevDesc } = - useReviewDescriptionState(selectedCamera); - - const handleCheckedChange = useCallback( - (isChecked: boolean) => { - if (!isChecked) { - form.reset({ - alerts_zones: watchedAlertsZones, - detections_zones: [], - }); - } - setChangedValue(true); - setSelectDetections(isChecked as boolean); - }, - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - [watchedAlertsZones], - ); - - const saveToConfig = useCallback( - async ( - { alerts_zones, detections_zones }: CameraReviewSettingsValueType, // values submitted via the form - ) => { - const createQuery = (zones: string[], type: "alerts" | "detections") => - zones.length - ? zones - .map( - (zone) => - `&cameras.${selectedCamera}.review.${type}.required_zones=${zone}`, - ) - .join("") - : cameraConfig?.review[type]?.required_zones && - cameraConfig?.review[type]?.required_zones.length > 0 - ? `&cameras.${selectedCamera}.review.${type}.required_zones` - : ""; - - const alertQueries = createQuery(alerts_zones, "alerts"); - const detectionQueries = createQuery(detections_zones, "detections"); - - axios - .put(`config/set?${alertQueries}${detectionQueries}`, { - requires_restart: 0, - }) - .then((res) => { - if (res.status === 200) { - toast.success( - t("cameraReview.reviewClassification.toast.success"), - { - position: "top-center", - }, - ); - updateConfig(); - } else { - toast.error( - t("toast.save.error.title", { - errorMessage: res.statusText, - ns: "common", - }), - { - position: "top-center", - }, - ); - } - }) - .catch((error) => { - const errorMessage = - error.response?.data?.message || - error.response?.data?.detail || - "Unknown error"; - toast.error( - t("toast.save.error.title", { - errorMessage, - ns: "common", - }), - { - position: "top-center", - }, - ); - }) - .finally(() => { - setIsLoading(false); - }); - }, - [updateConfig, setIsLoading, selectedCamera, cameraConfig, t], - ); - - const onCancel = useCallback(() => { - if (!cameraConfig) { - return; - } - - setChangedValue(false); - setUnsavedChanges(false); - removeMessage( - "camera_settings", - `review_classification_settings_${selectedCamera}`, - ); - form.reset({ - alerts_zones: cameraConfig?.review.alerts.required_zones ?? [], - detections_zones: cameraConfig?.review.detections.required_zones || [], - }); - setSelectDetections( - !!cameraConfig?.review.detections.required_zones?.length, - ); - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [removeMessage, selectedCamera, setUnsavedChanges, cameraConfig]); - - useEffect(() => { - onCancel(); - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [selectedCamera]); - - useEffect(() => { - if (changedValue) { - addMessage( - "camera_settings", - t("cameraReview.reviewClassification.unsavedChanges", { - camera: selectedCamera, - }), - undefined, - `review_classification_settings_${selectedCamera}`, - ); - } else { - removeMessage( - "camera_settings", - `review_classification_settings_${selectedCamera}`, - ); - } - // we know that these deps are correct - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [changedValue, selectedCamera]); - - function onSubmit(values: z.infer) { - setIsLoading(true); - - saveToConfig(values as CameraReviewSettingsValueType); - } - - useEffect(() => { - document.title = t("documentTitle.cameraReview"); - }, [t]); - - // Handle back navigation from add/edit form - const handleBack = useCallback(() => { - setViewMode("settings"); - setEditCameraName(undefined); - updateConfig(); - }, [updateConfig]); - - if (!cameraConfig && !selectedCamera && viewMode === "settings") { - return ; - } - - return ( - <> -
- -
- {viewMode === "settings" ? ( - <> - - {t("cameraReview.title")} - - - - cameraReview.review.title - - -
-
- { - sendAlerts(isChecked ? "ON" : "OFF"); - }} - /> -
- -
-
-
-
- { - sendDetections(isChecked ? "ON" : "OFF"); - }} - /> -
- -
-
-
- cameraReview.review.desc -
-
-
- {cameraConfig?.objects?.genai?.enabled_in_config && ( - <> - - - - - cameraReview.object_descriptions.title - - - -
-
- { - sendObjDesc(isChecked ? "ON" : "OFF"); - }} - /> -
- -
-
-
- - cameraReview.object_descriptions.desc - -
-
- - )} - - {cameraConfig?.review?.genai?.enabled_in_config && ( - <> - - - - - cameraReview.review_descriptions.title - - - -
-
- { - sendRevDesc(isChecked ? "ON" : "OFF"); - }} - /> -
- -
-
-
- - cameraReview.review_descriptions.desc - -
-
- - )} - - - - - - cameraReview.reviewClassification.title - - - -
-
-

- - cameraReview.reviewClassification.desc - -

-
- - {t("readTheDocumentation", { ns: "common" })} - - -
-
-
- -
- -
0 && - "grid items-start gap-5 md:grid-cols-2", - )} - > - ( - - {zones && zones?.length > 0 ? ( - <> -
- - - camera.review.alerts - - - - - - cameraReview.reviewClassification.selectAlertsZones - - -
-
- {zones?.map((zone) => ( - ( - - - { - setChangedValue(true); - return checked - ? field.onChange([ - ...field.value, - zone.name, - ]) - : field.onChange( - field.value?.filter( - (value) => - value !== zone.name, - ), - ); - }} - /> - - - {zone.friendly_name || zone.name} - - - )} - /> - ))} -
- - ) : ( -
- - cameraReview.reviewClassification.noDefinedZones - -
- )} - -
- {watchedAlertsZones && watchedAlertsZones.length > 0 - ? t( - "cameraReview.reviewClassification.zoneObjectAlertsTips", - { - alertsLabels, - zone: formatList( - watchedAlertsZones.map((zone) => - getZoneName(zone), - ), - ), - cameraName: selectCameraName, - }, - ) - : t( - "cameraReview.reviewClassification.objectAlertsTips", - { - alertsLabels, - cameraName: selectCameraName, - }, - )} -
-
- )} - /> - - ( - - {zones && zones?.length > 0 && ( - <> -
- - - camera.review.detections - - - - {selectDetections && ( - - - cameraReview.reviewClassification.selectDetectionsZones - - - )} -
- - {selectDetections && ( -
- {zones?.map((zone) => ( - ( - - - { - return checked - ? field.onChange([ - ...field.value, - zone.name, - ]) - : field.onChange( - field.value?.filter( - (value) => - value !== zone.name, - ), - ); - }} - /> - - - {zone.friendly_name || zone.name} - - - )} - /> - ))} -
- )} - - -
- -
- -
-
- - )} - -
- {watchedDetectionsZones && - watchedDetectionsZones.length > 0 ? ( - !selectDetections ? ( - - getZoneName(zone), - ), - ), - cameraName: selectCameraName, - }} - ns="views/settings" - /> - ) : ( - - getZoneName(zone), - ), - ), - cameraName: selectCameraName, - }} - ns="views/settings" - /> - ) - ) : ( - - )} -
-
- )} - /> -
- - -
- - -
- - - - ) : ( - <> -
- -
-
- -
- - )} -
-
- - setShowWizard(false)} - /> - - ); -}