wired the api for rest mock data
This commit is contained in:
@@ -127,11 +127,11 @@ const moverIcon = (color) => new L.DivIcon({
|
||||
});
|
||||
|
||||
// ════════════════════════════════════════════════════════════════════════════════
|
||||
// Mock data — every rider has a DELIVERY trip and a PICKUP trip for the day, each
|
||||
// with its own ordered stops, timings and distance. Every order stop carries the
|
||||
// full detail shown in the side drawer when clicked.
|
||||
// Rider routes — loaded live from GET /hub/rider-routes (see the load effect below).
|
||||
// Each rider has a PICKUP trip for the day with its own ordered stops and distance;
|
||||
// every order stop carries the detail shown in the side drawer when clicked.
|
||||
//
|
||||
// Delivery: hub → customer drops. Pickup: merchant collections → hub.
|
||||
// Pickup: merchant collections → hub.
|
||||
// ════════════════════════════════════════════════════════════════════════════════
|
||||
// The hub all pickup legs return to. Label comes from the logged-in hub context;
|
||||
// the coordinate is only the initial map centre (the map auto-fits to the stops).
|
||||
@@ -144,30 +144,41 @@ const ROUTE_COLORS = ['#1A73E8', '#8E24AA', '#1E8E3E', '#E8710A', '#C01227', '#0
|
||||
// page renders: a `pickup` trip with a list of order stops. Fields the API does
|
||||
// not provide (customer, weight, COD, slot, instructions) default gracefully.
|
||||
const mapRoute = (r, i) => {
|
||||
const apiStops = Array.isArray(r.stops) ? r.stops : [];
|
||||
const stops = apiStops.map((s) => ({
|
||||
kind: 'order',
|
||||
orderId: s.bookingid != null ? `BK-${s.bookingid}` : `Stop ${s.seq}`,
|
||||
bookingid: s.bookingid,
|
||||
customer: s.customer || '',
|
||||
phone: s.phone || '',
|
||||
address: s.address || '—',
|
||||
lat: s.lat,
|
||||
lng: s.lon,
|
||||
time: s.eta_minutes != null ? `${s.eta_minutes} min` : '',
|
||||
status: s.status === 'completed' ? 'Picked' : s.status === 'in_progress' ? 'In progress' : 'Pending',
|
||||
items: s.items ?? 0,
|
||||
weight: '',
|
||||
slot: '',
|
||||
cod: 0,
|
||||
payment: '',
|
||||
legKm: 0,
|
||||
instructions: ''
|
||||
}));
|
||||
// Be tolerant of backend field-name variants: the stops list, coordinates, ids
|
||||
// and distance can arrive under a few different keys. `??` only falls back when
|
||||
// the primary key is missing, so the documented shape still works unchanged.
|
||||
const apiStops = [r.stops, r.route, r.pickups, r.orders, r.legs].find(Array.isArray) || [];
|
||||
const mileruserid = r.mileruserid ?? r.userid ?? r.miler_user_id ?? r.id;
|
||||
const stops = apiStops.map((s) => {
|
||||
const bookingid = s.bookingid ?? s.consignmentid ?? s.booking_id;
|
||||
return {
|
||||
kind: 'order',
|
||||
orderId: bookingid != null ? `BK-${bookingid}` : `Stop ${s.seq ?? ''}`,
|
||||
bookingid,
|
||||
customer: s.customer || s.sendername || s.name || '',
|
||||
phone: s.phone || '',
|
||||
address: s.address || '—',
|
||||
lat: s.lat ?? s.latitude,
|
||||
lng: s.lon ?? s.lng ?? s.longitude,
|
||||
time: s.eta_minutes != null ? `${s.eta_minutes} min` : '',
|
||||
status: s.status === 'completed' ? 'Picked' : s.status === 'in_progress' ? 'In progress' : 'Pending',
|
||||
// Optional fields: keep whatever the API sends; `null`/'' means "unknown" and
|
||||
// the UI hides it rather than showing a fake 0. No placeholder/mock values.
|
||||
items: s.items ?? s.parcels ?? null,
|
||||
weight: s.weight ?? '',
|
||||
// Backend (buildMilerRoute) emits `timeslot` and `legdistance_km`; keep the
|
||||
// other aliases as fallbacks in case the shape ever changes.
|
||||
slot: s.timeslot ?? s.slot ?? s.time_slot ?? '',
|
||||
cod: s.cod ?? s.cod_amount ?? 0,
|
||||
payment: s.payment ?? s.payment_mode ?? '',
|
||||
legKm: s.legdistance_km ?? s.legKm ?? s.leg_km ?? s.distance_km ?? null,
|
||||
instructions: s.instructions ?? s.notes ?? ''
|
||||
};
|
||||
});
|
||||
return {
|
||||
id: `RDR-${r.mileruserid}`,
|
||||
mileruserid: r.mileruserid,
|
||||
name: r.milername || `Miler ${r.mileruserid}`,
|
||||
id: `RDR-${mileruserid}`,
|
||||
mileruserid,
|
||||
name: r.milername || r.displayname || r.name || `Miler ${mileruserid}`,
|
||||
color: ROUTE_COLORS[i % ROUTE_COLORS.length],
|
||||
vehicle: '—',
|
||||
vehicleNo: '—',
|
||||
@@ -175,7 +186,7 @@ const mapRoute = (r, i) => {
|
||||
pickup: {
|
||||
startTime: '',
|
||||
endTime: '',
|
||||
distanceKm: r.totaldistance_km || 0,
|
||||
distanceKm: r.totaldistance_km ?? r.total_distance_km ?? r.distance_km ?? r.distance ?? 0,
|
||||
stops
|
||||
}
|
||||
};
|
||||
@@ -262,7 +273,7 @@ function OrderDetailPanel({ data, onBack }) {
|
||||
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
|
||||
</Button>
|
||||
<Stack direction="row" alignItems="center" spacing={0.75} mb={1} flexWrap="wrap" useFlexGap>
|
||||
<Stack direction="row" alignItems="center" spacing={0.75} useFlexGap sx={{ flexWrap: 'wrap', mb: 1 }}>
|
||||
<Chip size="small" icon={(isPickup ? <StorefrontOutlinedIcon /> : <DeliveryDiningRoundedIcon />)} label={isPickup ? 'Pickup' : 'Delivery'}
|
||||
sx={{ bgcolor: 'rgba(255,255,255,0.18)', color: '#fff', fontWeight: 700, '& .MuiChip-icon': { color: '#fff' } }} />
|
||||
<Chip size="small" icon={<StatusIcon sx={{ color: '#fff !important' }} />} label={meta.label || order.status}
|
||||
@@ -296,9 +307,9 @@ function OrderDetailPanel({ data, onBack }) {
|
||||
<Grid container rowSpacing={2} columnSpacing={1.5}>
|
||||
<Grid item xs={6}><DetailRow icon={AccessTimeOutlinedIcon} label="Picked at" value={order.time} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={ScheduleOutlinedIcon} label="Time slot" value={order.slot} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={Inventory2OutlinedIcon} label="Items" value={`${order.items} ${order.items === 1 ? 'parcel' : 'parcels'}`} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={Inventory2OutlinedIcon} label="Items" value={order.items != null ? `${order.items} ${order.items === 1 ? 'parcel' : 'parcels'}` : ''} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={ScaleOutlinedIcon} label="Weight" value={order.weight} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={StraightenRoundedIcon} label="Leg distance" value={`${order.legKm} km`} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={StraightenRoundedIcon} label="Leg distance" value={order.legKm != null ? `${order.legKm} km` : ''} /></Grid>
|
||||
<Grid item xs={6}><DetailRow icon={PaymentsOutlinedIcon} label="Payment" value={order.payment} /></Grid>
|
||||
</Grid>
|
||||
</Paper>
|
||||
@@ -323,7 +334,8 @@ function OrderDetailPanel({ data, onBack }) {
|
||||
<Stack spacing={1.75}>
|
||||
<DetailRow icon={isPickup ? StorefrontOutlinedIcon : WarehouseRoundedIcon} label="Route leg"
|
||||
value={isPickup ? `${order.customer} → ${HUB.label}` : `${HUB.label} → ${order.customer}`} />
|
||||
<DetailRow icon={MyLocationOutlinedIcon} label="Coordinates" value={`${order.lat.toFixed(4)}, ${order.lng.toFixed(4)}`} />
|
||||
<DetailRow icon={MyLocationOutlinedIcon} label="Coordinates"
|
||||
value={order.lat != null && order.lng != null ? `${order.lat.toFixed(4)}, ${order.lng.toFixed(4)}` : ''} />
|
||||
{order.instructions && <DetailRow icon={NotesOutlinedIcon} label="Instructions" value={order.instructions} valueColor="#5F6368" />}
|
||||
</Stack>
|
||||
</Paper>
|
||||
@@ -376,6 +388,7 @@ export default function RiderRoutes() {
|
||||
const rafRef = useRef(null);
|
||||
const lastTsRef = useRef(0);
|
||||
const resolvedRef = useRef({}); // keys we've already fetched/attempted
|
||||
const mapRef = useRef(null); // map column — scrolled into view when playback starts on mobile
|
||||
|
||||
const modeCfg = MODES[mode];
|
||||
|
||||
@@ -474,6 +487,11 @@ export default function RiderRoutes() {
|
||||
if (playing === riderId) { setPlaying(null); return; }
|
||||
if (playing !== riderId) setProgress(0);
|
||||
setPlaying(riderId);
|
||||
// On the stacked layout (< lg) the map sits below the list, so the moving
|
||||
// marker would animate off-screen. Bring the map into view when playback starts.
|
||||
if (typeof window !== 'undefined' && window.matchMedia('(max-width: 1199px)').matches) {
|
||||
requestAnimationFrame(() => mapRef.current?.scrollIntoView({ behavior: 'smooth', block: 'start' }));
|
||||
}
|
||||
};
|
||||
const resetAnim = () => { setPlaying(null); setProgress(0); };
|
||||
const toggleVisible = (id) => setVisible((v) => ({ ...v, [id]: !v[id] }));
|
||||
@@ -689,12 +707,14 @@ export default function RiderRoutes() {
|
||||
<Typography variant="caption" color="text.secondary" noWrap sx={{ flex: 1, minWidth: 0 }}>{s.address}</Typography>
|
||||
</Stack>
|
||||
|
||||
{/* metric chips */}
|
||||
<Stack direction="row" gap={0.75} flexWrap="wrap" sx={{ mt: 1.25 }}>
|
||||
<Chip size="small" icon={<StraightenRoundedIcon sx={{ fontSize: '13px !important' }} />} label={`${s.legKm} km`} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />
|
||||
<Chip size="small" icon={<ScaleOutlinedIcon sx={{ fontSize: '13px !important' }} />} label={s.weight} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />
|
||||
<Chip size="small" icon={<Inventory2OutlinedIcon sx={{ fontSize: '13px !important' }} />} label={`${s.items} ${s.items === 1 ? 'parcel' : 'parcels'}`} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />
|
||||
</Stack>
|
||||
{/* metric chips — only render fields the API actually returned */}
|
||||
{(s.legKm != null || s.weight || s.items != null) && (
|
||||
<Stack direction="row" sx={{ flexWrap: 'wrap', gap: 0.75, mt: 1.25 }}>
|
||||
{s.legKm != null && <Chip size="small" icon={<StraightenRoundedIcon sx={{ fontSize: '13px !important' }} />} label={`${s.legKm} km`} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />}
|
||||
{s.weight && <Chip size="small" icon={<ScaleOutlinedIcon sx={{ fontSize: '13px !important' }} />} label={s.weight} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />}
|
||||
{s.items != null && <Chip size="small" icon={<Inventory2OutlinedIcon sx={{ fontSize: '13px !important' }} />} label={`${s.items} ${s.items === 1 ? 'parcel' : 'parcels'}`} sx={{ height: 22, fontSize: '0.68rem', fontWeight: 600, bgcolor: '#F1F3F5' }} />}
|
||||
</Stack>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
@@ -709,7 +729,7 @@ export default function RiderRoutes() {
|
||||
</Box>
|
||||
|
||||
{/* ── Map ── */}
|
||||
<Box sx={{ flex: 1, minWidth: 0 }}>
|
||||
<Box ref={mapRef} sx={{ flex: 1, minWidth: 0, scrollMarginTop: 72 }}>
|
||||
<Card sx={{ height: { xs: 460, sm: 560, lg: 700 }, borderRadius: 2, border: '1px solid #ECEEF1', overflow: 'hidden', position: 'relative', boxShadow: '0 4px 20px rgba(0,0,0,0.04)' }}>
|
||||
{playState && (
|
||||
<Box sx={{ position: 'absolute', top: 14, left: 14, zIndex: 1000, minWidth: 230, bgcolor: 'rgba(255,255,255,0.97)', borderRadius: 2, p: 1.75, boxShadow: '0 8px 28px rgba(0,0,0,0.14)', border: `1px solid ${alpha(playState.rider.color, 0.3)}` }}>
|
||||
@@ -819,6 +839,10 @@ export default function RiderRoutes() {
|
||||
open={Boolean(detail)}
|
||||
onClose={closeDetail}
|
||||
sx={{
|
||||
// Temporary drawers render at zIndex.drawer (1200) here, below the app bar
|
||||
// (drawer + 1). On mobile the sheet starts at top:0, so its 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: 360 },
|
||||
maxWidth: '100%',
|
||||
|
||||
Reference in New Issue
Block a user