import React from 'react'; import { Printer, Tag, TrendingUp, FileText, Users, UserCircle, Package, MapPin, ArrowUpRight, ArrowDownRight, } from 'lucide-react'; import { toast } from 'sonner'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../ui/card'; import { Button } from '../ui/button'; import { Badge } from '../ui/badge'; import { Skeleton } from '../ui/skeleton'; import { CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line, PieChart, Pie, Cell, XAxis, YAxis, } from 'recharts'; import { getDashboardOverview } from '../../services/dashboardService'; import { getTemplatePrintStatList } from '../../services/reportsService'; import type { DashboardMetricCardDto, DashboardOverviewDto, DashboardRecentLabelItemDto } from '../../types/dashboardOverview'; import type { TemplatePrintStatListItem } from '../../types/reports'; import { useAuth } from '../auth/AuthProvider'; import { displayNameFromUser } from '../../lib/currentUserDisplay'; import { formatDisplayText } from '../../lib/emptyDisplay'; import { formatRelativeSince } from '../../lib/relativeSince'; const CATEGORY_CHART_COLORS = ['#3b82f6', '#f59e0b', '#6366f1', '#10b981', '#ec4899', '#8b5cf6', '#14b8a6']; function recentLabelBadgeText(item: DashboardRecentLabelItemDto): string { const b = (item.labelTypeBadge || '').trim(); if (b && b !== '—') return b.length >= 2 ? b.slice(0, 2) : `${b} `.slice(0, 2); const d = (item.displayName || '').trim(); if (d && d !== '—') return d.slice(0, 2); return 'LB'; } function isRecentLabelExpired(item: DashboardRecentLabelItemDto): boolean { return (item.status || '').toLowerCase() === 'expired'; } /** 与 reports template-print-stat 默认区间一致:近 30 天(含今天) */ function last30DaysIsoRange(): { startDate: string; endDate: string } { const end = new Date(); const start = new Date(); start.setHours(12, 0, 0, 0); end.setHours(12, 0, 0, 0); start.setDate(start.getDate() - 29); return { startDate: start.toISOString().slice(0, 10), endDate: end.toISOString().slice(0, 10), }; } function formatHeaderDate(iso: string | null | undefined): string { const d = iso ? new Date(iso) : new Date(); if (!Number.isFinite(d.getTime())) return new Date().toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); return d.toLocaleDateString('en-US', { month: 'long', day: 'numeric', year: 'numeric' }); } function formatTrendPrinted(m: DashboardMetricCardDto): string { const r = m.changeRate; const sign = r > 0 ? '+' : ''; return `${sign}${r.toFixed(1)}%`; } function formatTrendNewCount(m: DashboardMetricCardDto): string { const v = m.changeValue; if (v === 0) return 'No change'; return `${v > 0 ? '+' : ''}${v} New`; } function formatTrendActiveUsers(m: DashboardMetricCardDto): string { if (m.changeValue === 0) return 'Stable'; return `${m.changeValue > 0 ? '+' : ''}${m.changeValue} New`; } function trendUpPrinted(m: DashboardMetricCardDto): boolean { return m.changeRate > 0 || (m.changeRate === 0 && m.changeValue >= 0); } function trendUpDelta(m: DashboardMetricCardDto): boolean { return m.changeValue >= 0; } function buildWeeklyChartSeries(overview: DashboardOverviewDto): { day: string; labels: number; date: string }[] { const pts = overview.weeklyPrintVolume ?? []; if (pts.length) { return pts.map((p) => ({ date: p.date, day: new Date(`${p.date}T12:00:00`).toLocaleDateString('en-US', { weekday: 'short' }), labels: p.value, })); } const out: { day: string; labels: number; date: string }[] = []; for (let i = 6; i >= 0; i--) { const d = new Date(); d.setHours(12, 0, 0, 0); d.setDate(d.getDate() - i); const iso = d.toISOString().slice(0, 10); out.push({ date: iso, day: d.toLocaleDateString('en-US', { weekday: 'short' }), labels: 0, }); } return out; } type KPICardProps = { title: string; value: string; trend: string; trendUp: boolean; icon: React.ComponentType<{ className?: string }>; color: string; bgColor: string; }; export type DashboardProps = { /** 跳转 Reports 并选中 Label Report 标签页 */ onViewReports?: () => void; /** Recent Labels 区域的 View All:跳转到 Reports 页面 */ onViewAllRecentLabels?: () => void; }; export function Dashboard({ onViewReports, onViewAllRecentLabels }: DashboardProps = {}) { const auth = useAuth(); const welcomeName = auth.roleDisplay ?? displayNameFromUser(auth.user); const [overview, setOverview] = React.useState(null); const [loading, setLoading] = React.useState(true); const [templateStats, setTemplateStats] = React.useState([]); const [templateStatsLoading, setTemplateStatsLoading] = React.useState(true); const load = React.useCallback(async () => { setLoading(true); setTemplateStatsLoading(true); const { startDate, endDate } = last30DaysIsoRange(); try { const [data, statRes] = await Promise.all([ getDashboardOverview(), getTemplatePrintStatList({ skipCount: 1, maxResultCount: 12, startDate, endDate, sorting: 'PrintedCount desc', }).catch((e) => { console.error(e); return { items: [], totalCount: 0 }; }), ]); setOverview(data); setTemplateStats(statRes.items ?? []); } catch (e) { console.error(e); toast.error(e instanceof Error ? e.message : 'Failed to load dashboard'); setOverview(null); setTemplateStats([]); } finally { setLoading(false); setTemplateStatsLoading(false); } }, []); React.useEffect(() => { void load(); }, [load]); const weeklySeries = React.useMemo(() => (overview ? buildWeeklyChartSeries(overview) : []), [overview]); const pieRows = React.useMemo(() => { if (!overview?.byCategory?.length) return []; return overview.byCategory.map((c, i) => ({ id: c.categoryId || `cat-${i}`, name: c.categoryName || '—', value: c.count, color: CATEGORY_CHART_COLORS[i % CATEGORY_CHART_COLORS.length], })); }, [overview]); const recentLabels = overview?.recentLabels ?? []; const categoryTotal = overview?.byCategoryTotal ?? 0; const generatedAt = overview?.generatedAt; return (

