From 635409cf2310d1f03ed82280e59634e68d56001a Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Sat, 16 May 2026 07:17:12 -0500 Subject: [PATCH] Embed detector form in merged settings view --- .../DetectorsAndModelSettingsView.tsx | 58 +++++++++++++++++-- 1 file changed, 53 insertions(+), 5 deletions(-) diff --git a/web/src/views/settings/DetectorsAndModelSettingsView.tsx b/web/src/views/settings/DetectorsAndModelSettingsView.tsx index 4a20d950fa..82bc0da1bc 100644 --- a/web/src/views/settings/DetectorsAndModelSettingsView.tsx +++ b/web/src/views/settings/DetectorsAndModelSettingsView.tsx @@ -13,7 +13,10 @@ import { Button } from "@/components/ui/button"; import { Toaster } from "@/components/ui/sonner"; import type { FrigateConfig } from "@/types/frigateConfig"; import type { SettingsPageProps } from "@/views/settings/SingleSectionPage"; +import type { SectionStatus } from "@/views/settings/SingleSectionPage"; import type { ConfigSectionData } from "@/types/configForm"; +import { SettingsGroupCard } from "@/components/card/SettingsGroupCard"; +import { ConfigSectionTemplate } from "@/components/config-form/sections"; type ModelTab = "plus" | "custom"; @@ -65,6 +68,42 @@ export default function DetectorsAndModelSettingsView( const [snapshot, setSnapshot] = useState(null); const [state, setState] = useState(null); const [isSaving, setIsSaving] = useState(false); + const [childPending, setChildPending] = useState< + Record + >({}); + const [detectorStatus, setDetectorStatus] = useState({ + hasChanges: false, + isOverridden: false, + hasValidationErrors: false, + }); + + const handleChildPendingChange = useCallback( + ( + sectionKey: string, + _cameraName: string | undefined, + data: ConfigSectionData | null, + ) => { + setChildPending((prev) => { + if (data === null) { + if (!(sectionKey in prev)) return prev; + const { [sectionKey]: _drop, ...rest } = prev; + return rest; + } + return { ...prev, [sectionKey]: data }; + }); + }, + [], + ); + + useEffect(() => { + const detectorsPending = childPending["detectors"]; + if (detectorsPending && state) { + setState((prev) => + prev ? { ...prev, detectors: detectorsPending } : prev, + ); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [childPending["detectors"]]); useEffect(() => { if (!config || snapshot !== null) return; @@ -110,7 +149,8 @@ export default function DetectorsAndModelSettingsView( return ; } - const saveDisabled = !isDirty || isSaving; + const saveDisabled = + !isDirty || isSaving || detectorStatus.hasValidationErrors; return (
@@ -145,10 +185,18 @@ export default function DetectorsAndModelSettingsView(
-
- {t("detectorsAndModel.cardTitles.detector")} — placeholder, filled - in Task 5. -
+ + setDetectorStatus(status)} + /> +
{t("detectorsAndModel.cardTitles.model")} — placeholder, filled in Tasks 6–8.