update Riders UI layout, refine status chip styling, and remove API documentation file

This commit is contained in:
2026-07-08 18:39:57 +05:30
parent e650ecf88b
commit f53578c520
5 changed files with 23 additions and 906 deletions

View File

@@ -1,889 +0,0 @@
# Doormile Hub Console — Frontend Integration Guide
**Base URL:** `https://api.doormile.com`
**Auth:** Bearer token in `Authorization` header on every authenticated request
**Content-Type:** `application/json` on all POST / PATCH calls
## How the flow works
1. Staff opens the console → Login page
2. `POST /api/v1/hub/login` → receives JWT + hub context
3. Store token + hub context in localStorage
4. Every page reads hub name/city from stored context (no extra call)
5. Every API call sends `Authorization: Bearer <token>`
6. On 401 → clear localStorage → redirect to `/login`
---
## 1. Authentication
### POST /api/v1/hub/login
No auth header needed.
Request body:
```json
{
"email": "hub.coimbatore@doormile.in",
"password": "password123"
}
```
Response:
```json
{
"success": true,
"token": "eyJhbGci...",
"hub": {
"hubid": 1,
"hubname": "Coimbatore Jupiter Hub",
"hubtype": "sorting_center",
"city": "Coimbatore",
"capacity": 50
},
"staff": {
"displayname": "Coimbatore Hub Staff",
"email": "hub.coimbatore@doormile.in",
"role": 6
},
"is_doormile_staff": true
}
```
What to store in localStorage after login:
```js
localStorage.setItem('hub_token', data.token)
localStorage.setItem('hub_context', JSON.stringify(data.hub))
localStorage.setItem('hub_staff', JSON.stringify(data.staff))
localStorage.setItem('hub_is_doormile', String(data.is_doormile_staff))
```
Test accounts:
| Email | Password | Hub | Access |
|---|---|---|---|
| hub.coimbatore@doormile.in | password123 | Coimbatore Jupiter Hub | Full (Doormile staff) |
| hub.hyderabad@doormile.in | password123 | Hyderabad hub | Full (Doormile staff) |
| hub.bangalore@doormile.in | password123 | Bangalore hub | Full (Doormile staff) |
| hub.chennai@doormile.in | password123 | Chennai hub | Full (Doormile staff) |
| hub@kpmtravels.in | password123 | Hub 1 | Restricted (partner — no hub management) |
---
## 2. Dashboard
### GET /api/v1/hub/dashboard
Returns live KPI stats for the logged-in staff's hub.
Response:
```json
{
"success": true,
"data": {
"parcels_received_today": 142,
"milers_available": 6,
"milers_on_duty": 12,
"pending_pickups": 8,
"batches_sent_today": 3,
"exceptions": 2
}
}
```
Which UI element uses which field:
| Dashboard card | Field |
|---|---|
| Total Parcels Received | `parcels_received_today` |
| Available Milers | `milers_available` |
| Milers on Duty | `milers_on_duty` |
| Pending Pickups | `pending_pickups` |
| Batches Sent Today | `batches_sent_today` |
| Needs Checking | `exceptions` |
**Note:** Hub name shown in the header/title comes from localStorage (`hub_context.hubname`) — not from this endpoint. No extra call needed.
---
## 3. Inbound — Receive Parcels
### GET /api/v1/hub/inbound/today
Returns all parcels inbounded at this hub today. Now returns `sendername`, `originname`, `destinationname`, `temperature` (readable names instead of raw IDs and pincodes).
Response:
```json
{
"success": true,
"data": [
{
"bookingid": 15,
"trackingnumber": "DM-882201",
"sendername": "Acme Corp",
"originname": "Mumbai Hub",
"destinationname": "Dwarka Sec 12, Coimbatore",
"weight": "2.4 kg",
"condition": "Good",
"temperature": "N/A",
"shelf": "Zone A (Shelf 1)",
"inboundedat": "2026-07-04T09:15:00Z"
}
],
"total": 1
}
```
### POST /api/v1/hub/bookings/:id/inbound
Scan a parcel in. `:id` is the booking/consignment ID.
Request body:
```json
{
"tracking_id": "DM-882201",
"condition": "Good",
"temperature": "N/A",
"shelf": "Zone A (Shelf 1)",
"weight": "2.4 kg"
}
```
Condition values: `Good` · `Damaged Box` · `Wet / Crushed` · `Missing Label`
Temperature: Free text — "4.1°C" for cold chain items, "N/A" for dry goods
Response:
```json
{
"success": true,
"data": {
"bookingid": 15,
"trackingnumber": "DM-882201",
"recommended_shelf": "Zone A (Shelf 1)",
"status": "At_Hub"
}
}
```
Shelf recommendation logic (backend handles this automatically):
- Condition contains Damaged / Wet / Missing → Exception Area
- Temperature is not N/A → Zone C (Cold Room)
- Otherwise → Zone A or Zone B based on destination
Error — tracking ID not found:
```json
{
"success": false,
"message": "booking not found"
}
```
HTTP 404.
---
## 4. Order Assignment — Pickup Requests
### GET /api/v1/hub/bookings/unassigned
Returns all pending pickup requests for this hub's city.
Response:
```json
{
"success": true,
"data": [
{
"bookingid": 21,
"customerName": "Ramesh K.",
"pickupaddress": "Gandhipuram, Coimbatore",
"deliveryaddress": "Hitech City, Hyderabad",
"packagedescription": "Small Box, 2kg",
"createdat": "2026-07-04T08:45:00Z",
"status": "Pending_Pickup"
}
],
"total": 3
}
```
For assigning a miler to these bookings, see **Section 14 — Hub Assignment Override**.
---
## 5. Dispatch & Batches
### GET /api/v1/hub/batches
Returns all outgoing batches for this hub.
Response:
```json
{
"success": true,
"data": [
{
"tripsheetid": 1,
"batchlabel": "BATCH-9281",
"batchkind": "transfer",
"destinationlabel": "Mumbai Hub (BOM-02)",
"vehicle": "Truck DL-01-BZ-8055",
"itemcount": 154,
"status": "Draft",
"createdat": "2026-07-04T08:00:00Z",
"dispatchtime": null
}
],
"total": 1
}
```
Status values: `Draft``Ready``Dispatched`
Batchkind values: `local` · `transfer`
### POST /api/v1/hub/batches
Create a new outgoing batch.
Request body:
```json
{
"route": "Transfer to Mumbai Hub",
"destination": "Mumbai Hub (BOM-02)",
"vehicle": "Truck DL-01-BZ-8055",
"parcels_count": 154,
"kind": "transfer"
}
```
Response:
```json
{
"success": true,
"data": {
"tripsheetid": 2,
"batchlabel": "BATCH-9282",
"status": "Draft"
}
}
```
### PATCH /api/v1/hub/batches/:id/status
Move a batch through its status flow.
Request body — mark as ready:
```json
{ "status": "Ready" }
```
Request body — dispatch (send out):
```json
{ "status": "Dispatched" }
```
Response:
```json
{
"success": true,
"data": {
"tripsheetid": 1,
"status": "Ready",
"dispatchtime": null
}
}
```
Invalid status transition → 400:
```json
{
"success": false,
"message": "invalid status"
}
```
---
## 6. Milers
### GET /api/v1/hub/milers
Returns all milers assigned to this hub, now with operational stats per miler.
Response:
```json
{
"success": true,
"data": [
{
"userid": 4,
"displayname": "Rajesh Kumar",
"phone": "+91 98765 43210",
"vehicleid": 1,
"hubid": 1,
"availabilitystatus": "Available",
"rating": 4.8,
"completedorders": 45,
"cancelledorders": 2,
"currentlat": 11.0168,
"currentlon": 76.9558,
"device_token": "fcm_token_here",
"zones": ["641001", "641012"],
"assignedload": 2,
"capacity": 30,
"pickupspending": 1,
"codcollected": 4500.00,
"codpending": 1200.00,
"checkinat": "2026-07-04T08:12:00Z",
"hoursactive": 6.4,
"isverified": true
}
],
"total": 6
}
```
Availability status values: `Available` · `Assigned` · `On_Break` · `Offline`
The `zones`, `assignedload`, `capacity`, `pickupspending`, `codcollected`, `codpending`, `checkinat`, `hoursactive`, `isverified` fields were the empty fields the Milers page was trying to show. Now populated from real data.
### POST /api/v1/admin/milers
Create (onboard) a new miler. Hub JWT is accepted here.
Request body:
```json
{
"displayname": "Muthu Kumar",
"phone": "+91 90011 22334",
"hubid": 1,
"vehicleid": 2,
"availabilitystatus": "Available"
}
```
Response:
```json
{
"success": true,
"data": {
"userid": 10,
"displayname": "Muthu Kumar"
}
}
```
### PATCH /api/v1/admin/milers/:id
Update a miler's details or status.
Request body (any subset of fields):
```json
{
"availabilitystatus": "On_Break",
"hubid": 2
}
```
### DELETE /api/v1/admin/milers/:id
Remove a miler from the roster.
Response:
```json
{
"success": true,
"message": "miler removed"
}
```
---
## 7. Routing — Where Does It Go?
### GET /api/v1/hub/routing/:trackingno
Scan a parcel and get its sort destination. `:trackingno` is the tracking number scanned at the sort station.
Response:
```json
{
"success": true,
"data": {
"trackingno": "DM-TRK-0015",
"destination": "Peelamedu, 641004",
"recommendedshelf": "Zone B",
"nexthop": "Local delivery",
"condition": "Good",
"iscoldchain": false,
"weight": "2.4 kg",
"customername": "Ramesh K.",
"bookingid": 15
}
}
```
`nexthop` values: `Local delivery` · `Transfer to Mumbai Hub` (or whichever destination hub).
Use `recommendedshelf` to show the big shelf indicator UI.
Error — tracking number not found → 404. Show a "Parcel not found" error state.
```json
{
"success": false,
"message": "parcel not found"
}
```
---
## 8. Rider Routes
### GET /api/v1/hub/rider-routes
Returns all milers at this hub with their planned stops today.
### GET /api/v1/hub/milers/:id/route
Returns the planned stops for a single miler. `:id` is the miler user ID.
Response (same `data[]` shape for both; the single-miler endpoint returns one entry):
```json
{
"success": true,
"data": [
{
"mileruserid": 4,
"milername": "Rajesh Kumar",
"mode": "pickup",
"totalstops": 5,
"completedstops": 2,
"totaldistance_km": 12.4,
"stops": [
{ "seq": 1, "address": "Gandhipuram, Coimbatore", "lat": 11.0168, "lon": 76.9558, "items": 1, "bookingid": 20, "status": "completed", "eta_minutes": 0 },
{ "seq": 2, "address": "RS Puram, Coimbatore", "lat": 11.001, "lon": 76.951, "items": 2, "bookingid": 21, "status": "pending", "eta_minutes": 12 }
]
}
]
}
```
`mode` values: `pickup` · `delivery`
Stop status values: `pending` · `in_progress` · `completed`
Pass `stops[].lat/lon` to OSRM to draw the route line — the OSRM call stays client-side.
---
## 9. Live Trucks
### GET /api/v1/hub/tripsheets/in-transit
Returns transfer trucks currently in transit, with interpolated live positions.
Poll this every 5 seconds alongside miler locations.
Response:
```json
{
"success": true,
"data": [
{
"tripsheetid": 3,
"tripsheetno": "DM-TS-0003",
"label": "Chennai Jupiter Hub → Coimbatore Jupiter Hub",
"originlat": 13.0827, "originlon": 80.2707,
"destlat": 11.0168, "destlon": 76.9558,
"currentlat": 12.1, "currentlon": 78.9,
"status": "In_Transit",
"progresspct": 40,
"vehicleno": "TN-04-AX-8822",
"itemcount": 154,
"eta_minutes": 180
}
]
}
```
`currentlat/lon` gives the truck's position on the map. Replace the fake animated truck with these real coordinates.
---
## 10. Arriving Vehicles
### GET /api/v1/hub/inbound/vehicles
Returns trucks arriving at this hub (Dashboard → "Trucks Arriving" table).
Response:
```json
{
"success": true,
"data": [
{
"vehicleno": "TN-04-AX-8822",
"origin": "Chennai Jupiter Hub",
"tripsheetid": 3,
"status": "On the way",
"eta": "2 hrs 30 min",
"unloadedpct": 0,
"itemcount": 154
}
]
}
```
Status values: `On the way` · `Unloading`
(Dispatched maps to `On the way`, Arrived maps to `Unloading`.)
---
## 11. Activity Feed
### GET /api/v1/hub/activity?limit=10
Returns a real event feed for this hub (UNION across 4 sources: inbound, dispatch, exceptions, sorting).
Response:
```json
{
"success": true,
"data": [
{ "time": "2026-07-04T11:24:00Z", "type": "exception", "text": "Exception raised: Damaged on DM-TRK-999999" },
{ "time": "2026-07-04T11:22:00Z", "type": "inbound", "text": "Received parcel DM-TRK-999999 from Coimbatore Jupiter Hub" },
{ "time": "2026-07-04T11:20:00Z", "type": "dispatch", "text": "Dispatched batch DM-TS-0003 to Mumbai Hub" }
],
"total": 3
}
```
Type values: `inbound` · `dispatch` · `exception` · `sorting`
Use `type` to pick the icon color — same color scheme as the existing mock.
---
## 12. Zones
### GET /api/v1/hub/zones
Returns the delivery zone breakdown for this hub (Dashboard → "Delivery Areas").
Response:
```json
{
"success": true,
"data": [
{ "zone": "641001", "zonename": "Gandhipuram", "parcels": 12, "milers": 2, "status": "Active" },
{ "zone": "641004", "zonename": "Peelamedu", "parcels": 8, "milers": 1, "status": "Active" },
{ "zone": "641012", "zonename": "Jupiter Nagar","parcels": 3, "milers": 0, "status": "Need Milers" }
]
}
```
Status values: `Active` · `Need Milers` — use this to color the status chip.
---
## 13. Notifications
### GET /api/v1/hub/notifications
Returns notifications for the header bell icon, generated from real events.
Response:
```json
{
"success": true,
"data": [
{ "id": 1, "title": "Exception: Damaged", "type": "exception", "time": "3 min ago", "read": false },
{ "id": 2, "title": "Truck arriving from Chennai Hub", "type": "inbound", "time": "5 min ago", "read": false },
{ "id": 3, "title": "Pickup waiting 30+ min", "type": "alert", "time": "12 min ago", "read": false }
],
"total": 3
}
```
Type values: `exception` · `inbound` · `warning` · `alert`
### PATCH /api/v1/hub/notifications/:id/read
Mark a notification as read.
Response:
```json
{ "success": true }
```
On bell click → `GET /hub/notifications`. On item click → `PATCH /hub/notifications/:id/read`, then mark it read in local state.
---
## 14. Hub Assignment Override
The old admin assign endpoint (`POST /admin/bookings/:id/assign-miler`) returned **403** for hub staff. Two new hub-scoped endpoints replace it.
### POST /api/v1/hub/bookings/:id/assign-miler
Manual assign — hub staff picks the miler.
Request body:
```json
{ "mileruserid": 4 }
```
Response:
```json
{
"success": true,
"data": {
"bookingid": 21,
"mileruserid": 4,
"milername": "Rajesh Kumar",
"status": "Miler_Assigned"
}
}
```
Use this on the **Pickup Requests** page when staff manually selects a miler from the dialog.
### POST /api/v1/hub/bookings/:id/auto-assign
Auto-assign — AI engine picks the miler.
Request body:
```json
{}
```
Response (success):
```json
{
"success": true,
"data": {
"mileruserid": 4,
"milername": "Rajesh Kumar",
"distance_km": 0.4
}
}
```
Response (no miler) — HTTP 422:
```json
{
"success": false,
"message": "No eligible miler found in range"
}
```
Response (timeout) — HTTP 202:
```json
{
"success": true,
"message": "assignment in progress"
}
```
Use this for the **Auto-Assign** bulk button. Loop over selected booking IDs and call this for each one.
**Backend implementation notes:** shared `AssignMilerToBooking()` service (`booking_assignment_service.go`), `TryAssignOnce()` single-attempt wrapper on `crm_assignment`, `assignedbyuserid` is nullable for hub-initiated assignments, and `AdminAssignMiler` now sends FCM to the miler.
---
## 15. Hub Management (Doormile staff only)
These endpoints return 403 for partner accounts (`is_doormile_staff = false`).
Use the `is_doormile_staff` flag from localStorage to show/hide the UI for these.
### GET /api/v1/hub/hubs
List all hubs in the logged-in staff's city. Now includes `lat`/`lon` for map pins.
Response:
```json
{
"success": true,
"data": [
{
"hubid": 1,
"hubname": "Coimbatore Jupiter Hub",
"hubtype": "sorting_center",
"capacity": 50,
"status": "active",
"has_staff": true,
"lat": 11.0168,
"lon": 76.9558
},
{
"hubid": 2,
"hubname": "Coimbatore Spoke 1",
"hubtype": "spoke",
"capacity": 50,
"status": "active",
"has_staff": false,
"lat": 11.0210,
"lon": 76.9610
}
]
}
```
Use `lat/lon` to place hub pins on the Live Map.
### POST /api/v1/hub/hubs
Create a new hub in the staff's city.
Request body:
```json
{
"hubname": "Coimbatore RS Puram Hub",
"hubtype": "spoke",
"capacity": 30,
"contact": "+91 98765 00000",
"address": "RS Puram, Coimbatore",
"pincode": "641002"
}
```
**Note:** `applocationid` (city) is set automatically from the logged-in staff's hub — cannot be overridden.
### POST /api/v1/hub/staff
Create a hub staff login for any hub in the city.
Request body:
```json
{
"hubid": 2,
"email": "hub.rspuram@doormile.in",
"password": "securepassword",
"displayname": "RS Puram Hub Staff",
"tenantid": null
}
```
Set `tenantid` to the partner's tenant ID for partner accounts, `null` for Doormile staff.
Response:
```json
{
"success": true,
"data": {
"staffid": 6,
"email": "hub.rspuram@doormile.in",
"hubid": 2
}
}
```
---
## Error response shape
All errors follow the same shape:
```json
{
"success": false,
"message": "human readable error description"
}
```
| HTTP code | Meaning |
|---|---|
| 400 | Bad request — invalid body or status transition |
| 401 | Token expired or missing → redirect to /login |
| 202 | Accepted — async assignment in progress |
| 403 | Forbidden — partner account trying Doormile-only endpoint |
| 404 | Resource not found (booking ID, miler ID, tracking no etc.) |
| 422 | Unprocessable — no eligible miler found in range |
| 500 | Server error — show generic "something went wrong" toast |
---
## Page-to-endpoint map (quick reference)
| Page | Endpoints called |
|---|---|
| Login | POST /hub/login |
| Dashboard | GET /hub/dashboard · GET /hub/inbound/vehicles · GET /hub/activity · GET /hub/zones |
| Receive Parcels | GET /hub/inbound/today · POST /hub/bookings/:id/inbound |
| Pickup Requests | GET /hub/bookings/unassigned · GET /hub/milers · POST /hub/bookings/:id/assign-miler · POST /hub/bookings/:id/auto-assign |
| Where Does It Go? (Routing) | GET /hub/routing/:trackingno |
| Dispatch | GET /hub/batches · POST /hub/batches · PATCH /hub/batches/:id/status |
| Milers | GET /hub/milers · POST /admin/milers · PATCH /admin/milers/:id · DELETE /admin/milers/:id |
| Rider Routes | GET /hub/rider-routes · GET /hub/milers/:id/route |
| Live Map | GET /admin/milers/locations (poll 5s) · GET /hub/tripsheets/in-transit (poll 5s) · GET /hub/hubs |
| Header — Notifications | GET /hub/notifications · PATCH /hub/notifications/:id/read |
| Hub Settings | GET /hub/hubs · POST /hub/hubs · POST /hub/staff |
---
## Frontend implementation notes
**Token storage:**
```js
// On login success:
localStorage.setItem('hub_token', response.token)
localStorage.setItem('hub_context', JSON.stringify(response.hub))
localStorage.setItem('hub_staff', JSON.stringify(response.staff))
localStorage.setItem('hub_is_doormile', String(response.is_doormile_staff))
// On every API call:
headers: { 'Authorization': `Bearer ${localStorage.getItem('hub_token')}` }
// On logout or 401:
localStorage.clear()
navigate('/login')
```
**Show hub name dynamically (not hardcoded):**
```js
const hub = JSON.parse(localStorage.getItem('hub_context'))
// hub.hubname → "Coimbatore Jupiter Hub"
// hub.city → "Coimbatore"
// hub.hubid → 1
```
**Hide hub management UI for partners:**
```js
const isDoormile = localStorage.getItem('hub_is_doormile') === 'true'
// Show "Add New Hub" / "Add Staff" buttons only if isDoormile === true
// KPM partner account gets false → those buttons hidden
```
**On 401 — token expired, force re-login:**
```js
if (response.status === 401) {
localStorage.clear()
window.location.href = '/login'
}
```
**Polling intervals:**
- Miler locations → every 5 seconds
- Truck positions → every 5 seconds (same call cycle)
- Dashboard stats → every 30 seconds
- Notifications → every 60 seconds
---
*All endpoints live at `https://api.doormile.com` · Hub Console Backend v1.0 complete*

