import { useState, useMemo, useEffect, useCallback, useRef } from 'react'; import dayjs from 'dayjs'; import { Truck, FileText, AlertTriangle, RefreshCw, Layers, Activity, Download, Info, ArrowRight } from 'lucide-react'; import { Card } from '@astryxdesign/core/Card'; import { Table, proportional, pixel } from '@astryxdesign/core/Table'; import { Badge } from '@astryxdesign/core/Badge'; import { Heading, Text } from '@astryxdesign/core/Text'; import Button from '@/components/Button'; import Panel from '@/components/Panel'; import PageHeader from '@/components/PageHeader'; import StatCard from '@/components/StatCard'; import { getDashboard, getInboundVehicles, getZones, getHubReport, getActivity } from '@/api/hub'; import { getHubContext } from '@/auth/session'; const BRAND = 'var(--color-brand)'; const DATE_FMT = 'YYYY-MM-DD'; const RANGE_KEY = 'hub_dashboard_range'; const clockTime = (iso) => { const d = new Date(iso); if (Number.isNaN(d.getTime())) return ''; return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }); }; const KPI_DEFS = [ { key: 'parcels_received_today', label: 'Total Parcels Received', icon: Layers, tone: 'blue', sub: 'Received', ranged: true }, { key: 'milers_available', label: 'Available Milers', icon: Activity, tone: 'green', sub: 'Free right now' }, { key: 'milers_on_duty', label: 'Milers on Duty', icon: Info, tone: 'cyan', sub: 'Currently working' }, { key: 'pending_pickups', label: 'Pending Pickups', icon: FileText, tone: 'orange', sub: 'Awaiting a miler' }, { key: 'batches_sent_today', label: 'Batches Sent', icon: Truck, tone: 'purple', sub: 'Dispatched', ranged: true }, { key: 'exceptions', label: 'Needs Checking', icon: AlertTriangle, tone: 'red', sub: 'Damaged or unclear', ranged: true } ]; export default function Dashboard() { const today = dayjs().format(DATE_FMT); const weekAgo = dayjs().subtract(6, 'day').format(DATE_FMT); // eslint-disable-next-line no-unused-vars const [range, setRange] = useState(() => { try { const saved = JSON.parse(localStorage.getItem(RANGE_KEY)); if (saved?.from && saved?.to && !dayjs(saved.to).isAfter(dayjs(today))) return saved; } catch { // ignore } return { from: weekAgo, to: today }; }); useEffect(() => { localStorage.setItem(RANGE_KEY, JSON.stringify(range)); }, [range]); const hub = getHubContext(); const [kpis, setKpis] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(''); const [incomingVehicles, setIncomingVehicles] = useState([]); const [activeRoutes, setActiveRoutes] = useState([]); const [recentActivity, setRecentActivity] = useState([]); const [exporting, setExporting] = useState(false); const loadRequestId = useRef(0); const loadDashboard = useCallback(async () => { const requestId = ++loadRequestId.current; setLoading(true); setError(''); try { const [dash, vehicles, zones, activity] = await Promise.all([ getDashboard(range.from, range.to).catch(() => null), getInboundVehicles().catch(() => null), getZones().catch(() => null), getActivity(8).catch(() => null) ]); if (loadRequestId.current !== requestId) return; setKpis(dash?.data || {}); setIncomingVehicles( (vehicles?.data || []).map((v) => ({ id: v.vehicleno || `Trip ${v.tripsheetid}`, origin: v.origin || '—', status: v.status || 'On the way', progress: v.unloadedpct || 0 })) ); setActiveRoutes( (zones?.data || []).map((z) => ({ zone: z.zonename ? `${z.zonename} (${z.zone})` : z.zone, packages: z.parcels ?? 0, riders: z.milers ?? 0, status: z.status || 'Active' })) ); setRecentActivity( (activity?.data || []).map((a) => ({ time: clockTime(a.time), type: a.type, text: a.text })) ); } catch (err) { if (loadRequestId.current === requestId) { setError(err?.message || 'Could not load dashboard stats.'); } } finally { if (loadRequestId.current === requestId) { setLoading(false); } } }, [range.from, range.to]); useEffect(() => { loadDashboard(); }, [loadDashboard]); const handleExport = useCallback(async () => { if (exporting) return; setExporting(true); try { const [{ downloadHubReport }, res] = await Promise.all([ import('@/lib/hubReport'), getHubReport(range.from, range.to) ]); downloadHubReport(res?.data || {}, { hubName: hub.hubname || 'Hub', from: range.from, to: range.to }); } catch (err) { console.error(err); } finally { setExporting(false); } }, [exporting, range.from, range.to, hub.hubname]); const rangeLabel = useMemo(() => { if (range.from === today && range.to === today) return 'today'; if (range.from === range.to) return `on ${dayjs(range.from).format('DD MMM')}`; return `${dayjs(range.from).format('DD MMM')} – ${dayjs(range.to).format('DD MMM')}`; }, [range, today]); const kpiCards = KPI_DEFS.map((d) => ({ ...d, sub: d.ranged ? `${d.sub} ${rangeLabel}` : d.sub, value: kpis && kpis[d.key] != null ? Number(kpis[d.key]).toLocaleString('en-IN') : '—' })); const sorted = Number(kpis?.parcels_sorted ?? 0); const target = Number(kpis?.sorting_target ?? 0); const pct = target > 0 ? Math.min(100, Math.round((sorted / target) * 100)) : 0; const getVehicleBadgeVariant = (status) => { const s = String(status).toLowerCase(); if (s.includes('unloading')) return 'positive'; if (s.includes('way')) return 'info'; return 'neutral'; }; const getZoneBadgeVariant = (status) => { const s = String(status).toLowerCase(); if (s.includes('active')) return 'positive'; if (s.includes('need') || s.includes('pending')) return 'warning'; return 'neutral'; }; const trucksColumns = useMemo(() => [ { key: 'id', header: