import React, { useState, useMemo } from 'react'; import { useQuery } from '@tanstack/react-query'; // material-ui import { Avatar, Box, Chip, Collapse, Dialog, DialogContent, Divider, Grid, IconButton, Paper, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Tooltip, Typography, useMediaQuery, useTheme } from '@mui/material'; import { MdDirectionsBike, MdCalendarMonth, MdLocalShipping, MdCheckCircle, MdHourglassEmpty, MdCancel, MdCurrencyRupee, MdStraighten, MdMap, MdExpandMore, MdExpandLess, MdGroups, MdPerson, MdOutlineLocalShipping, MdOutlineCheckCircle, MdOutlineCurrencyRupee } from 'react-icons/md'; import dayjs from 'dayjs'; var utc = require('dayjs/plugin/utc'); dayjs.extend(utc); import { fetchRidersSummary } from 'pages/api/api'; import Loader from 'components/Loader'; import DateFilterDialog from 'components/DateFilterDialog'; import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete'; import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar'; import PageHeader from 'components/nearle_components/PageHeader'; import StatCard from 'components/nearle_components/StatCard'; import { OrdersTableSkeleton } from '../orders/OrdersTableSkeleton'; import RidersRoutes from './RidersRoutes'; import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard'; import { DT, tint, soft, ring, edge } from 'themes/dt/tokens'; import { AccentAvatar } from 'themes/dt/primitives'; const BRAND = '#000000'; const BRAND_LIGHT = '#3A3A3A'; // Pill used in cells for numeric / km / amount metrics. const MetricPill = ({ color, icon, label, tooltip, minWidth = 80 }) => ( {icon} {label} ); // Coloured numeric cell — non-zero values get a soft red badge to draw the eye, // preserving the legacy "red when present" affordance. const CountCell = ({ value, color = '#ef4444', icon }) => { const n = Number(value) || 0; if (n === 0) { return ( 0 ); } return ( {icon} {n} ); }; function formatNumberToRupees(value) { return new Intl.NumberFormat('en-IN', { style: 'currency', currency: 'INR', minimumFractionDigits: 2 }).format(Number(value) || 0); } // ==============================|| Riders Summary ||============================== // export default function RidersSummary() { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD')); const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD')); const [locaName, setLocoName] = useState('All'); const [open, setOpen] = useState(false); const [datestatus, setDatestatus] = useState('Today'); const [tenantData, setTenantData] = useState([]); const [openRow, setOpenRow] = useState(null); const [appId, setAppId] = useState(0); const [loading, setLoading] = useState(false); const [mapOpen, setMapOpen] = useState(false); const [logDetails, setLogDetails] = useState(null); const [selectedRider, setSelectedRider] = useState(null); const [routeLoading, setRouteLoading] = useState(false); const [searchword, setSearchword] = useState(''); const [debouncedSearch, setDebouncedSearch] = useState(''); // ==============================|| fetchRidersSummary ||============================== // const { isLoading: isLoadingReports, data: rows } = useQuery({ queryKey: ['ridersummary', appId, startdate, enddate], queryFn: fetchRidersSummary }); // Client-side filter across rider name + id. // GET /admin/milers/summary confirmed live: { milerprofileid, userid, // displayname, phone, availabilitystatus, rating, assigned, accepted, // rejected, completed, cancelled, delivered, riderkms, ridercharges, ... } // — no firstname/lastname/charges/deliveryamt/totalorders/pending/kms/ // cumulativekms field exists at all, which is why this whole page showed // "—" everywhere. const filteredRows = useMemo(() => { if (!rows) return []; if (!debouncedSearch) return rows; const q = debouncedSearch.toLowerCase().trim(); return rows.filter((r) => [r.displayname, String(r.userid)].filter(Boolean).some((field) => String(field).toLowerCase().includes(q))); }, [rows, debouncedSearch]); // Aggregate KPIs from the loaded summary. const stats = useMemo(() => { if (!rows || rows.length === 0) return { riders: 0, orders: 0, delivered: 0, amount: 0 }; return rows.reduce( (acc, r) => { acc.riders += 1; acc.orders += Number(r.assigned) || 0; acc.delivered += Number(r.delivered) || 0; acc.amount += Number(r.ridercharges) || 0; return acc; }, { riders: 0, orders: 0, delivered: 0, amount: 0 } ); }, [rows]); // No per-rider tenant-breakdown endpoint in the new API. const fetchTenantSummary = async () => { setLoading(true); setTenantData(null); setLoading(false); }; // ==============================|| rider planned route (for map) ||============================== // // /admin/consignments has no documented per-row schema (lat/lng/step/ // customer fields aren't shown anywhere in the doc), so building a planned // route from it would be guesswork that risks plotting wrong pins on the // map. Degrades to an empty route instead. const getuserdeliverylogs = async () => { setRouteLoading(true); setLogDetails([]); setRouteLoading(false); }; // Total Amount sum (preserved from legacy bottom bar). const total = useMemo(() => { if (!rows) return 0; return rows.reduce((sum, row) => sum + (Number(row.ridercharges) || 0), 0); }, [rows]); const KPI_META = [ { key: 'riders', label: 'Active Riders', color: BRAND, icon: MdDirectionsBike, value: stats.riders }, { key: 'orders', label: 'Total Orders', color: '#0ea5e9', icon: MdOutlineLocalShipping, value: stats.orders }, { key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdOutlineCheckCircle, value: stats.delivered }, { key: 'amount', label: 'Total Amount', color: '#f59e0b', icon: MdOutlineCurrencyRupee, value: formatNumberToRupees(total) } ]; return ( <> {(isLoadingReports || loading) && } {/* ============================================= || Header || ============================================= */} } /> {/* ============================================= || KPI Cards || ============================================= */} {KPI_META.map((item) => { const Icon = item.icon; return ( } color={item.color} loading={isLoadingReports} /> ); })} {/* ============================================= || Filter Bar (date + search) || ============================================= */} Orders · {datestatus} {filteredRows.length} riders · {stats.orders} orders setOpen(true)} sx={{ display: 'inline-flex', alignItems: 'center', gap: 0.75, px: 1.25, py: 0.75, borderRadius: 999, cursor: 'pointer', bgcolor: tint('#f59e0b'), border: `1.5px solid ${edge('#f59e0b')}`, color: '#f59e0b', fontWeight: 800, fontSize: 12, ml: 1, transition: 'all 0.18s', '&:hover': { borderColor: '#f59e0b', boxShadow: `0 0 0 3px ${ring('#f59e0b')}` } }} > {dayjs(startdate).format('DD/MM/YY')} – {dayjs(enddate).format('DD/MM/YY')} {/* ============================================= || Table || ============================================= */} {isMobile ? ( {isLoadingReports && ( Loading riders… )} {(!filteredRows || filteredRows.length === 0) && !isLoadingReports ? ( No riders to show {searchword ? 'Try a different keyword.' : 'Pick a zone or date range to load the summary.'} ) : ( filteredRows.map((row, index) => { const isOpen = openRow === row.userid; const amount = Number(row.ridercharges) || 0; const riderName = row?.displayname || '—'; return ( {riderName} #{String(index + 1).padStart(2, '0')} · ID #{row.userid} { setSelectedRider({ userid: row?.userid, name: row?.displayname || `Rider ${row?.userid}` }); setLogDetails(null); setMapOpen(true); getuserdeliverylogs(row?.userid); }} sx={{ bgcolor: soft('#0ea5e9'), color: '#0ea5e9', border: `1px solid ${edge('#0ea5e9')}`, '&:hover': { bgcolor: '#0ea5e9', color: '#fff' } }} > { const isOpening = !isOpen; setOpenRow(isOpening ? row.userid : null); if (isOpening) fetchTenantSummary(row.userid); }} sx={{ bgcolor: isOpen ? BRAND : soft(BRAND), color: isOpen ? '#fff' : BRAND, border: `1px solid ${edge(BRAND)}`, '&:hover': { bgcolor: BRAND, color: '#fff' } }} > {isOpen ? : } } > } /> } /> } /> } /> } label={`${Number(row.riderkms || 0).toFixed(2)} km`} tooltip="KMS" /> } label={formatNumberToRupees(amount).replace('₹', '').trim()} tooltip="Total Amount" minWidth={100} /> {/* per-tenant breakdown */} {isOpen && ( Tenant Breakdown }> {loading && ( Loading… )} {!loading && (!tenantData || tenantData.length === 0) ? ( No tenant breakdown available. ) : ( tenantData?.map((sub, sidx) => ( {sub.tenantname || '—'} } /> } /> } /> } /> } label={`${Number(sub.kms || 0).toFixed(2)} km`} tooltip="KMS" /> } label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`} tooltip="Actual KMS" /> } label={formatNumberToRupees( Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0) ) .replace('₹', '') .trim()} tooltip="Total Amount" minWidth={100} /> )) )} )} ); }) )} ) : ( # Rider Orders Accepted Cancelled Delivered KMS Amount Action {isLoadingReports && } {(!filteredRows || filteredRows.length === 0) && !isLoadingReports ? ( No riders to show {searchword ? 'Try a different keyword.' : 'Pick a zone or date range to load the summary.'} ) : ( filteredRows.map((row, index) => { const isOpen = openRow === row.userid; const amount = Number(row.ridercharges) || 0; return ( {String(index + 1).padStart(2, '0')} {row?.displayname || '—'} ID #{row.userid} } /> } /> } /> } /> } label={`${Number(row.riderkms || 0).toFixed(2)} km`} tooltip="KMS" /> } label={formatNumberToRupees(amount).replace('₹', '').trim()} tooltip="Total Amount" minWidth={100} /> { setSelectedRider({ userid: row?.userid, name: row?.displayname || `Rider ${row?.userid}` }); setLogDetails(null); setMapOpen(true); getuserdeliverylogs(row?.userid); }} sx={{ bgcolor: soft('#0ea5e9'), color: '#0ea5e9', border: `1px solid ${edge('#0ea5e9')}`, '&:hover': { bgcolor: '#0ea5e9', color: '#fff' } }} > { const isOpening = !isOpen; setOpenRow(isOpening ? row.userid : null); if (isOpening) fetchTenantSummary(row.userid); }} sx={{ bgcolor: isOpen ? BRAND : soft(BRAND), color: isOpen ? '#fff' : BRAND, border: `1px solid ${edge(BRAND)}`, '&:hover': { bgcolor: BRAND, color: '#fff' } }} > {isOpen ? : } {/* ============================================= || Collapsible per-tenant breakdown || ============================================= */} {isOpen && ( Tenant Breakdown · {row?.displayname || ''}
# Client All Pending Completed Cancelled KMS Amount {loading && } {!loading && (!tenantData || tenantData.length === 0) ? ( No tenant breakdown available. ) : ( tenantData?.map((sub, sidx) => ( {String(sidx + 1).padStart(2, '0')} {sub.tenantname || '—'} } /> } /> } /> } /> } label={`${Number(sub.kms || 0).toFixed(2)} km`} tooltip="KMS" /> } label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`} tooltip="Actual KMS" /> } label={formatNumberToRupees( Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0) ) .replace('₹', '') .trim()} tooltip="Total Amount" minWidth={100} /> )) )}
)} ); }) )} )} {/* ============================================= || Total Bar || ============================================= */} {filteredRows.length > 0 && ( <> Grand Total )} {/* ============================================= || Map Dialog || ============================================= */} { setMapOpen(false); setLogDetails(null); setSelectedRider(null); }} > { setMapOpen(false); setLogDetails(null); setSelectedRider(null); }} /> {/* ============================================= || Date Filter Dialog || ============================================= */} setOpen(false)} onSelect={(range) => { setStartdate(range.startDate); setEnddate(range.endDate); setDatestatus(range.label); }} /> ); }