frigate/web/src/views/settings/SingleSectionPage.tsx

113 lines
3.4 KiB
TypeScript
Raw Normal View History

import { useEffect, useState } from "react";
2026-01-31 21:35:23 +03:00
import { useTranslation } from "react-i18next";
import Heading from "@/components/ui/heading";
import type { SectionConfig } from "@/components/config-form/sections";
import { ConfigSectionTemplate } from "@/components/config-form/sections";
2026-01-31 21:35:23 +03:00
import type { PolygonType } from "@/types/canvas";
import { Badge } from "@/components/ui/badge";
2026-01-31 21:35:23 +03:00
export type SettingsPageProps = {
selectedCamera?: string;
setUnsavedChanges?: React.Dispatch<React.SetStateAction<boolean>>;
selectedZoneMask?: PolygonType[];
onSectionStatusChange?: (
sectionKey: string,
level: "global" | "camera",
status: SectionStatus,
) => void;
};
export type SectionStatus = {
hasChanges: boolean;
isOverridden: boolean;
2026-01-31 21:35:23 +03:00
};
export type SingleSectionPageOptions = {
sectionKey: string;
level: "global" | "camera";
sectionConfig?: SectionConfig;
requiresRestart?: boolean;
showOverrideIndicator?: boolean;
};
2026-02-01 21:44:54 +03:00
export type SingleSectionPageProps = SettingsPageProps &
SingleSectionPageOptions;
export function SingleSectionPage({
2026-01-31 21:35:23 +03:00
sectionKey,
level,
sectionConfig,
requiresRestart,
showOverrideIndicator = true,
2026-02-01 21:44:54 +03:00
selectedCamera,
setUnsavedChanges,
onSectionStatusChange,
2026-02-01 21:44:54 +03:00
}: SingleSectionPageProps) {
const sectionNamespace =
level === "camera" ? "config/cameras" : "config/global";
const { t, i18n } = useTranslation([
sectionNamespace,
"views/settings",
"common",
]);
const [sectionStatus, setSectionStatus] = useState<SectionStatus>({
hasChanges: false,
isOverridden: false,
});
useEffect(() => {
onSectionStatusChange?.(sectionKey, level, sectionStatus);
}, [onSectionStatusChange, sectionKey, level, sectionStatus]);
2026-01-31 21:35:23 +03:00
2026-02-01 21:44:54 +03:00
if (level === "camera" && !selectedCamera) {
2026-01-31 21:35:23 +03:00
return (
2026-02-01 21:44:54 +03:00
<div className="flex h-full items-center justify-center text-muted-foreground">
{t("configForm.camera.noCameras", { ns: "views/settings" })}
2026-01-31 21:35:23 +03:00
</div>
);
2026-02-01 21:44:54 +03:00
}
return (
<div className="flex size-full flex-col pr-2">
<div className="space-y-4">
2026-02-01 21:44:54 +03:00
<Heading as="h3">
{t(`${sectionKey}.label`, { ns: sectionNamespace })}
</Heading>
2026-02-01 21:44:54 +03:00
{i18n.exists(`${sectionKey}.description`, {
ns: sectionNamespace,
}) && (
<p className="text-sm text-muted-foreground">
{t(`${sectionKey}.description`, { ns: sectionNamespace })}
</p>
)}
<div className="flex flex-wrap items-center gap-2">
{level === "camera" &&
showOverrideIndicator &&
sectionStatus.isOverridden && (
<Badge variant="secondary" className="text-xs">
{t("overridden", { ns: "common", defaultValue: "Overridden" })}
</Badge>
)}
{sectionStatus.hasChanges && (
<Badge variant="outline" className="text-xs">
{t("modified", { ns: "common", defaultValue: "Modified" })}
</Badge>
)}
</div>
2026-02-01 21:44:54 +03:00
</div>
<ConfigSectionTemplate
sectionKey={sectionKey}
level={level}
cameraName={level === "camera" ? selectedCamera : undefined}
showOverrideIndicator={showOverrideIndicator}
onSave={() => setUnsavedChanges?.(false)}
showTitle={false}
sectionConfig={sectionConfig}
requiresRestart={requiresRestart}
onStatusChange={setSectionStatus}
2026-02-01 21:44:54 +03:00
/>
</div>
);
2026-01-31 21:35:23 +03:00
}