import { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { Button } from "@/components/ui/button"; import { LuTriangleAlert } from "react-icons/lu"; import FilterSwitch from "@/components/filter/FilterSwitch"; import ActivityIndicator from "@/components/indicators/activity-indicator"; import { ImportSummary, TransferSection, UiSettingsFile, } from "@/utils/uiSettingsTransfer"; type ImportUiSettingsDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; fileName: string; file: UiSettingsFile; summary: ImportSummary; onConfirm: (sections: Record) => Promise; }; export default function ImportUiSettingsDialog({ open, onOpenChange, fileName, file, summary, onConfirm, }: ImportUiSettingsDialogProps) { const { t } = useTranslation(["views/settings", "common"]); const [isImporting, setIsImporting] = useState(false); const available = useMemo( () => ({ layouts: summary.layoutGroupCount > 0, streaming: summary.streamingCameraCount > 0, preferences: summary.preferenceCount > 0, }), [summary], ); const [sections, setSections] = useState>(available); // a new file can be chosen while this component stays mounted, so the // toggles reset to what the current file actually offers each time it // opens useEffect(() => { if (open) { setSections(available); setIsImporting(false); } }, [open, available]); const canImport = useMemo( () => !isImporting && (Object.keys(sections) as TransferSection[]).some( (section) => sections[section] && available[section], ), [isImporting, sections, available], ); // warn only about what the enabled sections will actually write const visibleUnknownGroups = useMemo(() => { const groups = new Set(); if (sections.layouts) { summary.unknownLayoutGroups.forEach((group) => groups.add(group)); } if (sections.streaming) { summary.unknownStreamingGroups.forEach((group) => groups.add(group)); } return Array.from(groups).sort(); }, [sections, summary]); const visibleUnknownCameras = useMemo( () => (sections.streaming ? summary.unknownCameras : []), [sections.streaming, summary.unknownCameras], ); const handleConfirm = useCallback(async () => { setIsImporting(true); await onConfirm(sections); setIsImporting(false); }, [onConfirm, sections]); const exportedDate = useMemo(() => { const parsed = new Date(file.exported_at); return isNaN(parsed.getTime()) ? file.exported_at : parsed.toLocaleString(); }, [file.exported_at]); return ( {t("general.backupRestore.importDialog.title")} {t("general.backupRestore.importDialog.desc")}

{fileName}

{t("general.backupRestore.importDialog.exportedFrom", { date: exportedDate, version: file.frigate_version, })}

setSections((prev) => ({ ...prev, layouts: checked })) } /> setSections((prev) => ({ ...prev, streaming: checked })) } /> setSections((prev) => ({ ...prev, preferences: checked })) } />
{(visibleUnknownGroups.length > 0 || visibleUnknownCameras.length > 0) && ( {visibleUnknownGroups.length > 0 && (

{t("general.backupRestore.importDialog.unknownGroups", { count: visibleUnknownGroups.length, groups: visibleUnknownGroups.join(", "), })}

)} {visibleUnknownCameras.length > 0 && (

{t("general.backupRestore.importDialog.unknownCameras", { count: visibleUnknownCameras.length, cameras: visibleUnknownCameras.join(", "), })}

)}
)}
); }