diff --git a/src/api/hub.js b/src/api/hub.js index 6eb3011..c1098c0 100644 --- a/src/api/hub.js +++ b/src/api/hub.js @@ -16,8 +16,18 @@ export function login(email, password) { } // ── 2. Dashboard ───────────────────────────────────────────────────────────── -export function getDashboard() { - return http.get(`${V1}/hub/dashboard`); +/** + * Live KPI stats for the logged-in staff's hub. + * @param {string} [from] Inclusive range start, YYYY-MM-DD. Range-scoped fields + * (parcels received, batches sent, exceptions) aggregate + * over [from, to]; live fields (milers available/on-duty, + * pending pickups) always reflect "now". + * @param {string} [to] Inclusive range end, YYYY-MM-DD. + * When omitted the backend defaults to today. + */ +export function getDashboard(from, to) { + const qs = from && to ? `?from=${encodeURIComponent(from)}&to=${encodeURIComponent(to)}` : ''; + return http.get(`${V1}/hub/dashboard${qs}`); } /** diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 5c4a79a..25f03b5 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -171,13 +171,15 @@ function RangeCalendar({ from, to, maxDate, onSelect }) { } // Live KPI cards — each maps to one field of GET /api/v1/hub/dashboard. +// `ranged: true` → the metric aggregates over the selected date range, so its +// subtitle shows the window. The rest are live "right now" figures. const KPI_DEFS = [ - { key: 'parcels_received_today', label: 'Total Parcels Received', icon: DynamicFeedIcon, color: '#1A73E8', sub: 'Received today' }, + { key: 'parcels_received_today', label: 'Total Parcels Received', icon: DynamicFeedIcon, color: '#1A73E8', sub: 'Received', ranged: true }, { key: 'milers_available', label: 'Available Milers', icon: DeliveryDiningIcon, color: '#1E8E3E', sub: 'Free right now' }, { key: 'milers_on_duty', label: 'Milers on Duty', icon: InfoOutlinedIcon, color: '#1A73E8', sub: 'Currently working' }, { key: 'pending_pickups', label: 'Pending Pickups', icon: AssignmentIcon, color: '#F29900', sub: 'Awaiting a miler' }, - { key: 'batches_sent_today', label: 'Batches Sent Today', icon: LocalShippingIcon, color: '#8E24AA', sub: 'Dispatched today' }, - { key: 'exceptions', label: 'Needs Checking', icon: WarningAmberIcon, color: '#D93025', sub: 'Damaged or unclear' } + { key: 'batches_sent_today', label: 'Batches Sent', icon: LocalShippingIcon, color: '#8E24AA', sub: 'Dispatched', ranged: true }, + { key: 'exceptions', label: 'Needs Checking', icon: WarningAmberIcon, color: '#D93025', sub: 'Damaged or unclear', ranged: true } ]; const DATE_FMT = 'YYYY-MM-DD'; @@ -219,7 +221,7 @@ export default function Dashboard() { setError(''); try { const [dash, vehicles, activity, zones] = await Promise.all([ - getDashboard(), + getDashboard(range.from, range.to), getInboundVehicles().catch(() => null), getActivity(8).catch(() => null), getZones().catch(() => null) @@ -251,10 +253,14 @@ export default function Dashboard() { } finally { setLoading(false); } - }, []); + }, [range.from, range.to]); + // Re-fetch whenever the range (or loadDashboard) changes. Debounced so picking a + // start-then-end date in the calendar fires one request for the final range, + // not an extra one for the transient single-day selection. useEffect(() => { - loadDashboard(); + const t = setTimeout(loadDashboard, 250); + return () => clearTimeout(t); }, [loadDashboard]); // Pull the full report for the selected range and download it as an Excel file. @@ -287,8 +293,16 @@ export default function Dashboard() { } }, [exporting, range.from, range.to, hub.hubname]); + // Short label for the selected window, appended to range-scoped KPI subtitles. + 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') : '—' }));