Dashboard Overview

Welcome back, {welcomeName}. Here's what's happening today.

{formatHeaderDate(generatedAt)} | Last updated:{' '} {auth.loading && !auth.user?.lastUpdated ? '…' : formatRelativeSince(auth.user?.lastUpdated ?? generatedAt ?? null)}

{loading || !overview ? ( <> {Array.from({ length: 6 }).map((_, i) => ( ))} ) : ( <> )}
Weekly Print Volume Number of labels printed over the last 7 days {loading ? (
) : (
{ const p = payload?.[0]?.payload as { date?: string } | undefined; return p?.date ?? ''; }} contentStyle={{ borderRadius: '8px', border: 'none', boxShadow: '0 4px 6px -1px rgb(0 0 0 / 0.1)', }} cursor={{ stroke: '#d1d5db', strokeWidth: 1 }} />
)}
Recent Labels Latest printed labels across all locations
{loading ? (
{Array.from({ length: 5 }).map((_, i) => (
))}
) : recentLabels.length === 0 ? (
No recent labels. Printed labels will appear here.
) : (
{recentLabels.map((label, i) => { const expired = isRecentLabelExpired(label); return (
{recentLabelBadgeText(label)}

{formatDisplayText(label.displayName, '—')}

{formatDisplayText(label.labelCode, '—')} • {formatDisplayText(label.printedByName, '—')}

{formatRelativeSince(label.printedAt || null)} {label.status || '—'}
); })}
)}
By Category {loading ? (
) : pieRows.length === 0 ? (
No category distribution
) : ( <>
{pieRows.map((entry, index) => ( ))}
{categoryTotal} Total
{pieRows.map((item) => (
{item.name}
{item.value}
))}
)} Printed labels byTemplates By template — labels printed in the last 30 days {loading || templateStatsLoading ? (
{Array.from({ length: 6 }).map((_, i) => (
))}
) : templateStats.length === 0 ? (
No template print data in the last 30 days.
) : (
{templateStats.map((row, i) => { const name = (row.templateName ?? '').trim() || 'None'; const key = row.templateId ? `${row.templateId}-${i}` : `row-${i}-${name}`; return (

{name}

{row.printedCount.toLocaleString()}
); })}
)}
); } function KPICard({ title, value, trend, trendUp, icon: Icon, color, bgColor }: KPICardProps) { return (

{title}

{value}

{trendUp ? ( ) : ( )} {trend} Vs. last period
); }