import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { Link } from "react-router-dom"; import { Trans, useTranslation } from "react-i18next"; import cloneDeep from "lodash/cloneDeep"; import get from "lodash/get"; import set from "lodash/set"; import { LuExternalLink } from "react-icons/lu"; import { MdCircle } from "react-icons/md"; import Heading from "@/components/ui/heading"; import { Checkbox } from "@/components/ui/checkbox"; import { Label } from "@/components/ui/label"; import { cn } from "@/lib/utils"; import { useDocDomain } from "@/hooks/use-doc-domain"; import { useCameraFriendlyName } from "@/hooks/use-camera-friendly-name"; import { resolveZoneName } from "@/hooks/use-zone-friendly-name"; import { getTranslatedLabel } from "@/utils/i18n"; import { formatList } from "@/utils/stringUtil"; import type { ConfigSectionData, JsonObject } from "@/types/configForm"; import type { SectionRendererProps } from "./registry"; const EMPTY_ZONES: string[] = []; function getRequiredZones( formData: JsonObject | undefined, path: string, ): string[] { const value = get(formData, path); return Array.isArray(value) ? (value as string[]) : EMPTY_ZONES; } export default function CameraReviewClassification({ formContext, selectedCamera, }: SectionRendererProps) { const { t } = useTranslation(["views/settings", "common"]); const { getLocaleDocUrl } = useDocDomain(); const cameraName = formContext?.cameraName ?? selectedCamera; const fullFormData = formContext?.formData as JsonObject | undefined; const cameraConfig = formContext?.fullCameraConfig; const alertsZones = useMemo( () => getRequiredZones(fullFormData, "alerts.required_zones"), [fullFormData], ); const detectionsZones = useMemo( () => getRequiredZones(fullFormData, "detections.required_zones"), [fullFormData], ); const [selectDetections, setSelectDetections] = useState( detectionsZones.length > 0, ); const previousCameraRef = useRef(cameraName); const isSynced = formContext?.hasChanges === false; useEffect(() => { const cameraChanged = previousCameraRef.current !== cameraName; if (cameraChanged) { previousCameraRef.current = cameraName; } if (cameraChanged || isSynced) { setSelectDetections(detectionsZones.length > 0); } }, [cameraName, detectionsZones.length, isSynced]); const zones = useMemo(() => { if (!cameraConfig) { return undefined; } return Object.entries(cameraConfig.zones).map(([name, zoneData]) => { const zone = zoneData as (typeof cameraConfig.zones)[keyof typeof cameraConfig.zones]; return { camera: cameraConfig.name, name, friendly_name: cameraConfig.zones[name].friendly_name, objects: zone.objects, color: zone.color, }; }); }, [cameraConfig]); const alertsLabels = useMemo(() => { return cameraConfig?.review.alerts.labels ? formatList( cameraConfig.review.alerts.labels.map((label: string) => 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: string) => getTranslatedLabel( label, cameraConfig?.audio?.listen?.includes(label) ? "audio" : "object", ), ), ) : ""; }, [cameraConfig]); const selectCameraName = useCameraFriendlyName(cameraName); const getZoneName = useCallback( (zoneId: string, camId?: string) => resolveZoneName(formContext?.fullConfig, zoneId, camId), [formContext?.fullConfig], ); const updateFormData = useCallback( (path: string, nextValue: string[]) => { if (!formContext?.onFormDataChange || !fullFormData) { return; } const nextData = cloneDeep(fullFormData) as JsonObject; set(nextData, path, nextValue); formContext.onFormDataChange(nextData as ConfigSectionData); }, [formContext, fullFormData], ); const handleZoneToggle = useCallback( (path: string, zoneName: string) => { const currentZones = getRequiredZones(fullFormData, path); const nextZones = currentZones.includes(zoneName) ? currentZones.filter((value) => value !== zoneName) : [...currentZones, zoneName]; updateFormData(path, nextZones); }, [fullFormData, updateFormData], ); const handleDetectionsToggle = useCallback( (checked: boolean | string) => { const isChecked = checked === true; if (!isChecked) { updateFormData("detections.required_zones", []); } setSelectDetections(isChecked); }, [updateFormData], ); if (!cameraName || formContext?.level !== "camera") { return null; } return (