import { useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { isDesktop } from "react-device-detect"; import { FaCalendarAlt } from "react-icons/fa"; import { Button } from "@/components/ui/button"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Input } from "@/components/ui/input"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; import { Label } from "@/components/ui/label"; import { Calendar } from "@/components/ui/calendar"; import { FrigateConfig } from "@/types/frigateConfig"; import { getUTCOffset } from "@/utils/dateUtil"; type CustomSuspensionDialogProps = { open: boolean; onOpenChange: (open: boolean) => void; onConfirm: (minutes: number) => void; config?: FrigateConfig; }; export default function CustomSuspensionDialog({ open, onOpenChange, onConfirm, config, }: CustomSuspensionDialogProps) { const { t } = useTranslation(["views/settings"]); const [tab, setTab] = useState<"duration" | "untilTime">("duration"); // duration tab state const [hours, setHours] = useState(1); const [minutes, setMinutes] = useState(0); // until-time tab state — epoch seconds in UI-timezone-adjusted frame, // matching the pattern used by CustomTimeSelector. const timezoneOffset = useMemo( () => config?.ui.timezone ? Math.round(getUTCOffset(new Date(), config.ui.timezone)) : undefined, [config?.ui.timezone], ); const localTimeOffset = useMemo( () => Math.round( getUTCOffset( new Date(), Intl.DateTimeFormat().resolvedOptions().timeZone, ), ), [], ); const initialUntilEpoch = () => { let epoch = Math.floor(Date.now() / 1000) + 3600; if (timezoneOffset !== undefined) { epoch = epoch + (timezoneOffset - localTimeOffset) * 60; } return epoch; }; const [untilEpoch, setUntilEpoch] = useState(initialUntilEpoch); const [calendarOpen, setCalendarOpen] = useState(false); useEffect(() => { if (open) { setTab("duration"); setHours(1); setMinutes(0); setUntilEpoch(initialUntilEpoch()); } // eslint-disable-next-line react-hooks/exhaustive-deps }, [open]); const clockText = useMemo(() => { const date = new Date(untilEpoch * 1000); return `${date.getHours().toString().padStart(2, "0")}:${date .getMinutes() .toString() .padStart(2, "0")}`; }, [untilEpoch]); const dateText = useMemo(() => { const date = new Date(untilEpoch * 1000); return date.toLocaleDateString(undefined, { year: "numeric", month: "short", day: "numeric", }); }, [untilEpoch]); const totalMinutes = useMemo(() => { if (tab === "duration") { return Math.max(0, Math.floor(hours) * 60 + Math.floor(minutes)); } // until-time: undo the TZ shift to get the real target epoch, then diff now. let realEpoch = untilEpoch; if (timezoneOffset !== undefined) { realEpoch = untilEpoch - (timezoneOffset - localTimeOffset) * 60; } const nowEpoch = Math.floor(Date.now() / 1000); return Math.ceil((realEpoch - nowEpoch) / 60); }, [tab, hours, minutes, untilEpoch, timezoneOffset, localTimeOffset]); const canApply = totalMinutes > 0; const handleApply = () => { if (!canApply) return; onConfirm(totalMinutes); onOpenChange(false); }; return ( {t("notification.customSuspension.title")} {t("notification.customSuspension.description")} setTab(v as "duration" | "untilTime")} > {t("notification.customSuspension.tabDuration")} {t("notification.customSuspension.tabUntilTime")}
{ const n = Number.parseInt(e.target.value, 10); setHours(Number.isNaN(n) ? 0 : Math.max(0, n)); }} />
{ const n = Number.parseInt(e.target.value, 10); setMinutes( Number.isNaN(n) ? 0 : Math.min(59, Math.max(0, n)), ); }} />
{ if (!day) return; const current = new Date(untilEpoch * 1000); const next = new Date(day); next.setHours( current.getHours(), current.getMinutes(), current.getSeconds(), 0, ); setUntilEpoch(Math.floor(next.getTime() / 1000)); setCalendarOpen(false); }} /> { const [h, m] = e.target.value.split(":"); const next = new Date(untilEpoch * 1000); next.setHours( Number.parseInt(h ?? "0", 10), Number.parseInt(m ?? "0", 10), 0, 0, ); setUntilEpoch(Math.floor(next.getTime() / 1000)); }} />
{!canApply && (

{t("notification.customSuspension.invalidTime")}

)}
); }