2026-02-02 00:30:46 +03:00
|
|
|
import { useEffect, useState } from "react";
|
2026-01-31 21:35:23 +03:00
|
|
|
import { useTranslation } from "react-i18next";
|
|
|
|
|
import Heading from "@/components/ui/heading";
|
2026-02-01 20:35:04 +03:00
|
|
|
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";
|
2026-02-02 00:30:46 +03:00
|
|
|
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[];
|
2026-02-02 00:30:46 +03:00
|
|
|
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,
|
2026-02-02 00:30:46 +03:00
|
|
|
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",
|
|
|
|
|
]);
|
2026-02-02 00:30:46 +03:00
|
|
|
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">
|
2026-02-02 00:30:46 +03:00
|
|
|
<div className="space-y-4">
|
2026-02-01 21:44:54 +03:00
|
|
|
<Heading as="h3">
|
|
|
|
|
{t(`${sectionKey}.label`, { ns: sectionNamespace })}
|
|
|
|
|
</Heading>
|
2026-02-02 00:30:46 +03:00
|
|
|
|
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>
|
|
|
|
|
)}
|
2026-02-02 00:30:46 +03:00
|
|
|
<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}
|
2026-02-02 00:30:46 +03:00
|
|
|
onStatusChange={setSectionStatus}
|
2026-02-01 21:44:54 +03:00
|
|
|
/>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
2026-01-31 21:35:23 +03:00
|
|
|
}
|