View File

@@ -28,8 +28,8 @@ import { setSession } from '@/auth/session';
export default function Login() {
const navigate = useNavigate();
const [show, setShow] = useState(false);
const [auth, setAuth] = useState('hub.coimbatore@doormile.in');
const [pwd, setPwd] = useState('password123');
const [auth, setAuth] = useState('');
const [pwd, setPwd] = useState('');
const [loading, setLoading] = useState(false);
const [error, setError] = useState('');

View File

@@ -259,7 +259,7 @@ export default function Dispatch() {
<Card key={m.id} elevation={0} sx={{ borderRadius: 3, border: '1px solid #ECEEF1' }}>
<CardContent sx={{ p: 2.25, '&:last-child': { pb: 2.25 } }}>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1.5 }} gap={1}>
<Stack direction="row" alignItems="center" gap={1.5} sx={{ minWidth: 0 }}>
<Stack direction="row" alignItems="center" spacing={2.25} sx={{ minWidth: 0 }}>
<Avatar variant="rounded" sx={{ bgcolor: meta.bg, color: meta.color, borderRadius: 2, width: 38, height: 38 }}>
<KindIcon sx={{ fontSize: 20 }} />
</Avatar>
@@ -309,7 +309,7 @@ export default function Dispatch() {
return (
<TableRow key={m.id} hover sx={{ '& td': { borderBottom: '1px solid #F4F6F8', py: 2.25 }, '&:last-child td': { border: 0 } }}>
<TableCell>
<Stack direction="row" alignItems="center" gap={1.5}>
<Stack direction="row" alignItems="center" spacing={2.25}>
<Avatar variant="rounded" sx={{ bgcolor: meta.bg, color: meta.color, borderRadius: 2, width: 38, height: 38 }}>
<KindIcon sx={{ fontSize: 20 }} />
</Avatar>

View File

@@ -548,14 +548,14 @@ export default function RiderRoutes() {
)}
{/* ── Analysis KPI strip ── */}
<Grid container spacing={2.5} mt={4} sx={{ mb: 2 }}>
<Grid container spacing={{ xs: 1.5, sm: 2 }} mt={4} sx={{ mb: 3 }}>
{[
{ icon: GroupsOutlinedIcon, label: 'Active Milers', value: kpi.activeRiders, sub: `of ${riders.length}`, color: '#1A73E8', bg: '#E8F0FE' },
{ icon: modeCfg.icon, label: modeCfg.label, value: kpi.orders, sub: 'pickups covered', color: '#C01227', bg: alpha('#C01227', 0.1) },
{ icon: CheckCircleRoundedIcon, label: modeCfg.doneLabel, value: kpi.done, sub: `${kpi.fail} missed`, color: '#1E8E3E', bg: '#E6F4EA' },
{ icon: StraightenRoundedIcon, label: 'Distance', value: `${kpi.km} km`, sub: 'fleet total today', color: '#8E24AA', bg: '#F3E5F5' },
].map((k, i) => (
<Grid item xs={6} sm={6} md={3} key={i}><KpiCard {...k} /></Grid>
<Grid size={{ xs: 6, md: 3 }} key={i}><KpiCard {...k} /></Grid>
))}
</Grid>

View File

@@ -527,10 +527,12 @@ function ProfileDrawer({ rider, onClose, onSave, startInEdit = false }) {
// Close button would hide under the app bar. Lift the whole modal above it.
zIndex: (t) => t.zIndex.modal,
'& .MuiDrawer-paper': {
width: { xs: '100%', sm: 380 },
width: { xs: '100%', sm: 480 },
maxWidth: '100%',
top: { xs: 0, sm: 64 },
height: { xs: '100%', sm: 'calc(100% - 64px)' },
// 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',
@@ -1185,7 +1187,7 @@ export default function Riders() {
</Stack>
{/* KPIs */}
<Grid container spacing={{ xs: 2, md: 3 }} sx={{ mt: 2, mb: 2 }}>
<Grid container spacing={{ xs: 1.5, sm: 2 }} sx={{ mb: 3 }}>
{[
{ 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' },
@@ -1193,7 +1195,7 @@ export default function Riders() {
{ 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) => (
<Grid item xs={6} sm={4} md={2.4} key={i}>
<Grid size={{ xs: 6, md: 2.4 }} key={i}>
<KpiCard {...k} />
</Grid>
))}
@@ -1271,24 +1273,28 @@ export default function Riders() {
{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 (
<Chip
key={s}
label={s}
clickable
onClick={() => setStatusFilter(s)}
icon={meta.color ? <CircleRoundedIcon sx={{ fontSize: 10, color: meta.color }} /> : undefined}
icon={meta.color ? <CircleRoundedIcon sx={{ fontSize: 10, color: active ? '#fff' : meta.color }} /> : undefined}
sx={{
height: 38,
px: 1,
borderRadius: 2,
fontSize: '0.82rem',
fontWeight: active ? 700 : 500,
bgcolor: active ? '#1A1A2E' : '#F8F9FB',
color: active ? '#fff' : '#5F6368',
border: active ? 'none' : '1px solid #ECEEF1',
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 ? '#1A1A2E' : '#F1F3F5' }
'&:hover': { bgcolor: active ? accent : alpha(accent, 0.14) }
}}
/>
);