"use client"; /* eslint-disable react-hooks/set-state-in-effect */ import { useState, useCallback, useEffect } from "react"; import { getReportsSummary, getOrdersByStopReport, getSalesByProductReport, getFulfillmentReport, getPickupStatusByStop, getContactGrowthReport, getCampaignActivityReport, } from "@/actions/reports"; import { exportOrdersByStop, exportSalesByProduct, exportPickupStatus, exportContactGrowth, exportFulfillment, exportCampaignActivity, type ReportsSummary, type OrderByStop, type SalesByProduct, type FulfillmentRow, type PickupStatusByStop, type ContactGrowthRow, type CampaignActivityRow, } from "@/lib/reports-export"; import { formatDate, formatDateRange } from "@/lib/date-utils"; import { AdminButton, AdminFilterTabs } from "@/components/admin/design-system"; type DatePreset = "week" | "month" | "quarter" | "this_year" | "custom"; type DateRange = { start: string; end: string }; function buildRange(preset: DatePreset, customStart?: string, customEnd?: string): DateRange { const now = new Date(); const toDateStr = (d: Date) => d.toISOString().slice(0, 10); const today = toDateStr(now); switch (preset) { case "week": { const s = new Date(now); s.setDate(s.getDate() - 6); return { start: toDateStr(s), end: today }; } case "month": { const start = new Date(now.getFullYear(), now.getMonth(), 1); return { start: toDateStr(start), end: today }; } case "quarter": { const q = Math.floor(now.getMonth() / 3); const start = new Date(now.getFullYear(), q * 3, 1); return { start: toDateStr(start), end: today }; } case "this_year": { const start = new Date(now.getFullYear(), 0, 1); return { start: toDateStr(start), end: today }; } case "custom": return { start: customStart ?? today, end: customEnd ?? today }; } } // ── Quarter label helper ────────────────────────────────────────────────────── function quarterLabel(startISO: string): string { const s = new Date(startISO + "T00:00:00"); const q = Math.floor(s.getMonth() / 3) + 1; return `Q${q} ${s.getFullYear()}`; } function monthLabel(startISO: string): string { const s = new Date(startISO + "T00:00:00"); return s.toLocaleDateString("en-US", { month: "long", year: "numeric" }); } // ── Summary card ───────────────────────────────────────────────────────────── function SummaryCard({ label, value, prefix, suffix, }: { label: string; value: number; prefix?: string; suffix?: string; }) { return (

{label}

