mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-17 05:38:25 +03:00
disable save all when any section is invalid
This commit is contained in:
parent
3b5f84c601
commit
49f4bc48b6
@ -121,6 +121,7 @@ export interface BaseSectionProps {
|
|||||||
onStatusChange?: (status: {
|
onStatusChange?: (status: {
|
||||||
hasChanges: boolean;
|
hasChanges: boolean;
|
||||||
isOverridden: boolean;
|
isOverridden: boolean;
|
||||||
|
hasValidationErrors: boolean;
|
||||||
}) => void;
|
}) => void;
|
||||||
/** Pending form data keyed by "sectionKey" or "cameraName::sectionKey" */
|
/** Pending form data keyed by "sectionKey" or "cameraName::sectionKey" */
|
||||||
pendingDataBySection?: Record<string, unknown>;
|
pendingDataBySection?: Record<string, unknown>;
|
||||||
@ -371,8 +372,8 @@ export function ConfigSection({
|
|||||||
}, [formData, pendingData, extraHasChanges]);
|
}, [formData, pendingData, extraHasChanges]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onStatusChange?.({ hasChanges, isOverridden });
|
onStatusChange?.({ hasChanges, isOverridden, hasValidationErrors });
|
||||||
}, [hasChanges, isOverridden, onStatusChange]);
|
}, [hasChanges, isOverridden, hasValidationErrors, onStatusChange]);
|
||||||
|
|
||||||
// Handle form data change
|
// Handle form data change
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
|
|||||||
@ -668,6 +668,13 @@ export default function Settings() {
|
|||||||
const { data: fullSchema } = useSWR<RJSFSchema>("config/schema.json");
|
const { data: fullSchema } = useSWR<RJSFSchema>("config/schema.json");
|
||||||
|
|
||||||
const hasPendingChanges = Object.keys(pendingDataBySection).length > 0;
|
const hasPendingChanges = Object.keys(pendingDataBySection).length > 0;
|
||||||
|
const hasPendingValidationErrors = useMemo(
|
||||||
|
() =>
|
||||||
|
Object.values(sectionStatusByKey).some(
|
||||||
|
(status) => !!status && status.hasChanges && status.hasValidationErrors,
|
||||||
|
),
|
||||||
|
[sectionStatusByKey],
|
||||||
|
);
|
||||||
const pendingChangesPreview = useMemo<SaveAllPreviewItem[]>(() => {
|
const pendingChangesPreview = useMemo<SaveAllPreviewItem[]>(() => {
|
||||||
if (!config || !fullSchema) return [];
|
if (!config || !fullSchema) return [];
|
||||||
|
|
||||||
@ -734,7 +741,13 @@ export default function Settings() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const handleSaveAll = useCallback(async () => {
|
const handleSaveAll = useCallback(async () => {
|
||||||
if (!config || !fullSchema || !hasPendingChanges) return;
|
if (
|
||||||
|
!config ||
|
||||||
|
!fullSchema ||
|
||||||
|
!hasPendingChanges ||
|
||||||
|
hasPendingValidationErrors
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
setIsSavingAll(true);
|
setIsSavingAll(true);
|
||||||
let successCount = 0;
|
let successCount = 0;
|
||||||
@ -812,6 +825,7 @@ export default function Settings() {
|
|||||||
updated[menuKey] = {
|
updated[menuKey] = {
|
||||||
...updated[menuKey],
|
...updated[menuKey],
|
||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
|
hasValidationErrors: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -865,6 +879,7 @@ export default function Settings() {
|
|||||||
config,
|
config,
|
||||||
fullSchema,
|
fullSchema,
|
||||||
hasPendingChanges,
|
hasPendingChanges,
|
||||||
|
hasPendingValidationErrors,
|
||||||
pendingDataBySection,
|
pendingDataBySection,
|
||||||
pendingKeyToMenuKey,
|
pendingKeyToMenuKey,
|
||||||
t,
|
t,
|
||||||
@ -885,6 +900,7 @@ export default function Settings() {
|
|||||||
updated[menuKey] = {
|
updated[menuKey] = {
|
||||||
...updated[menuKey],
|
...updated[menuKey],
|
||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
|
hasValidationErrors: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1011,7 +1027,9 @@ export default function Settings() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!selectedCamera || !cameraOverrides) return;
|
if (!selectedCamera || !cameraOverrides) return;
|
||||||
|
|
||||||
const overrideMap: Partial<Record<SettingsType, SectionStatus>> = {};
|
const overrideMap: Partial<
|
||||||
|
Record<SettingsType, Pick<SectionStatus, "hasChanges" | "isOverridden">>
|
||||||
|
> = {};
|
||||||
|
|
||||||
// Set override status for all camera sections using the shared mapping
|
// Set override status for all camera sections using the shared mapping
|
||||||
Object.entries(CAMERA_SECTION_MAPPING).forEach(
|
Object.entries(CAMERA_SECTION_MAPPING).forEach(
|
||||||
@ -1031,7 +1049,12 @@ export default function Settings() {
|
|||||||
// Merge and update both hasChanges and isOverridden for camera sections
|
// Merge and update both hasChanges and isOverridden for camera sections
|
||||||
const merged = { ...prev };
|
const merged = { ...prev };
|
||||||
Object.entries(overrideMap).forEach(([key, status]) => {
|
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;
|
return merged;
|
||||||
});
|
});
|
||||||
@ -1164,7 +1187,7 @@ export default function Settings() {
|
|||||||
onClick={handleSaveAll}
|
onClick={handleSaveAll}
|
||||||
variant="select"
|
variant="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
disabled={isSavingAll}
|
disabled={isSavingAll || hasPendingValidationErrors}
|
||||||
className="flex w-full items-center justify-center gap-2"
|
className="flex w-full items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{isSavingAll ? (
|
{isSavingAll ? (
|
||||||
@ -1306,7 +1329,7 @@ export default function Settings() {
|
|||||||
variant="select"
|
variant="select"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleSaveAll}
|
onClick={handleSaveAll}
|
||||||
disabled={isSavingAll}
|
disabled={isSavingAll || hasPendingValidationErrors}
|
||||||
className="flex items-center justify-center gap-2"
|
className="flex items-center justify-center gap-2"
|
||||||
>
|
>
|
||||||
{isSavingAll ? (
|
{isSavingAll ? (
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export type SettingsPageProps = {
|
|||||||
export type SectionStatus = {
|
export type SectionStatus = {
|
||||||
hasChanges: boolean;
|
hasChanges: boolean;
|
||||||
isOverridden: boolean;
|
isOverridden: boolean;
|
||||||
|
hasValidationErrors: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SingleSectionPageOptions = {
|
export type SingleSectionPageOptions = {
|
||||||
@ -67,6 +68,7 @@ export function SingleSectionPage({
|
|||||||
const [sectionStatus, setSectionStatus] = useState<SectionStatus>({
|
const [sectionStatus, setSectionStatus] = useState<SectionStatus>({
|
||||||
hasChanges: false,
|
hasChanges: false,
|
||||||
isOverridden: false,
|
isOverridden: false,
|
||||||
|
hasValidationErrors: false,
|
||||||
});
|
});
|
||||||
const resolvedSectionConfig = useMemo(
|
const resolvedSectionConfig = useMemo(
|
||||||
() => sectionConfig ?? getSectionConfig(sectionKey, level),
|
() => sectionConfig ?? getSectionConfig(sectionKey, level),
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user