added the api for the real data

This commit is contained in:
2026-07-04 17:08:16 +05:30
parent 200831d19c
commit e6ad37b20f
22 changed files with 2854 additions and 1061 deletions

View File

@@ -1,4 +1,4 @@
import React, { useState, useMemo, useEffect } from 'react';
import React, { useState, useMemo, useEffect, useCallback } from 'react';
import {
Box, Typography, Card, CardContent, Avatar, Chip, Table, TableBody,
TableCell, TableContainer, TableHead, TableRow, IconButton, Button,
@@ -6,7 +6,7 @@ import {
TextField, MenuItem, Divider, InputAdornment, Menu, ListItemIcon,
Snackbar, Alert, useMediaQuery, Grid, Select, FormControl, InputLabel,
Autocomplete, Switch, FormControlLabel, Paper, ToggleButtonGroup,
ToggleButton, Stepper, Step, StepLabel
ToggleButton, Stepper, Step, StepLabel, CircularProgress
} from '@mui/material';
import { useTheme, alpha } from '@mui/material/styles';
@@ -48,6 +48,20 @@ import DownloadOutlinedIcon from '@mui/icons-material/DownloadOutlined';
import HomeWorkOutlinedIcon from '@mui/icons-material/HomeWorkOutlined';
import ArrowBackRoundedIcon from '@mui/icons-material/ArrowBackRounded';
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 },
@@ -74,74 +88,49 @@ const STATUS_META = {
let idSeq = 8060;
const genId = () => `RDR-${idSeq++}`;
// ── Seed roster ────────────────────────────────────────────────────────────────
const SEED = [
{
id: 'RDR-8012', name: 'Muthu Kumar', phone: '+91 98765 43210', hub: 'Delhi Operations Hub',
zones: ['Dwarka', 'Janakpuri'], vehicle: 'Electric Bike', vehicleNo: 'DL-04-EB-1234',
status: 'On Pickup', checkInTime: '08:12', hoursToday: 6.4,
assigned: 24, capacity: 30, pickupsPending: 3, deliveriesPending: 21,
deliveriesDone: 45, deliveriesFailed: 2, codCollected: 4500, codPending: 1200,
rating: 4.8, verified: true,
},
{
id: 'RDR-8015', name: 'Rajesh Sharma', phone: '+91 98765 43211', hub: 'Delhi Operations Hub',
zones: ['Saket', 'Malviya Nagar'], vehicle: 'Cargo Van', vehicleNo: 'DL-01-CV-9876',
status: 'On Pickup', checkInTime: '07:50', hoursToday: 6.8,
assigned: 88, capacity: 120, pickupsPending: 12, deliveriesPending: 76,
deliveriesDone: 110, deliveriesFailed: 4, codCollected: 12000, codPending: 3400,
rating: 4.9, verified: true,
},
{
id: 'RDR-8022', name: 'Vikram Singh', phone: '+91 98765 43212', hub: 'Delhi Operations Hub',
zones: ['Rohini'], vehicle: 'Motorcycle', vehicleNo: 'DL-08-MC-4567',
status: 'Idle', checkInTime: '09:05', hoursToday: 5.2,
assigned: 0, capacity: 25, pickupsPending: 0, deliveriesPending: 0,
deliveriesDone: 68, deliveriesFailed: 1, codCollected: 0, codPending: 0,
rating: 4.5, verified: false,
},
// ... (rest of seed data remains the same)
{
id: 'RDR-8030', name: 'Amit Patel', phone: '+91 98765 43213', hub: 'Delhi Operations Hub',
zones: ['Vasant Kunj'], vehicle: 'Electric Bike', vehicleNo: 'DL-03-EB-7654',
status: 'Returning to Hub', checkInTime: '08:30', hoursToday: 7.1,
assigned: 2, capacity: 30, pickupsPending: 0, deliveriesPending: 2,
deliveriesDone: 55, deliveriesFailed: 6, codCollected: 8500, codPending: 0,
rating: 4.2, verified: true,
},
{
id: 'RDR-8041', name: 'Sana Sheikh', phone: '+91 98765 43214', hub: 'Delhi Operations Hub',
zones: ['Central Delhi', 'Karol Bagh'], vehicle: 'Motorcycle', vehicleNo: 'DL-02-MC-1199',
status: 'On Pickup', checkInTime: '08:00', hoursToday: 6.6,
assigned: 19, capacity: 25, pickupsPending: 2, deliveriesPending: 17,
deliveriesDone: 73, deliveriesFailed: 0, codCollected: 6200, codPending: 900,
rating: 5.0, verified: true,
},
{
id: 'RDR-8044', name: 'Harpreet Gill', phone: '+91 98765 43215', hub: 'Delhi Operations Hub',
zones: ['Mayur Vihar'], vehicle: 'Cycle', vehicleNo: '—',
status: 'On Break', checkInTime: '09:20', hoursToday: 3.9,
assigned: 8, capacity: 15, pickupsPending: 1, deliveriesPending: 7,
deliveriesDone: 22, deliveriesFailed: 1, codCollected: 1500, codPending: 300,
rating: 4.6, verified: true,
},
{
id: 'RDR-8050', name: 'Deepak Yadav', phone: '+91 98765 43216', hub: 'Delhi Operations Hub',
zones: ['Lajpat Nagar'], vehicle: 'Mini Truck', vehicleNo: 'DL-09-MT-3321',
status: 'Offline', checkInTime: '—', hoursToday: 0,
assigned: 0, capacity: 200, pickupsPending: 0, deliveriesPending: 0,
deliveriesDone: 0, deliveriesFailed: 0, codCollected: 0, codPending: 0,
rating: 4.4, verified: false,
},
{
id: 'RDR-8055', name: 'Iqbal Ahmed', phone: '+91 98765 43217', hub: 'Delhi Operations Hub',
zones: ['Connaught Place'], vehicle: 'Electric Bike', vehicleNo: 'DL-05-EB-8080',
status: 'Suspended', checkInTime: '—', hoursToday: 0,
assigned: 0, capacity: 30, pickupsPending: 0, deliveriesPending: 0,
deliveriesDone: 0, deliveriesFailed: 9, codCollected: 0, codPending: 5600,
rating: 3.1, verified: true,
},
];
// 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,
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) => {
@@ -938,12 +927,35 @@ export default function Riders() {
const isMdDown = useMediaQuery(theme.breakpoints.down('md'));
const isLgDown = useMediaQuery(theme.breakpoints.down('lg'));
const [riders, setRiders] = useState(SEED);
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);
@@ -979,22 +991,43 @@ export default function Riders() {
});
}, [riders, search, statusFilter, vehicleFilter]);
const handleSave = (rider) => {
if (formDialog.mode === 'add') {
setRiders(p => [rider, ...p]);
toast(`${rider.name} onboarded successfully`);
} else {
setRiders(p => p.map(r => r.id === rider.id ? rider : r));
toast(`${rider.name} updated`);
// 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');
}
setFormDialog({ open: false, mode: 'add', initial: null });
};
const handleDelete = () => {
setRiders(p => p.filter(r => r.id !== deleteTarget.id));
toast(`${deleteTarget.name} removed`, 'info');
setDeleteTarget(null);
setProfile(null);
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 });
@@ -1009,11 +1042,16 @@ export default function Riders() {
setProfile(r);
setProfileEdit(false);
};
const saveProfileEdit = (updated) => {
setRiders(p => p.map(r => (r.id === updated.id ? updated : r)));
setProfile(updated);
setProfileEdit(false);
toast(`${updated.name} updated`);
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);
@@ -1253,7 +1291,16 @@ export default function Riders() {
</Box>
{/* Content Area */}
{filtered.length === 0 ? (
{loadError && (
<Alert severity="error" onClose={() => setLoadError('')} sx={{ m: 2, borderRadius: 2 }}>
{loadError}
</Alert>
)}
{loading ? (
<Box sx={{ py: 12, display: 'flex', justifyContent: 'center' }}>
<CircularProgress />
</Box>
) : filtered.length === 0 ? (
<Box sx={{ py: 12, textAlign: 'center' }}>
<SearchOutlinedIcon sx={{ fontSize: 80, color: '#E0E0E0', mb: 3 }} />
<Typography variant="h5" fontWeight={600}>No milers match your filters</Typography>