"use client" import { useCallback, useEffect, useState } from "react" import Link from "next/link" import { TrendingUp, ArrowUpRight, Loader2, Calendar, ChevronDown, Package, ShoppingCart, Users, Lightbulb, TrendingDown, Minus, Sparkles, } from "lucide-react" import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, AreaChart, Area, LineChart, Line, BarChart, Bar, PieChart, Pie, Cell, Legend, } from "recharts" import { useTheme } from "@/components/ThemeProvider" import { cn } from "@/lib/utils" type TimeRange = "7days" | "30days" | "1year" type BusinessMetricsPayload = { currency: string disclaimer: string kpis: { key: string label: string value: number changePct: number suffix?: string sparkline: number[] }[] monthlyProfitBars: { month: string; profit: number }[] feeDonut: { name: string; value: number }[] feeTotal: number productMargins: { product: string unitsSold: number cogs: number netProfit: number marginPct: number trend: "up" | "down" | "flat" refundUnits: number }[] aiInsight: string totals: { gross: number; refunds: number; orders: number } } function formatMoney(amount: number, currency: string) { try { return new Intl.NumberFormat(undefined, { style: "currency", currency, maximumFractionDigits: 0 }).format(amount) } catch { return `${currency} ${amount.toFixed(0)}` } } function MiniSpark({ data, color, }: { data: number[] color: string }) { const pts = data.map((y, i) => ({ i, y })) return (
) } export default function DashboardPage() { const { theme } = useTheme() const isLight = theme === "light" const [snapshot, setSnapshot] = useState({ totalSales: 0, products: 0, orders: 0, customers: 0, }) const [business, setBusiness] = useState(null) const [chartData, setChartData] = useState<{ date: string; amount: number }[]>([]) const [loading, setLoading] = useState(true) const [bizErr, setBizErr] = useState(null) const [timeRange, setTimeRange] = useState("7days") const fetchDashboardData = useCallback(async () => { try { setLoading(true) const [productsRes, ordersRes, customersRes] = await Promise.all([ fetch("/api/shopify/inventory").catch(() => ({ ok: false, json: async () => ({ products: [], message: "Failed to fetch products" }), })), fetch("/api/shopify/orders").catch(() => ({ ok: false, json: async () => ({ orders: [], message: "Failed to fetch orders" }), })), fetch("/api/shopify/customers").catch(() => ({ ok: false, json: async () => ({ customers: [], message: "Failed to fetch customers" }), })), ]) const [productsData, ordersData, customersData] = await Promise.all([ productsRes.json().catch(() => ({ products: [] })), ordersRes.json().catch(() => ({ orders: [] })), customersRes.json().catch(() => ({ customers: [] })), ]) try { const bizRes = await fetch("/api/dashboard/business-metrics") const j = await bizRes.json().catch(() => null) if (bizRes.ok && j && typeof j === "object" && !("message" in j && typeof j.message === "string")) { setBusiness(j as BusinessMetricsPayload) setBizErr(null) } else { setBusiness(null) setBizErr(typeof j?.message === "string" ? j.message : null) } } catch { setBusiness(null) setBizErr(null) } const orders = ordersData.orders || [] const totalSales = orders.reduce((sum: number, order: { total_price?: string }) => sum + parseFloat(order.total_price ?? "0"), 0) || 0 const generateDateRange = (range: TimeRange) => { const dates = [] const now = new Date() if (range === "1year") { for (let i = 11; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth() - i, 1) dates.push(d.toLocaleDateString("en-US", { month: "short", year: "2-digit" })) } } else { const count = range === "30days" ? 30 : 7 for (let i = count - 1; i >= 0; i--) { const d = new Date(now.getFullYear(), now.getMonth(), now.getDate() - i) dates.push(d.toLocaleDateString("en-US", { month: "short", day: "numeric" })) } } return dates } const rangeLabels = generateDateRange(timeRange) const salesByDate: Record = {} orders.forEach((order: { created_at?: string; total_price?: string }) => { const orderDate = new Date(order.created_at ?? "") let label = "" if (timeRange === "1year") { label = orderDate.toLocaleDateString("en-US", { month: "short", year: "2-digit" }) } else { label = orderDate.toLocaleDateString("en-US", { month: "short", day: "numeric" }) } if (rangeLabels.includes(label)) { salesByDate[label] = (salesByDate[label] || 0) + parseFloat(order.total_price ?? "0") } }) const processedChartData = rangeLabels.map((date) => ({ date, amount: Math.round((salesByDate[date] || 0) * 100) / 100, })) setChartData(processedChartData) setSnapshot({ totalSales, products: productsData.products?.length ?? 0, orders: orders.length, customers: customersData.customers?.length ?? 0, }) } catch (error) { console.error("Dashboard stats fetch error:", error) } finally { setLoading(false) } }, [timeRange]) useEffect(() => { void fetchDashboardData() }, [fetchDashboardData]) const currency = business?.currency ?? "USD" const gridStroke = isLight ? "rgba(0,0,0,0.06)" : "rgba(255,255,255,0.08)" const chartMuted = isLight ? "#64748b" : "#94a3b8" const donutColors = ["#6366f1", "#a855f7", "#f43f5e", "#f97316"] const kpiAccent: Record = { netProfit: "#22c55e", grossRevenue: "#3b82f6", fees: "#f43f5e", margin: "#22c55e", } return (

Overview

Shopify snapshot plus estimated profit, fees, and product economics from your synced orders.

{snapshot.orders} orders {snapshot.products} products {snapshot.customers} customers AI analytics
{/* Financial KPI row — Image 2 style */}
{(business?.kpis ?? []).map((kpi) => (

{kpi.label}

{loading && !business ? ( ) : kpi.suffix ? ( `${kpi.value.toFixed(1)}${kpi.suffix}` ) : ( formatMoney(kpi.value, currency) )}

= 0 ? "text-emerald-500" : "text-rose-400", )} > {kpi.changePct >= 0 ? "+" : ""} {kpi.changePct}% vs prior month

))} {!business && !loading && (
{bizErr ? `Could not load estimated profit metrics: ${bizErr}.` : "Estimated profit and fee cards will appear once synced order history is available for your active store."}
)}
{/* Profit bars + fee donut */} {business && (

Monthly profit (est.)

Based on gross revenue × assumed net margin ({business.disclaimer.slice(0, 80)}…)

formatMoney(Number(v), currency)} /> [formatMoney(Number(v ?? 0), currency), "Profit (est.)"]} />

