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 || ============================================= */}
{/* ============================================= || Date Filter Dialog || ============================================= */}
setOpen(false)}
onSelect={(range) => {
setStartdate(range.startDate);
setEnddate(range.endDate);
setDatestatus(range.label);
}}
/>
>
);
}