import type { ReportKind } from './shared'; import { useMemo, useState } from 'react'; import { BarChart3, Download, ExternalLink, Loader2 } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { EmptyState } from './components'; import { REPORT_KINDS } from './shared'; import { downloadPdf, useReportUrl } from './useInvoiceShelfData'; // Reports. Every one of these is a server-rendered PDF — InvoiceShelf builds them in Blade and there is no // JSON equivalent, so this screen is a range picker and a viewer, which is exactly what upstream's is. // // from_date and to_date are MANDATORY, not optional filters: each controller does // `Carbon::createFromFormat('Y-m-d', $request->from_date)` unguarded, so omitting either is a 500 rather than // an unbounded report. The range therefore always has a value here and the presets always resolve to a pair. type PresetId = | 'today' | 'this-week' | 'this-month' | 'this-quarter' | 'this-year' | 'previous-week' | 'previous-month' | 'previous-quarter' | 'previous-year' | 'custom'; const PRESETS: { id: PresetId; label: string }[] = [ { id: 'today', label: 'Today' }, { id: 'this-week', label: 'This week' }, { id: 'this-month', label: 'This month' }, { id: 'this-quarter', label: 'This quarter' }, { id: 'this-year', label: 'This year' }, { id: 'previous-week', label: 'Previous week' }, { id: 'previous-month', label: 'Previous month' }, { id: 'previous-quarter', label: 'Previous quarter' }, { id: 'previous-year', label: 'Previous year' }, { id: 'custom', label: 'Custom' }, ]; const iso = (d: Date): string => { const pad = (n: number) => String(n).padStart(2, '0'); return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`; }; /** Monday-start, matching Carbon's default `startOfWeek()` on this instance. */ const startOfWeek = (d: Date): Date => { const out = new Date(d); const offset = (out.getDay() + 6) % 7; out.setDate(out.getDate() - offset); return out; }; function resolvePreset(id: PresetId): { from: string; to: string } | null { if (id === 'custom') return null; const now = new Date(); const y = now.getFullYear(); const q = Math.floor(now.getMonth() / 3); switch (id) { case 'today': return { from: iso(now), to: iso(now) }; case 'this-week': { const start = startOfWeek(now); const end = new Date(start); end.setDate(end.getDate() + 6); return { from: iso(start), to: iso(end) }; } case 'previous-week': { const start = startOfWeek(now); start.setDate(start.getDate() - 7); const end = new Date(start); end.setDate(end.getDate() + 6); return { from: iso(start), to: iso(end) }; } case 'this-month': return { from: iso(new Date(y, now.getMonth(), 1)), to: iso(new Date(y, now.getMonth() + 1, 0)) }; case 'previous-month': return { from: iso(new Date(y, now.getMonth() - 1, 1)), to: iso(new Date(y, now.getMonth(), 0)) }; case 'this-quarter': return { from: iso(new Date(y, q * 3, 1)), to: iso(new Date(y, q * 3 + 3, 0)) }; case 'previous-quarter': return { from: iso(new Date(y, q * 3 - 3, 1)), to: iso(new Date(y, q * 3, 0)) }; case 'this-year': return { from: iso(new Date(y, 0, 1)), to: iso(new Date(y, 11, 31)) }; case 'previous-year': return { from: iso(new Date(y - 1, 0, 1)), to: iso(new Date(y - 1, 11, 31)) }; default: return null; } } export const ReportsView = () => { const [kind, setKind] = useState('sales-customers'); const [preset, setPreset] = useState('this-month'); // Seeded from the default preset so the first render already has a valid range to request. const [custom, setCustom] = useState(() => resolvePreset('this-month') ?? { from: '', to: '' }); const range = useMemo(() => resolvePreset(preset) ?? custom, [preset, custom]); const ready = Boolean(range.from && range.to); const { url, isLoading, error } = useReportUrl(kind, { from_date: range.from, to_date: range.to }, ready); const label = REPORT_KINDS.find((r) => r.id === kind)?.label ?? 'Report'; return (
{REPORT_KINDS.map((r) => ( ))}
{!ready && ( )} {ready && isLoading && (
Building {label.toLowerCase()}…
)} {ready && error && !isLoading && (
Could not build the report
{error}
)} {ready && url && !isLoading &&