import { useCallback, useMemo } from "react"; import { useSearchParams } from "react-router-dom"; import type { DashboardPeriod } from "../shared/types"; import { computeDateRange } from "../utils/dateRange"; const VALID_PERIODS: readonly DashboardPeriod[] = [ "month", "3months", "6months", "year", "12months", "all", "custom", ]; function isValidPeriod(p: string | null): p is DashboardPeriod { return p !== null && (VALID_PERIODS as readonly string[]).includes(p); } function isValidIsoDate(s: string | null): s is string { return !!s && /^\d{4}-\d{2}-\d{2}$/.test(s); } function currentYearRange(today: Date = new Date()): { from: string; to: string } { const year = today.getFullYear(); return { from: `${year}-01-01`, to: `${year}-12-31` }; } /** * Pure resolver used by the hook and unit tests. Exposed to keep the core * logic hookless and testable without rendering a router. */ export function resolveReportsPeriod( rawFrom: string | null, rawTo: string | null, rawPeriod: string | null, today: Date = new Date(), ): { from: string; to: string; period: DashboardPeriod } { if (isValidIsoDate(rawFrom) && isValidIsoDate(rawTo)) { const p = isValidPeriod(rawPeriod) ? rawPeriod : "custom"; return { from: rawFrom, to: rawTo, period: p }; } if (isValidPeriod(rawPeriod) && rawPeriod !== "custom") { const range = computeDateRange(rawPeriod); const { from: defaultFrom, to: defaultTo } = currentYearRange(today); return { from: range.dateFrom ?? defaultFrom, to: range.dateTo ?? defaultTo, period: rawPeriod, }; } const { from, to } = currentYearRange(today); return { from, to, period: "custom" }; } export interface UseReportsPeriodResult { from: string; to: string; period: DashboardPeriod; setPeriod: (period: DashboardPeriod) => void; setCustomDates: (from: string, to: string) => void; } /** * Reads/writes the active reporting period via the URL query string so it is * bookmarkable and shared across the four report sub-routes. * * Defaults to the current civil year (Jan 1 → Dec 31). */ export function useReportsPeriod(): UseReportsPeriodResult { const [searchParams, setSearchParams] = useSearchParams(); const rawPeriod = searchParams.get("period"); const rawFrom = searchParams.get("from"); const rawTo = searchParams.get("to"); const { from, to, period } = useMemo( () => resolveReportsPeriod(rawFrom, rawTo, rawPeriod), [rawPeriod, rawFrom, rawTo], ); const setPeriod = useCallback( (next: DashboardPeriod) => { setSearchParams( (prev) => { const params = new URLSearchParams(prev); if (next === "custom") { params.set("period", "custom"); } else { params.set("period", next); params.delete("from"); params.delete("to"); } return params; }, { replace: true }, ); }, [setSearchParams], ); const setCustomDates = useCallback( (nextFrom: string, nextTo: string) => { setSearchParams( (prev) => { const params = new URLSearchParams(prev); params.set("period", "custom"); params.set("from", nextFrom); params.set("to", nextTo); return params; }, { replace: true }, ); }, [setSearchParams], ); return { from, to, period, setPeriod, setCustomDates }; }