{prefix ?? ""}{typeof value === "number" ? value.toLocaleString() : value}{suffix ?? ""}

); } // ── CSV export button ─────────────────────────────────────────────────────── function ExportBtn({ label, onClick, }: { label: string; onClick: () => void; }) { return ( Export CSV ); } function ExplainBtn({ onClick, loading, }: { onClick: () => void; loading: boolean; }) { return ( {loading ? "Analyzing..." : "🤖 Explain this Report"} ); } // ── Generic table ─────────────────────────────────────────────────────────── function ReportTable({ headers, rows, renderRow }: { headers: string[]; rows: unknown[]; renderRow: (row: unknown, i: number) => React.ReactNode; }) { return (
{headers.map((h) => ( ))} {rows.length === 0 ? ( ) : ( rows.map((_, i) => renderRow(_, i)) )}
{h}
No data
); } // ── Tab definitions ───────────────────────────────────────────────────────── const TABS = [ { value: "overview", label: "Overview" }, { value: "orders-by-stop", label: "Orders by Stop" }, { value: "sales-by-product", label: "Sales by Product" }, { value: "fulfillment", label: "Fulfillment" }, { value: "pickup-status", label: "Pickup Status" }, { value: "contact-growth", label: "Contact Growth" }, { value: "campaigns", label: "Campaigns" }, ]; // ── Main component ────────────────────────────────────────────────────────── export default function ReportsDashboard({ brands, initialBrandId, isPlatformAdmin, brandId, }: { brands: { id: string; name: string }[]; initialBrandId: string | null; isPlatformAdmin: boolean; brandId?: string | null; }) { const [preset, setPreset] = useState("quarter"); const [customStart, setCustomStart] = useState(""); const [customEnd, setCustomEnd] = useState(""); const [selectedBrandId, setSelectedBrandId] = useState(initialBrandId ?? ""); const [activeTab, setActiveTab] = useState("overview"); const [summary, setSummary] = useState(null); const [ordersByStop, setOrdersByStop] = useState([]); const [salesByProduct, setSalesByProduct] = useState([]); const [fulfillment, setFulfillment] = useState([]); const [pickupStatus, setPickupStatus] = useState([]); const [contactGrowth, setContactGrowth] = useState([]); const [campaigns, setCampaigns] = useState([]); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); const [explainingTab, setExplainingTab] = useState(null); const [explanation, setExplanation] = useState<{ summary: string; keyInsights: string[]; suggestedActions: string[] } | null>(null); const [explainError, setExplainError] = useState(null); const range: DateRange = buildRange(preset, customStart, customEnd); // Stable memoized fetchAll — re-fetches when range or brand changes const fetchAll = useCallback(async () => { setLoading(true); setError(null); const brandId = selectedBrandId || null; try { const [sum, obs, sbp, ful, ps, cg, cam] = await Promise.all([ getReportsSummary(range, selectedBrandId || null), getOrdersByStopReport(range, selectedBrandId || null), getSalesByProductReport(range, selectedBrandId || null), getFulfillmentReport(range, selectedBrandId || null), getPickupStatusByStop(range, selectedBrandId || null), getContactGrowthReport(range, selectedBrandId || null), getCampaignActivityReport(range, selectedBrandId || null), ]); setSummary(sum); setOrdersByStop(obs); setSalesByProduct(sbp); setFulfillment(ful); setPickupStatus(ps); setContactGrowth(cg); setCampaigns(cam); } catch (err) { setError(err instanceof Error ? err.message : "Failed to load reports"); } finally { setLoading(false); } }, [range, selectedBrandId]); const handleFetch = () => { fetchAll(); }; // ── AI Report Explainer ──────────────────────────────────────────────────────── async function handleExplain() { setExplainingTab(activeTab); setExplanation(null); setExplainError(null); const tabDataMap: Record = { "orders-by-stop": ordersByStop, "sales-by-product": salesByProduct, "fulfillment": fulfillment, "pickup-status": pickupStatus, "contact-growth": contactGrowth, "campaigns": campaigns, }; const data = tabDataMap[activeTab] ?? []; try { const res = await fetch("/api/ai/report-explainer", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ reportType: activeTab, dateRange: range, brandId: brandId ?? selectedBrandId, reportData: data, }), }); const result = await res.json(); if (!res.ok) throw new Error(result.error ?? "Analysis failed"); setExplanation(result); } catch (err) { setExplainError(err instanceof Error ? err.message : "Analysis failed"); } finally { setExplainingTab(null); } } // Close explanation panel function closeExplanation() { setExplanation(null); setExplainError(null); } function handleExportCSV(exportFn: () => string, filename: string) { const csv = exportFn(); const blob = new Blob([csv], { type: "text/csv" }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; a.click(); URL.revokeObjectURL(url); } // Auto-fetch when brand changes useEffect(() => { fetchAll(); // eslint-disable-next-line react-hooks/exhaustive-deps }, [selectedBrandId]); return (
{/* ── Error banner ─────────────────────────────────────────────────── */} {error && (
{error}
)} {/* ── Filters ────────────────────────────────────────────────────────── */}
{/* Date preset */}
{(["week", "month", "quarter", "this_year", "custom"] as DatePreset[]).map((p) => ( setPreset(p)} variant={preset === p ? "primary" : "secondary"} size="sm" > {p === "week" ? "This Week" : p === "month" ? "This Month" : p === "quarter" ? "This Quarter" : p === "this_year" ? "This Year" : "Custom"} ))}
{/* Custom date inputs */} {preset === "custom" && (
setCustomStart(e.target.value)} className="rounded-lg border border-zinc-600 px-3 py-1.5 text-xs" /> to setCustomEnd(e.target.value)} className="rounded-lg border border-zinc-600 px-3 py-1.5 text-xs" />
)} {/* Brand filter (platform_admin only) */} {isPlatformAdmin && ( )} Refresh {preset === "quarter" ? quarterLabel(range.start) : preset === "this_year" ? `${new Date().getFullYear()} YTD` : preset === "month" ? monthLabel(range.start) : preset === "custom" ? `${range.start} → ${range.end}` : formatDateRange(range.start, range.end)}
{/* ── Tab bar ────────────────────────────────────────────────────────── */} {/* ── Tab content ──────────────────────────────────────────────────────── */} {/* OVERVIEW */} {activeTab === "overview" && summary && (

{formatDateRange(range.start, range.end)} {selectedBrandId ? ` · Brand: ${brands.find(b => b.id === selectedBrandId)?.name ?? selectedBrandId}` : " · All Brands"}

)} {activeTab === "overview" && !summary && !loading && (
Click Refresh to load summary data
)} {/* ORDERS BY STOP */} {activeTab === "orders-by-stop" && (

Orders by Stop

{ordersByStop.length > 0 && ( handleExportCSV(() => exportOrdersByStop(ordersByStop), `orders-by-stop-${range.start}.csv`)} /> )}
{ const r = row as OrderByStop; return ( {r.stop_name} {r.city} {r.state} {formatDate(r.date)} {r.order_count} ${r.gross_sales.toFixed(2)} {r.pending_count} {r.completed_count} ); }} />
)} {/* SALES BY PRODUCT */} {activeTab === "sales-by-product" && (

Sales by Product

{salesByProduct.length > 0 && ( handleExportCSV(() => exportSalesByProduct(salesByProduct), `sales-by-product-${range.start}.csv`)} /> )}
{ const r = row as SalesByProduct; return ( {r.product_name} {r.units_sold} ${r.gross_revenue.toFixed(2)} ${r.avg_price} ); }} />
)} {/* FULFILLMENT */} {activeTab === "fulfillment" && (

Fulfillment Breakdown

{fulfillment.length > 0 && ( handleExportCSV(() => exportFulfillment(fulfillment), `fulfillment-${range.start}.csv`)} /> )}
{ const r = row as FulfillmentRow; return ( {r.fulfillment_type} {r.order_count} ${r.revenue.toFixed(2)} {r.pct_of_total}% ); }} />
)} {/* PICKUP STATUS */} {activeTab === "pickup-status" && (

Pickup Status by Stop

{pickupStatus.length > 0 && ( handleExportCSV(() => exportPickupStatus(pickupStatus), `pickup-status-${range.start}.csv`)} /> )}
{ const r = row as PickupStatusByStop; return ( {r.stop_name} {r.city} {formatDate(r.date)} {r.total_orders} {r.pending} {r.completed} {r.canceled} ); }} />
)} {/* CONTACT GROWTH */} {activeTab === "contact-growth" && (

Contact Growth

{contactGrowth.length > 0 && ( handleExportCSV(() => exportContactGrowth(contactGrowth), `contact-growth-${range.start}.csv`)} /> )}
{ const r = row as ContactGrowthRow; return ( {r.date} {r.new_contacts} {r.imports} {r.total} ); }} />
)} {/* CAMPAIGNS */} {activeTab === "campaigns" && (

Campaign Activity

{campaigns.length > 0 && ( handleExportCSV(() => exportCampaignActivity(campaigns), `campaigns-${range.start}.csv`)} /> )}
{ const r = row as CampaignActivityRow; return ( {r.campaign_name} {r.status} {r.campaign_type} {r.sent_at ?? "—"} {r.messages_logged} ); }} />
)} {/* ── AI Explanation Panel ──────────────────────────────────────────────── */} {explanation && (
🤖

AI Report Analysis

· {activeTab.replace("-", " ")}

Summary

{explanation.summary}

Key Insights

    {explanation.keyInsights.map((insight, i) => (
  • {insight}
  • ))}

Suggested Actions

    {explanation.suggestedActions.map((action, i) => (
  • {action}
  • ))}
⚠️ AI-generated suggestions — review before use. Not a substitute for business judgment.
)} {explainError && (
{explainError}
)}
); }