"use client" import { useCallback, useMemo, useState } from "react" import { format } from "date-fns" import { ru } from "date-fns/locale" import { CalendarIcon } from "lucide-react" import { DateSelector, type DateSelectorValue, } from "@/components/reui/date-selector" import { DATE_SELECTOR_RU } from "@/components/statistics/date-selector-i18n" import { Button } from "@/components/ui/button" import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover" /** * Период отчёта — DateSelector в Popover (c-date-selector-2). * @see https://reui.io/preview/base/components/c-date-selector-2 * @see https://reui.io/docs/components/base/date-selector */ export type PeriodPreset = "today" | "24h" | "7d" | "30d" | "month" export interface DateRangeYmd { from: string to: string } const PRESETS: { id: PeriodPreset; label: string }[] = [ { id: "today", label: "Сегодня" }, { id: "24h", label: "24 ч" }, { id: "7d", label: "7 д" }, { id: "30d", label: "30 д" }, { id: "month", label: "Месяц" }, ] function pad2(n: number): string { return String(n).padStart(2, "0") } export function formatYmd(d: Date): string { return `${d.getFullYear()}-${pad2(d.getMonth() + 1)}-${pad2(d.getDate())}` } export function parseYmd(s: string): Date | null { const m = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s) if (!m) return null const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3])) return Number.isNaN(d.getTime()) ? null : d } /** Значение периода — дата (YYYY-MM-DD) или полный ISO-таймстамп (пресет «24 ч»). */ export function parseRangeDate(s: string): Date | null { if (s.includes("T")) { const d = new Date(s) return Number.isNaN(d.getTime()) ? null : d } return parseYmd(s) } export function rangeForPreset(preset: PeriodPreset, now = new Date()): DateRangeYmd { const to = formatYmd(now) if (preset === "today") return { from: to, to } if (preset === "24h") { // Последние 24 часа от сейчас: полный ISO, иначе date-only «вчера…сегодня» даёт до ~48 ч. return { from: new Date(now.getTime() - 24 * 3600_000).toISOString(), to: now.toISOString(), } } if (preset === "7d") { const from = new Date(now) from.setDate(from.getDate() - 6) return { from: formatYmd(from), to } } if (preset === "30d") { const from = new Date(now) from.setDate(from.getDate() - 29) return { from: formatYmd(from), to } } const start = new Date(now.getFullYear(), now.getMonth(), 1) return { from: formatYmd(start), to } } export function dateSelectorToRange(value: DateSelectorValue): DateRangeYmd | null { if (value.period === "day") { if (value.operator === "between") { if (!value.startDate || !value.endDate) return null const a = formatYmd(value.startDate) const b = formatYmd(value.endDate) return a <= b ? { from: a, to: b } : { from: b, to: a } } if (value.startDate) { const d = formatYmd(value.startDate) return { from: d, to: d } } } if (value.period === "month" && value.year != null && value.month != null) { const start = new Date(value.year, value.month, 1) const end = new Date(value.year, value.month + 1, 0) return { from: formatYmd(start), to: formatYmd(end) } } if (value.period === "year" && value.year != null) { return { from: `${value.year}-01-01`, to: `${value.year}-12-31` } } if (value.period === "quarter" && value.year != null && value.quarter != null) { const startMonth = value.quarter * 3 const start = new Date(value.year, startMonth, 1) const end = new Date(value.year, startMonth + 3, 0) return { from: formatYmd(start), to: formatYmd(end) } } if (value.period === "half-year" && value.year != null && value.halfYear != null) { const startMonth = value.halfYear * 6 const start = new Date(value.year, startMonth, 1) const end = new Date(value.year, startMonth + 6, 0) return { from: formatYmd(start), to: formatYmd(end) } } return null } export function rangeToSelector(range: DateRangeYmd): DateSelectorValue { return { period: "day", operator: "between", startDate: parseRangeDate(range.from) ?? undefined, endDate: parseRangeDate(range.to) ?? undefined, } } function formatRangeLabel(range: DateRangeYmd): string { const from = parseRangeDate(range.from) const to = parseRangeDate(range.to) if (!from || !to) return "Период" if (range.from.includes("T") || range.to.includes("T")) { return formatYmd(from) === formatYmd(to) ? `${format(from, "d MMM yyyy HH:mm", { locale: ru })} – ${format(to, "HH:mm", { locale: ru })}` : `${format(from, "d MMM HH:mm", { locale: ru })} – ${format(to, "d MMM HH:mm", { locale: ru })}` } if (range.from === range.to) return format(from, "d MMM yyyy", { locale: ru }) return `${format(from, "d MMM", { locale: ru })} – ${format(to, "d MMM yyyy", { locale: ru })}` } export function PeriodSelector({ range, onChange, }: { range: DateRangeYmd onChange: (next: DateRangeYmd) => void }) { const [open, setOpen] = useState(false) const selectorValue = useMemo(() => rangeToSelector(range), [range]) const handleSelectorChange = useCallback( (value: DateSelectorValue) => { const next = dateSelectorToRange(value) if (!next) return if (next.from === range.from && next.to === range.to) return onChange(next) setOpen(false) }, [onChange, range.from, range.to], ) const activePreset = PRESETS.find((p) => { // «24 ч» хранится ISO-таймстампами: сверяем разбором (длительность окна), а не строками. if (p.id === "24h") { const f = parseRangeDate(range.from) const t = parseRangeDate(range.to) return Boolean(f && t && Math.abs((t.getTime() - f.getTime()) - 24 * 3600_000) < 60_000) } const r = rangeForPreset(p.id) return r.from === range.from && r.to === range.to })?.id return (
{PRESETS.map((p) => ( ))} } > {formatRangeLabel(range)}
) }