Enhance dashboard API to support date range filtering and update KPI definitions for improved clarity

This commit is contained in:
2026-07-08 16:45:02 +05:30
parent 903c7ea74c
commit 13ef766ca6
2 changed files with 32 additions and 8 deletions

View File

@@ -16,8 +16,18 @@ export function login(email, password) {
} }
// ── 2. Dashboard ───────────────────────────────────────────────────────────── // ── 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}`);
} }
/** /**

View File

@@ -171,13 +171,15 @@ function RangeCalendar({ from, to, maxDate, onSelect }) {
} }
// Live KPI cards — each maps to one field of GET /api/v1/hub/dashboard. // 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 = [ 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_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: '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: '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: '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' } { key: 'exceptions', label: 'Needs Checking', icon: WarningAmberIcon, color: '#D93025', sub: 'Damaged or unclear', ranged: true }
]; ];
const DATE_FMT = 'YYYY-MM-DD'; const DATE_FMT = 'YYYY-MM-DD';
@@ -219,7 +221,7 @@ export default function Dashboard() {
setError(''); setError('');
try { try {
const [dash, vehicles, activity, zones] = await Promise.all([ const [dash, vehicles, activity, zones] = await Promise.all([
getDashboard(), getDashboard(range.from, range.to),
getInboundVehicles().catch(() => null), getInboundVehicles().catch(() => null),
getActivity(8).catch(() => null), getActivity(8).catch(() => null),
getZones().catch(() => null) getZones().catch(() => null)
@@ -251,10 +253,14 @@ export default function Dashboard() {
} finally { } finally {
setLoading(false); 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(() => { useEffect(() => {
loadDashboard(); const t = setTimeout(loadDashboard, 250);
return () => clearTimeout(t);
}, [loadDashboard]); }, [loadDashboard]);
// Pull the full report for the selected range and download it as an Excel file. // 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]); }, [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) => ({ const kpiCards = KPI_DEFS.map((d) => ({
...d, ...d,
sub: d.ranged ? `${d.sub} ${rangeLabel}` : d.sub,
value: kpis && kpis[d.key] != null ? Number(kpis[d.key]).toLocaleString('en-IN') : '—' value: kpis && kpis[d.key] != null ? Number(kpis[d.key]).toLocaleString('en-IN') : '—'
})); }));