Fee breakdown (est.)

Total estimated fees {formatMoney(business.feeTotal, currency)}

{business.feeDonut.map((_, i) => ( ))} formatMoney(Number(v ?? 0), currency)} contentStyle={{ borderRadius: 12, border: isLight ? "1px solid #e2e8f0" : "1px solid rgba(255,255,255,0.12)", background: isLight ? "#fff" : "#0f172a", fontWeight: 700, }} />
)} {/* Snapshot icons row — compact */}
{[ { label: "Live revenue (period)", value: formatMoney(snapshot.totalSales, currency), icon: TrendingUp }, { label: "Catalog size", value: String(snapshot.products), icon: Package }, { label: "Orders in sync", value: String(snapshot.orders), icon: ShoppingCart }, { label: "Customers", value: String(snapshot.customers), icon: Users }, ].map((row) => (
{loading ? : }

{row.label}

{row.value}

))}
{/* Product margins + AI insight */} {business && business.productMargins.length > 0 && (

Product-level margins (est.)

{business.productMargins.map((r) => ( ))}
Product Units Est. COGS Est. net Margin Trend
{r.product} {r.unitsSold} {formatMoney(r.cogs, currency)} {formatMoney(r.netProfit, currency)} {r.marginPct}% {r.trend === "up" && } {r.trend === "down" && } {r.trend === "flat" && }

{business.aiInsight}

)}

Recent Sales Trend

Revenue analysis over time

{(["7days", "30days", "1year"] as const).map((r) => ( ))}
{loading ? (
) : chartData.length > 0 ? ( `$${val > 999 ? (val / 1000).toFixed(1) + "k" : val}`} /> { const num = typeof value === "number" ? value : Number(value) const v = Number.isFinite(num) ? num : 0 return [`$${v.toLocaleString()}`, "Revenue"] }} /> ) : (
No sales data found for this period.
)}
) }