update Riders UI layout, refine status chip styling, and remove API documentation file
This commit is contained in:
@@ -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*
|
|
||||||
@@ -28,8 +28,8 @@ import { setSession } from '@/auth/session';
|
|||||||
export default function Login() {
|
export default function Login() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [show, setShow] = useState(false);
|
const [show, setShow] = useState(false);
|
||||||
const [auth, setAuth] = useState('hub.coimbatore@doormile.in');
|
const [auth, setAuth] = useState('');
|
||||||
const [pwd, setPwd] = useState('password123');
|
const [pwd, setPwd] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState('');
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
|||||||
@@ -259,7 +259,7 @@ export default function Dispatch() {
|
|||||||
<Card key={m.id} elevation={0} sx={{ borderRadius: 3, border: '1px solid #ECEEF1' }}>
|
<Card key={m.id} elevation={0} sx={{ borderRadius: 3, border: '1px solid #ECEEF1' }}>
|
||||||
<CardContent sx={{ p: 2.25, '&:last-child': { pb: 2.25 } }}>
|
<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" 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 }}>
|
<Avatar variant="rounded" sx={{ bgcolor: meta.bg, color: meta.color, borderRadius: 2, width: 38, height: 38 }}>
|
||||||
<KindIcon sx={{ fontSize: 20 }} />
|
<KindIcon sx={{ fontSize: 20 }} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
@@ -309,7 +309,7 @@ export default function Dispatch() {
|
|||||||
return (
|
return (
|
||||||
<TableRow key={m.id} hover sx={{ '& td': { borderBottom: '1px solid #F4F6F8', py: 2.25 }, '&:last-child td': { border: 0 } }}>
|
<TableRow key={m.id} hover sx={{ '& td': { borderBottom: '1px solid #F4F6F8', py: 2.25 }, '&:last-child td': { border: 0 } }}>
|
||||||
<TableCell>
|
<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 }}>
|
<Avatar variant="rounded" sx={{ bgcolor: meta.bg, color: meta.color, borderRadius: 2, width: 38, height: 38 }}>
|
||||||
<KindIcon sx={{ fontSize: 20 }} />
|
<KindIcon sx={{ fontSize: 20 }} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
|
|||||||
@@ -548,14 +548,14 @@ export default function RiderRoutes() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* ── Analysis KPI strip ── */}
|
{/* ── 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: 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: 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: 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' },
|
{ icon: StraightenRoundedIcon, label: 'Distance', value: `${kpi.km} km`, sub: 'fleet total today', color: '#8E24AA', bg: '#F3E5F5' },
|
||||||
].map((k, i) => (
|
].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>
|
</Grid>
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
// Close button would hide under the app bar. Lift the whole modal above it.
|
||||||
zIndex: (t) => t.zIndex.modal,
|
zIndex: (t) => t.zIndex.modal,
|
||||||
'& .MuiDrawer-paper': {
|
'& .MuiDrawer-paper': {
|
||||||
width: { xs: '100%', sm: 380 },
|
width: { xs: '100%', sm: 480 },
|
||||||
maxWidth: '100%',
|
maxWidth: '100%',
|
||||||
top: { xs: 0, sm: 64 },
|
// Run the sheet the full height of the viewport so there is no empty
|
||||||
height: { xs: '100%', sm: 'calc(100% - 64px)' },
|
// strip above the red header (zIndex.modal keeps Close clickable).
|
||||||
|
top: 0,
|
||||||
|
height: '100%',
|
||||||
borderTopLeftRadius: { sm: 16 },
|
borderTopLeftRadius: { sm: 16 },
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
@@ -1185,7 +1187,7 @@ export default function Riders() {
|
|||||||
</Stack>
|
</Stack>
|
||||||
|
|
||||||
{/* KPIs */}
|
{/* 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: 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: 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: 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' },
|
{ icon: PaymentsOutlinedIcon, label: 'COP Pending', value: inr(kpi.codToCollect), sub: 'To be collected', color: '#8E24AA', bg: '#F3E5F5' },
|
||||||
].map((k, i) => (
|
].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} />
|
<KpiCard {...k} />
|
||||||
</Grid>
|
</Grid>
|
||||||
))}
|
))}
|
||||||
@@ -1271,24 +1273,28 @@ export default function Riders() {
|
|||||||
{STATUS_CHIPS.map(s => {
|
{STATUS_CHIPS.map(s => {
|
||||||
const active = statusFilter === s;
|
const active = statusFilter === s;
|
||||||
const meta = STATUS_META[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 (
|
return (
|
||||||
<Chip
|
<Chip
|
||||||
key={s}
|
key={s}
|
||||||
label={s}
|
label={s}
|
||||||
clickable
|
clickable
|
||||||
onClick={() => setStatusFilter(s)}
|
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={{
|
sx={{
|
||||||
height: 38,
|
height: 38,
|
||||||
px: 1,
|
px: 1,
|
||||||
borderRadius: 2,
|
borderRadius: 2,
|
||||||
fontSize: '0.82rem',
|
fontSize: '0.82rem',
|
||||||
fontWeight: active ? 700 : 500,
|
fontWeight: active ? 700 : 600,
|
||||||
bgcolor: active ? '#1A1A2E' : '#F8F9FB',
|
bgcolor: active ? accent : tint,
|
||||||
color: active ? '#fff' : '#5F6368',
|
color: active ? '#fff' : accent,
|
||||||
border: active ? 'none' : '1px solid #ECEEF1',
|
border: active ? 'none' : `1px solid ${alpha(accent, 0.25)}`,
|
||||||
|
'& .MuiChip-icon': { color: active ? '#fff' : meta.color },
|
||||||
'& .MuiChip-label': { px: 1.25 },
|
'& .MuiChip-label': { px: 1.25 },
|
||||||
'&:hover': { bgcolor: active ? '#1A1A2E' : '#F1F3F5' }
|
'&:hover': { bgcolor: active ? accent : alpha(accent, 0.14) }
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user