From 49f4bc48b68ba9699a40852c0a5f85d1a2ca10ba Mon Sep 17 00:00:00 2001 From: Josh Hawkins <32435876+hawkeye217@users.noreply.github.com> Date: Fri, 27 Feb 2026 08:55:55 -0600 Subject: [PATCH] disable save all when any section is invalid --- .../config-form/sections/BaseSection.tsx | 5 +-- web/src/pages/Settings.tsx | 33 ++++++++++++++++--- web/src/views/settings/SingleSectionPage.tsx | 2 ++ 3 files changed, 33 insertions(+), 7 deletions(-) diff --git a/web/src/components/config-form/sections/BaseSection.tsx b/web/src/components/config-form/sections/BaseSection.tsx index 7b845287c..606919dcc 100644 --- a/web/src/components/config-form/sections/BaseSection.tsx +++ b/web/src/components/config-form/sections/BaseSection.tsx @@ -121,6 +121,7 @@ export interface BaseSectionProps { onStatusChange?: (status: { hasChanges: boolean; isOverridden: boolean; + hasValidationErrors: boolean; }) => void; /** Pending form data keyed by "sectionKey" or "cameraName::sectionKey" */ pendingDataBySection?: Record; @@ -371,8 +372,8 @@ export function ConfigSection({ }, [formData, pendingData, extraHasChanges]); useEffect(() => { - onStatusChange?.({ hasChanges, isOverridden }); - }, [hasChanges, isOverridden, onStatusChange]); + onStatusChange?.({ hasChanges, isOverridden, hasValidationErrors }); + }, [hasChanges, isOverridden, hasValidationErrors, onStatusChange]); // Handle form data change const handleChange = useCallback( diff --git a/web/src/pages/Settings.tsx b/web/src/pages/Settings.tsx index 9e4d06c39..f92f6ff94 100644 --- a/web/src/pages/Settings.tsx +++ b/web/src/pages/Settings.tsx @@ -668,6 +668,13 @@ export default function Settings() { const { data: fullSchema } = useSWR("config/schema.json"); const hasPendingChanges = Object.keys(pendingDataBySection).length > 0; + const hasPendingValidationErrors = useMemo( + () => + Object.values(sectionStatusByKey).some( + (status) => !!status && status.hasChanges && status.hasValidationErrors, + ), + [sectionStatusByKey], + ); const pendingChangesPreview = useMemo(() => { if (!config || !fullSchema) return []; @@ -734,7 +741,13 @@ export default function Settings() { ); const handleSaveAll = useCallback(async () => { - if (!config || !fullSchema || !hasPendingChanges) return; + if ( + !config || + !fullSchema || + !hasPendingChanges || + hasPendingValidationErrors + ) + return; setIsSavingAll(true); let successCount = 0; @@ -812,6 +825,7 @@ export default function Settings() { updated[menuKey] = { ...updated[menuKey], hasChanges: false, + hasValidationErrors: false, }; } } @@ -865,6 +879,7 @@ export default function Settings() { config, fullSchema, hasPendingChanges, + hasPendingValidationErrors, pendingDataBySection, pendingKeyToMenuKey, t, @@ -885,6 +900,7 @@ export default function Settings() { updated[menuKey] = { ...updated[menuKey], hasChanges: false, + hasValidationErrors: false, }; } } @@ -1011,7 +1027,9 @@ export default function Settings() { useEffect(() => { if (!selectedCamera || !cameraOverrides) return; - const overrideMap: Partial> = {}; + const overrideMap: Partial< + Record> + > = {}; // Set override status for all camera sections using the shared mapping Object.entries(CAMERA_SECTION_MAPPING).forEach( @@ -1031,7 +1049,12 @@ export default function Settings() { // Merge and update both hasChanges and isOverridden for camera sections const merged = { ...prev }; Object.entries(overrideMap).forEach(([key, status]) => { - merged[key as SettingsType] = status; + const existingStatus = merged[key as SettingsType]; + merged[key as SettingsType] = { + hasChanges: status.hasChanges, + isOverridden: status.isOverridden, + hasValidationErrors: existingStatus?.hasValidationErrors ?? false, + }; }); return merged; }); @@ -1164,7 +1187,7 @@ export default function Settings() { onClick={handleSaveAll} variant="select" size="sm" - disabled={isSavingAll} + disabled={isSavingAll || hasPendingValidationErrors} className="flex w-full items-center justify-center gap-2" > {isSavingAll ? ( @@ -1306,7 +1329,7 @@ export default function Settings() { variant="select" size="sm" onClick={handleSaveAll} - disabled={isSavingAll} + disabled={isSavingAll || hasPendingValidationErrors} className="flex items-center justify-center gap-2" > {isSavingAll ? ( diff --git a/web/src/views/settings/SingleSectionPage.tsx b/web/src/views/settings/SingleSectionPage.tsx index 2e88a9646..c1e7752f7 100644 --- a/web/src/views/settings/SingleSectionPage.tsx +++ b/web/src/views/settings/SingleSectionPage.tsx @@ -31,6 +31,7 @@ export type SettingsPageProps = { export type SectionStatus = { hasChanges: boolean; isOverridden: boolean; + hasValidationErrors: boolean; }; export type SingleSectionPageOptions = { @@ -67,6 +68,7 @@ export function SingleSectionPage({ const [sectionStatus, setSectionStatus] = useState({ hasChanges: false, isOverridden: false, + hasValidationErrors: false, }); const resolvedSectionConfig = useMemo( () => sectionConfig ?? getSectionConfig(sectionKey, level),