Enhance dashboard API to support date range filtering and update KPI definitions for improved clarity
This commit is contained in:
@@ -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}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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') : '—'
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user