import React, { useState, useMemo, useEffect, useCallback } from 'react';
import {
Box, Typography, Card, CardContent, Avatar, Chip, Table, TableBody,
TableCell, TableContainer, TableHead, TableRow, IconButton, Button,
Stack, Tooltip, LinearProgress, Dialog, DialogContent, DialogActions, Drawer,
TextField, MenuItem, Divider, InputAdornment, Menu, ListItemIcon,
Snackbar, Alert, useMediaQuery, Grid, Select, FormControl, InputLabel,
Autocomplete, Switch, FormControlLabel, Paper, ToggleButtonGroup,
ToggleButton, Stepper, Step, StepLabel, CircularProgress
} from '@mui/material';
import { useTheme, alpha } from '@mui/material/styles';
// ── Professional outlined icon set ──────────────────────────────────────────────
import AddOutlinedIcon from '@mui/icons-material/AddOutlined';
import SearchOutlinedIcon from '@mui/icons-material/SearchOutlined';
import EditOutlinedIcon from '@mui/icons-material/EditOutlined';
import DeleteOutlineIcon from '@mui/icons-material/DeleteOutlineOutlined';
import VisibilityOutlinedIcon from '@mui/icons-material/VisibilityOutlined';
import MoreVertOutlinedIcon from '@mui/icons-material/MoreVertOutlined';
import CloseOutlinedIcon from '@mui/icons-material/CloseOutlined';
import VerifiedOutlinedIcon from '@mui/icons-material/VerifiedOutlined';
import CircleRoundedIcon from '@mui/icons-material/CircleRounded';
import StarRoundedIcon from '@mui/icons-material/StarRounded';
import PhoneOutlinedIcon from '@mui/icons-material/PhoneOutlined';
import CallOutlinedIcon from '@mui/icons-material/CallOutlined';
import PlaceOutlinedIcon from '@mui/icons-material/PlaceOutlined';
import BadgeOutlinedIcon from '@mui/icons-material/BadgeOutlined';
import PersonOutlineOutlinedIcon from '@mui/icons-material/PersonOutlineOutlined';
import PersonAddAlt1OutlinedIcon from '@mui/icons-material/PersonAddAlt1Outlined';
import GroupsOutlinedIcon from '@mui/icons-material/GroupsOutlined';
import TwoWheelerOutlinedIcon from '@mui/icons-material/TwoWheelerOutlined';
import ElectricBikeOutlinedIcon from '@mui/icons-material/ElectricBikeOutlined';
import PedalBikeOutlinedIcon from '@mui/icons-material/PedalBikeOutlined';
import LocalShippingOutlinedIcon from '@mui/icons-material/LocalShippingOutlined';
import AirportShuttleOutlinedIcon from '@mui/icons-material/AirportShuttleOutlined';
import InventoryOutlinedIcon from '@mui/icons-material/Inventory2Outlined';
import PaymentsOutlinedIcon from '@mui/icons-material/PaymentsOutlined';
import AccessTimeOutlinedIcon from '@mui/icons-material/AccessTimeOutlined';
import WarningAmberOutlinedIcon from '@mui/icons-material/WarningAmberOutlined';
import CheckCircleOutlinedIcon from '@mui/icons-material/CheckCircleOutlined';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import GppGoodOutlinedIcon from '@mui/icons-material/GppGoodOutlined';
import ApartmentOutlinedIcon from '@mui/icons-material/ApartmentOutlined';
import TableRowsOutlinedIcon from '@mui/icons-material/TableRowsOutlined';
import GridViewOutlinedIcon from '@mui/icons-material/GridViewOutlined';
import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
import HomeWorkOutlinedIcon from '@mui/icons-material/HomeWorkOutlined';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
import StatCard from '@/components/StatCard';
import { getMilers, createMiler, updateMiler, deleteMiler } from '@/api/hub';
import { getHubContext } from '@/auth/session';
// The backend uses snake-case availability values; this page uses friendlier labels.
const API_TO_UI_STATUS = { Available: 'Idle', Assigned: 'On Pickup', On_Break: 'On Break', Offline: 'Offline' };
const UI_TO_API_STATUS = {
Idle: 'Available',
'On Pickup': 'Assigned',
'On Break': 'On_Break',
'Returning to Hub': 'Assigned',
Offline: 'Offline',
Suspended: 'Offline'
};
// ── Reference data ──────────────────────────────────────────────────────────────
const VEHICLES = {
'Electric Bike': { capacity: 30, icon: ElectricBikeOutlinedIcon },
'Motorcycle': { capacity: 25, icon: TwoWheelerOutlinedIcon },
'Cycle': { capacity: 15, icon: PedalBikeOutlinedIcon },
'Cargo Van': { capacity: 120, icon: AirportShuttleOutlinedIcon },
'Mini Truck': { capacity: 200, icon: LocalShippingOutlinedIcon },
};
const VEHICLE_TYPES = Object.keys(VEHICLES);
const ALL_ZONES = ['Dwarka', 'Janakpuri', 'Saket', 'Malviya Nagar', 'Rohini', 'Vasant Kunj', 'Central Delhi', 'Lajpat Nagar', 'Karol Bagh', 'Connaught Place', 'Mayur Vihar'];
const HUB_OPTIONS = ['Delhi Operations Hub', 'Mumbai Hub (BOM-02)', 'Bengaluru Hub (BLR-03)', 'Jaipur Hub (JAI-08)'];
const STATUS_OPTIONS = ['On Pickup', 'Idle', 'On Break', 'Returning to Hub', 'Offline', 'Suspended'];
const STATUS_META = {
'On Pickup': { color: '#1A73E8', bg: '#E8F0FE', label: 'On Pickup' },
'Idle': { color: '#B06000', bg: '#FEF7E0', label: 'Idle / Available' },
'On Break': { color: '#8E24AA', bg: '#F3E5F5', label: 'On Break' },
'Returning to Hub': { color: '#0B8043', bg: '#E6F4EA', label: 'Returning' },
'Offline': { color: '#80868B', bg: '#F1F3F4', label: 'Offline' },
'Suspended': { color: '#D93025', bg: '#FCE8E6', label: 'Suspended' },
};
let idSeq = 8060;
const genId = () => `RDR-${idSeq++}`;
// Turn a vehicle type name into the 1-based id the backend uses (best effort).
const vehicleIdFor = (vehicle) => Math.max(1, VEHICLE_TYPES.indexOf(vehicle) + 1);
// Map a raw API miler onto the rich shape this page renders. Fields the API does
// not provide (zones, COD, live load) default to empty/zero so the UI still works.
// Format an ISO check-in timestamp as HH:MM (or "—").
const checkInLabel = (iso) => {
if (!iso) return '—';
const d = new Date(iso);
if (Number.isNaN(d.getTime())) return '—';
return d.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
};
const mapMiler = (m, hubName) => {
// Real backend exposes defaultvehicletype (e.g. "Bike"); fall back to id lookup.
const vehicle = m.defaultvehicletype || VEHICLE_TYPES[(m.vehicleid || 1) - 1] || 'Motorcycle';
const capacity = m.capacity || VEHICLES[vehicle]?.capacity || 0;
return {
id: m.userid,
userid: m.userid,
vehicleid: m.vehicleid,
hubid: m.hubid,
name: m.displayname || `Miler ${m.userid}`,
phone: m.phone || '—',
hub: hubName,
zones: Array.isArray(m.zones) ? m.zones : m.currentpincode ? [m.currentpincode] : [],
vehicle,
// Real registration plate from the joined vehicles table; fall back to a
// placeholder tag if the miler has no vehicle assigned.
vehicleNo: m.vehicleno || (m.vehicleid ? `VEH-${m.vehicleid}` : '—'),
status: API_TO_UI_STATUS[m.availabilitystatus] || 'Idle',
checkInTime: checkInLabel(m.checkinat),
hoursToday: m.hoursactive ?? 0,
assigned: m.assignedload ?? 0,
capacity,
pickupsPending: m.pickupspending ?? 0,
deliveriesPending: m.assignedload ?? 0,
deliveriesDone: m.totalcompletedpickups ?? m.completedorders ?? 0,
deliveriesFailed: m.totalcancelledpickups ?? m.cancelledorders ?? 0,
codCollected: m.codcollected ?? 0,
codPending: m.codpending ?? 0,
rating: m.rating ?? 0,
verified: Boolean(m.isverified ?? m.device_token)
};
};
// ── Helpers ─────────────────────────────────────────────────────────────
const successRate = (r) => {
const total = r.deliveriesDone + r.deliveriesFailed;
return total === 0 ? 100 : Math.round((r.deliveriesDone / total) * 100);
};
const loadPct = (r) => (r.capacity === 0 ? 0 : Math.round((r.assigned / r.capacity) * 100));
const initials = (name) => name.split(' ').map(w => w[0]).slice(0, 2).join('').toUpperCase();
const inr = (n) => `₹${n.toLocaleString('en-IN')}`;
const EMPTY_FORM = {
name: '', phone: '', hub: 'Delhi Operations Hub', zones: [],
vehicle: '', vehicleNo: '', status: 'Idle', verified: false,
};
// ════════════════════════════════════════════════════════════════════════════════
// Presentational Components (Improved spacing & visuals)
// ════════════════════════════════════════════════════════════════════════════════
function StatusPill({ status, size = 'md' }) {
const m = STATUS_META[status] || STATUS_META['Offline'];
const small = size === 'sm';
return (
{m.label}
);
}
function CapacityBar({ rider, showLabel = true }) {
const pct = loadPct(rider);
const color = pct > 85 ? '#D93025' : pct > 60 ? '#F29900' : '#1A73E8';
return (
{showLabel && (
{rider.assigned}/{rider.capacity}
· {pct}%
)}
);
}
function RiderAvatar({ rider, size = 48 }) {
const m = STATUS_META[rider.status] || STATUS_META['Offline'];
return (
{initials(rider.name)}
);
}
function VehicleCell({ vehicle, vehicleNo }) {
const Icon = VEHICLES[vehicle]?.icon || LocalShippingOutlinedIcon;
return (
{vehicle}
{vehicleNo}
);
}
// KPI card — the shared StatCard is the single reference design (see components/StatCard).
// Kept as a thin alias so the call sites below stay unchanged; `hover` on for the lift effect.
const KpiCard = (props) => ;
// ════════════════════════════════════════════════════════════════════════════════
// Dialogs (Cleaner & Better Spaced)
// ════════════════════════════════════════════════════════════════════════════════
const STEPS = ['Personal', 'Assignment', 'Vehicle & KYC'];
function RiderFormDialog({ open, onClose, onSave, initial, mode }) {
const theme = useTheme();
const fullScreen = useMediaQuery(theme.breakpoints.down('sm'));
const [activeStep, setActiveStep] = useState(0);
const [form, setForm] = useState(initial || EMPTY_FORM);
const [errors, setErrors] = useState({});
React.useEffect(() => {
if (open) {
setForm(initial || EMPTY_FORM);
setErrors({});
setActiveStep(0);
}
}, [open, initial]);
const set = (k, v) => {
setForm(p => ({ ...p, [k]: v }));
setErrors(e => ({ ...e, [k]: undefined }));
};
const validateStep = (step) => {
const e = {};
if (step === 0) {
if (!form.name.trim()) e.name = 'Full name is required';
if (!form.phone.trim()) e.phone = 'Phone number is required';
else if (!/[0-9]{10}/.test(form.phone.replace(/\D/g, ''))) e.phone = 'Enter a valid 10-digit phone number';
}
if (step === 1) {
if (form.zones.length === 0) e.zones = 'Assign at least one service zone';
}
if (step === 2) {
if (!form.vehicle) e.vehicle = 'Select a vehicle type';
if (!form.vehicleNo.trim() && form.vehicle !== 'Cycle') e.vehicleNo = 'Vehicle number is required';
}
setErrors(e);
return Object.keys(e).length === 0;
};
const next = () => { if (validateStep(activeStep)) setActiveStep(s => s + 1); };
const back = () => setActiveStep(s => s - 1);
const submit = () => {
if (!validateStep(2)) return;
const cap = VEHICLES[form.vehicle]?.capacity || 30;
onSave({
...form,
vehicleNo: form.vehicleNo.trim() || '—',
id: mode === 'add' ? genId() : form.id,
capacity: cap,
checkInTime: mode === 'add' ? '—' : form.checkInTime,
hoursToday: mode === 'add' ? 0 : form.hoursToday,
assigned: mode === 'add' ? 0 : form.assigned,
pickupsPending: mode === 'add' ? 0 : form.pickupsPending,
deliveriesPending: mode === 'add' ? 0 : form.deliveriesPending,
deliveriesDone: mode === 'add' ? 0 : form.deliveriesDone,
deliveriesFailed: mode === 'add' ? 0 : form.deliveriesFailed,
codCollected: mode === 'add' ? 0 : form.codCollected,
codPending: mode === 'add' ? 0 : form.codPending,
rating: mode === 'add' ? 5.0 : form.rating,
});
};
return (
);
}
// Profile Drawer - view + inline edit, in the same right-side sheet
function ProfileDrawer({ rider: riderProp, onClose, onSave, startInEdit = false }) {
const [editing, setEditing] = useState(startInEdit);
// Retain the last opened miler so the sheet keeps rendering its content while it
// slides OUT — the prop becomes null on close, and unmounting here (an early
// `return null`) would kill the exit animation and make closing feel instant.
const [retained, setRetained] = useState(riderProp);
const [form, setForm] = useState(riderProp || {});
// Reset the editable copy + mode whenever a different miler is opened.
useEffect(() => {
if (riderProp) {
setRetained(riderProp);
setForm(riderProp);
setEditing(startInEdit);
}
}, [riderProp, startInEdit]);
const open = Boolean(riderProp); // drives the slide; false triggers the exit anim
const rider = riderProp || retained; // keep content during the close transition
// NOTE: we deliberately do NOT early-return when there's no rider. The Drawer stays
// mounted (open=false) from first render, so the first click is a real false→true
// transition and animates — otherwise the first open mounts already-open and skips it.
const sr = rider ? successRate(rider) : 0;
const setField = (key) => (e) => setForm((f) => ({ ...f, [key]: e.target.value }));
const cancelEdit = () => { setForm(rider); setEditing(false); };
const saveEdit = () => {
const updated = { ...rider, ...form, capacity: VEHICLES[form.vehicle]?.capacity ?? rider.capacity };
onSave(updated);
setEditing(false);
};
return (
t.zIndex.modal,
'& .MuiDrawer-paper': {
width: { xs: '100%', sm: 520 },
maxWidth: '100%',
// Run the sheet the full height of the viewport so there is no empty
// strip above the red header (zIndex.modal keeps Close clickable).
top: 0,
height: '100%',
borderTopLeftRadius: { sm: 16 },
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
boxShadow: '-8px 0 30px rgba(0,0,0,0.12)',
},
}}
>
{rider && (<>
{/* Header */}
} size="small"
sx={{ color: 'rgba(255,255,255,0.95)', textTransform: 'none', fontWeight: 600, mb: 1.25, ml: -0.5, '&:hover': { bgcolor: 'rgba(255,255,255,0.12)' } }}>
Close
{form.name || rider.name}
{rider.id}
{rider.verified && (
} label="Verified"
sx={{ bgcolor: 'rgba(255,255,255,0.18)', color: '#fff', fontWeight: 700 }} />
)}
{/* Content */}
{editing ? (
/* ── Inline edit form (same sheet) ── */
EDIT MILER DETAILS
Status
Vehicle
Hub
setForm((f) => ({ ...f, zones: v }))}
renderInput={(params) => }
/>
) : (
{/* Today's Performance */}
TODAY'S PERFORMANCE
{[
{ label: "Picked up", value: rider.deliveriesDone, icon: CheckCircleOutlinedIcon, color: "#16A34A", bg: "#DCFCE7" },
{ label: "Failed", value: rider.deliveriesFailed, icon: WarningAmberOutlinedIcon, color: "#DC2626", bg: "#FEE2E2" },
{ label: "COP", value: inr(rider.codCollected), icon: PaymentsOutlinedIcon, color: "#D97706", bg: "#FEF3C7" },
].map((stat, i) => (
))}
{/* Live Load Panel */}
LIVE LOAD
Pending pickups
{rider.pickupsPending}
{/* Miler Details Panel */}
MILER DETAILS
{[
{ icon: PhoneOutlinedIcon, label: "Phone", value: rider.phone },
{ icon: HomeWorkOutlinedIcon, label: "Hub", value: rider.hub },
{ icon: PlaceOutlinedIcon, label: "Zones", value: rider.zones?.join(', ') || 'N/A' },
{ icon: BadgeOutlinedIcon, label: "Vehicle", value: `${rider.vehicle} • ${rider.vehicleNo}` },
{ icon: AccessTimeOutlinedIcon, label: "Check-in", value: `${rider.checkInTime} (${rider.hoursToday}h)` },
].map((detail, idx) => (
{detail.label}
{detail.value}
))}
)}
{/* Footer */}
{editing ? (
<>
} onClick={saveEdit} sx={{ borderRadius: 2, bgcolor: '#C01227', '&:hover': { bgcolor: '#9E0E1F' } }}>
Save Changes
>
) : (
<>
} href={`tel:${rider.phone}`} sx={{ borderRadius: 2 }}>
Call Miler
} onClick={() => setEditing(true)} sx={{ borderRadius: 2, bgcolor: '#C01227', '&:hover': { bgcolor: '#9E0E1F' } }}>
Edit Details
>
)}
>)}
);
}
// Delete Dialog
function DeleteDialog({ rider, onClose, onConfirm }) {
return (
);
}
// Miler Card (Grid)
function RiderCard({ rider, onView, onMenu }) {
const pct = loadPct(rider);
const pctColor =
pct > 85 ? "#D93025" : pct > 60 ? "#F29900" : "#1A73E8";
return (
onView(rider)}
sx={{
height: "100%",
borderRadius: 2,
border: "1px solid #E8EAED",
cursor: "pointer",
transition: "all .25s ease",
bgcolor: "#fff",
"&:hover": {
transform: "translateY(-4px)",
borderColor: "#C01227",
boxShadow: "0 16px 40px rgba(0,0,0,.08)",
},
}}
>
{/* Header */}
{rider.name}
{rider.verified && (
)}
{rider.id}
{
e.stopPropagation();
onMenu(e, rider);
}}
>
{/* Status */}
{/* Load */}
Today's Load
{rider.assigned}/{rider.capacity}
{pct}%
{/* Zones */}
SERVICE ZONES
{rider.zones.slice(0, 3).map((z) => (
))}
{rider.zones.length > 3 && (
)}
);
}
// ════════════════════════════════════════════════════════════════════════════════
// MAIN COMPONENT
// ════════════════════════════════════════════════════════════════════════════════
export default function Riders() {
const theme = useTheme();
const isMdDown = useMediaQuery(theme.breakpoints.down('md'));
const isLgDown = useMediaQuery(theme.breakpoints.down('lg'));
const hub = getHubContext();
const hubName = hub.hubname || 'This Hub';
const [riders, setRiders] = useState([]);
const [loading, setLoading] = useState(true);
const [loadError, setLoadError] = useState('');
const [search, setSearch] = useState('');
const [statusFilter, setStatusFilter] = useState('All');
const [vehicleFilter, setVehicleFilter] = useState('All');
const [view, setView] = useState('table');
const loadMilers = useCallback(async () => {
setLoading(true);
setLoadError('');
try {
const res = await getMilers();
setRiders((res?.data || []).map((m) => mapMiler(m, hubName)));
} catch (err) {
setLoadError(err?.message || 'Could not load milers.');
} finally {
setLoading(false);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
loadMilers();
}, [loadMilers]);
const [formDialog, setFormDialog] = useState({ open: false, mode: 'add', initial: null });
const [profile, setProfile] = useState(null);
const [profileEdit, setProfileEdit] = useState(false);
const [deleteTarget, setDeleteTarget] = useState(null);
const [menuAnchor, setMenuAnchor] = useState(null);
const [menuRider, setMenuRider] = useState(null);
const [snack, setSnack] = useState({ open: false, msg: '', severity: 'success' });
const toast = (msg, severity = 'success') => setSnack({ open: true, msg, severity });
const effectiveView = isLgDown ? 'grid' : view;
const kpi = useMemo(() => {
const onDuty = riders.filter(r => !['Offline', 'Suspended'].includes(r.status)).length;
const onField = riders.filter(r => ['On Pickup', 'Returning to Hub'].includes(r.status)).length;
const idle = riders.filter(r => r.status === 'Idle').length;
const slaRisk = riders.filter(r => loadPct(r) > 85 || r.deliveriesFailed >= 5).length;
const codToCollect = riders.reduce((s, r) => s + r.codPending, 0);
return { onDuty, onField, idle, slaRisk, codToCollect, total: riders.length };
}, [riders]);
const filtered = useMemo(() => {
const q = search.trim().toLowerCase();
return riders.filter(r => {
const matchSearch = !q ||
r.name.toLowerCase().includes(q) ||
r.id.toLowerCase().includes(q) ||
r.phone.includes(q) ||
r.zones.some(z => z.toLowerCase().includes(q));
const matchStatus = statusFilter === 'All' || r.status === statusFilter;
const matchVehicle = vehicleFilter === 'All' || r.vehicle === vehicleFilter;
return matchSearch && matchStatus && matchVehicle;
});
}, [riders, search, statusFilter, vehicleFilter]);
// Build the API payload from the rich form object.
const toApiPayload = (rider) => ({
displayname: rider.name,
phone: rider.phone,
hubid: rider.hubid || hub.hubid,
vehicleid: rider.vehicleid || vehicleIdFor(rider.vehicle),
availabilitystatus: UI_TO_API_STATUS[rider.status] || 'Available'
});
const handleSave = async (rider) => {
try {
if (formDialog.mode === 'add') {
await createMiler(toApiPayload(rider));
toast(`${rider.name} onboarded successfully`);
} else {
await updateMiler(rider.userid ?? rider.id, toApiPayload(rider));
toast(`${rider.name} updated`);
}
setFormDialog({ open: false, mode: 'add', initial: null });
loadMilers();
} catch (err) {
toast(err?.message || 'Could not save this miler.', 'error');
}
};
const handleDelete = async () => {
const target = deleteTarget;
try {
await deleteMiler(target.userid ?? target.id);
setRiders((p) => p.filter((r) => r.id !== target.id));
toast(`${target.name} removed`, 'info');
} catch (err) {
toast(err?.message || 'Could not remove this miler.', 'error');
} finally {
setDeleteTarget(null);
setProfile(null);
}
};
const openAdd = () => setFormDialog({ open: true, mode: 'add', initial: null });
// Edit now opens the same right-side sheet in edit mode (no separate modal).
const openEdit = (r) => {
setMenuAnchor(null);
setProfile(r);
setProfileEdit(true);
};
const openView = (r) => {
setMenuAnchor(null);
setProfile(r);
setProfileEdit(false);
};
const saveProfileEdit = async (updated) => {
try {
await updateMiler(updated.userid ?? updated.id, toApiPayload(updated));
setRiders((p) => p.map((r) => (r.id === updated.id ? updated : r)));
setProfile(updated);
setProfileEdit(false);
toast(`${updated.name} updated`);
} catch (err) {
toast(err?.message || 'Could not update this miler.', 'error');
}
};
const openMenu = (e, r) => {
setMenuAnchor(e.currentTarget);
setMenuRider(r);
};
const STATUS_CHIPS = ['All', 'On Pickup', 'Idle', 'On Break', 'Offline', 'Suspended'];
return (
{/* Header */}
{/* ── Improved Header with better gaps ── */}
{/* Title & Avatar Branding Group */}
Milers
{kpi.total} milers • who's working and where
{/* Actions Group - pushed to the far right with clear spacing between buttons */}
}
onClick={() => toast("Exported as CSV")}
sx={{
width: { xs: "100%", sm: "auto" },
borderRadius: 2,
px: 3,
height: 48,
textTransform: "none",
fontWeight: 600,
mr: { sm: 1 }, // Extra gap on desktop
}}
>
Export Roster
}
onClick={openAdd}
sx={{
width: { xs: "100%", sm: "auto" },
borderRadius: 2,
bgcolor: "#C01227",
px: 3.5,
height: 48,
textTransform: "none",
fontWeight: 600,
boxShadow: "none",
"&:hover": {
bgcolor: "#9E0E1F",
boxShadow: "none",
},
}}
>
Add New Miler
{/* KPIs */}
{[
{ icon: GroupsOutlinedIcon, label: 'On Duty', value: kpi.onDuty, sub: `of ${kpi.total}`, color: '#1A73E8', bg: '#E8F0FE', trend: true },
{ icon: RouteOutlinedIcon, label: 'In Field', value: kpi.onField, sub: 'Active pickups', color: '#1E8E3E', bg: '#E6F4EA' },
{ icon: AccessTimeOutlinedIcon, label: 'Available', value: kpi.idle, sub: 'Ready for assignment', color: '#B06000', bg: '#FEF7E0' },
{ icon: WarningAmberOutlinedIcon, label: 'SLA Risk', value: kpi.slaRisk, sub: 'Overloaded milers', color: '#D93025', bg: '#FCE8E6' },
{ icon: PaymentsOutlinedIcon, label: 'COP Pending', value: inr(kpi.codToCollect), sub: 'To be collected', color: '#8E24AA', bg: '#F3E5F5' },
].map((k, i) => (
))}
{/* Main Card */}
{/* Toolbar */}
setSearch(e.target.value)}
InputProps={{
startAdornment: (
),
sx: { borderRadius: 2 },
}}
sx={{
maxWidth: { lg: 420 },
}}
/>
{!isLgDown && (
v && setView(v)}
sx={{ borderRadius: 2 }}
>
)}
{/* Status Chips */}
{STATUS_CHIPS.map(s => {
const active = statusFilter === s;
const meta = STATUS_META[s] || {};
// Each status carries its own colour; "All" falls back to a neutral dark.
const accent = meta.color || '#1A1A2E';
const tint = meta.bg || '#F8F9FB';
return (
setStatusFilter(s)}
icon={meta.color ? : undefined}
sx={{
height: 38,
px: 1,
borderRadius: 2,
fontSize: '0.82rem',
fontWeight: active ? 700 : 600,
bgcolor: active ? accent : tint,
color: active ? '#fff' : accent,
border: active ? 'none' : `1px solid ${alpha(accent, 0.25)}`,
'& .MuiChip-icon': { color: active ? '#fff' : meta.color },
'& .MuiChip-label': { px: 1.25 },
'&:hover': { bgcolor: active ? accent : alpha(accent, 0.14) }
}}
/>
);
})}
{/* Content Area */}
{loadError && (
setLoadError('')} sx={{ m: 2, borderRadius: 2 }}>
{loadError}
)}
{loading ? (
) : filtered.length === 0 ? (
No milers match your filters
) : effectiveView === 'table' ? (
Miler
Status
Zones
Vehicle
Load
Today
{filtered.map(rider => (
openView(rider)}>
{rider.name}
{rider.id}
{rider.zones.map(z => )}
Done
{rider.deliveriesDone}
COP
{inr(rider.codCollected)}
{ e.stopPropagation(); openView(rider); }}>
{ e.stopPropagation(); openEdit(rider); }}>
{ e.stopPropagation(); openMenu(e, rider); }}>
))}
) : (
{filtered.map(r => (
))}
)}
{filtered.length > 0 && (
Showing {filtered.length} of {riders.length} milers
)}
{/* Context Menu */}
{/* Dialogs */}
setFormDialog({ open: false, mode: 'add', initial: null })} onSave={handleSave} />
{ setProfile(null); setProfileEdit(false); }}
onSave={saveProfileEdit}
/>
setDeleteTarget(null)} onConfirm={handleDelete} />
{/* Toast */}
setSnack(s => ({ ...s, open: false }))}>
{snack.msg}
);
}