Files
Doormilexpress_console/src/pages/nearle/reports/ridersSummary.js

1010 lines
46 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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 }) => (
<Tooltip title={tooltip || ''} placement="top">
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1,
py: 0.375,
borderRadius: 999,
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 11,
fontWeight: 800,
minWidth,
justifyContent: 'center',
whiteSpace: 'nowrap'
}}
>
{icon}
{label}
</Box>
</Tooltip>
);
// 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 (
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 700 }}>
0
</Typography>
);
}
return (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.875,
py: 0.25,
borderRadius: 999,
bgcolor: '#ffffff',
border: `1px solid ${edge(color)}`,
color,
fontSize: 12,
fontWeight: 800,
minWidth: 36,
justifyContent: 'center'
}}
>
{icon}
{n}
</Box>
);
};
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) && <Loader />}
{/* ============================================= || Header || ============================================= */}
<PageHeader
title="Riders Summary"
subtitle={`Live · ${locaName || 'All Zones'} · ${datestatus}`}
live
action={
<LocationAutocomplete
locaName={locaName}
setAppId={setAppId}
setLocoName={setLocoName}
placeholder="Select Zone"
/>
}
/>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 2, md: 2.5 }} sx={{ mb: { xs: 1.5, md: 2 } }}>
{KPI_META.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={3}>
<StatCard
title={item.label}
value={item.value ?? 0}
icon={<Icon size={20} />}
color={item.color}
loading={isLoadingReports}
/>
</Grid>
);
})}
</Grid>
{/* ============================================= || Filter Bar (date + search) || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1.5, md: 2 },
p: { xs: 1, md: 1.5 },
borderTopLeftRadius: DT.radiusCard / 8,
borderTopRightRadius: DT.radiusCard / 8,
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0,
border: '1px solid',
borderColor: DT.borderSubtle,
borderBottom: 0,
background: '#fff'
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'stretch', sm: 'center' }}
justifyContent="space-between"
spacing={1.25}
>
<Stack direction="row" alignItems="center" spacing={1.25} flexWrap="wrap">
<AccentAvatar color={BRAND} size={32}>
<MdDirectionsBike size={18} />
</AccentAvatar>
<Stack>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Orders · {datestatus}
</Typography>
<Typography variant="body2" sx={{ color: DT.textPrimary, fontWeight: 700 }}>
{filteredRows.length} riders · {stats.orders} orders
</Typography>
</Stack>
<Tooltip title="Date Filter" placement="top">
<Box
onClick={() => 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')}` }
}}
>
<MdCalendarMonth size={14} />
{dayjs(startdate).format('DD/MM/YY')} – {dayjs(enddate).format('DD/MM/YY')}
</Box>
</Tooltip>
</Stack>
<Box sx={{ width: { xs: '100%', sm: 280, lg: 340 }, flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
<DebounceSearchBar
value={searchword}
onChange={setSearchword}
onDebouncedChange={setDebouncedSearch}
placeholder="Search riders (ctrl+k)"
/>
</Box>
</Stack>
</Paper>
{/* ============================================= || Table || ============================================= */}
<Paper
elevation={0}
sx={{
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
borderBottomLeftRadius: DT.radiusCard / 8,
borderBottomRightRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
background: '#fff'
}}
>
{isMobile ? (
<MobileCardList scroll>
{isLoadingReports && (
<Stack alignItems="center" sx={{ py: 4 }}>
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 700 }}>
Loading riders…
</Typography>
</Stack>
)}
{(!filteredRows || filteredRows.length === 0) && !isLoadingReports ? (
<Stack alignItems="center" spacing={1.5} sx={{ py: 6 }}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdDirectionsBike size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No riders to show
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{searchword ? 'Try a different keyword.' : 'Pick a zone or date range to load the summary.'}
</Typography>
</Stack>
) : (
filteredRows.map((row, index) => {
const isOpen = openRow === row.userid;
const amount = Number(row.ridercharges) || 0;
const riderName = row?.displayname || '—';
return (
<MobileCard
key={row.userid || index}
accent={BRAND}
selected={isOpen}
header={
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
<AccentAvatar color={BRAND} size={36}>
<MdPerson size={18} />
</AccentAvatar>
<Stack sx={{ minWidth: 0 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{riderName}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
#{String(index + 1).padStart(2, '0')} · ID #{row.userid}
</Typography>
</Stack>
</Stack>
<Stack direction="row" spacing={0.75} sx={{ flexShrink: 0 }}>
<Tooltip title="View planned route" placement="top">
<IconButton
size="small"
onClick={() => {
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' }
}}
>
<MdMap size={14} />
</IconButton>
</Tooltip>
<Tooltip title={isOpen ? 'Collapse breakdown' : 'Expand breakdown'} placement="top">
<IconButton
size="small"
onClick={() => {
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 ? <MdExpandLess size={16} /> : <MdExpandMore size={16} />}
</IconButton>
</Tooltip>
</Stack>
</Stack>
}
>
<MobileFieldGrid columns={2}>
<MobileField label="Orders">
<CountCell value={row.assigned} color={BRAND} icon={<MdLocalShipping size={11} />} />
</MobileField>
<MobileField label="Delivered">
<CountCell value={row.delivered} color="#10b981" icon={<MdCheckCircle size={11} />} />
</MobileField>
<MobileField label="Accepted">
<CountCell value={row.accepted} color="#f59e0b" icon={<MdHourglassEmpty size={11} />} />
</MobileField>
<MobileField label="Cancelled">
<CountCell value={row.cancelled} color="#ef4444" icon={<MdCancel size={11} />} />
</MobileField>
<MobileField label="KMS">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={11} />}
label={`${Number(row.riderkms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Amount" full>
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={11} />}
label={formatNumberToRupees(amount).replace('₹', '').trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
</MobileFieldGrid>
{/* per-tenant breakdown */}
{isOpen && (
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<Paper
elevation={0}
sx={{
mt: 1.25,
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
boxShadow: DT.shadowSoft
}}
>
<Stack
direction="row"
alignItems="center"
spacing={1}
sx={{
px: 1.5,
py: 1,
background: '#ffffff',
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
<AccentAvatar color={BRAND} size={24} selected>
<MdGroups size={12} />
</AccentAvatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textPrimary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Tenant Breakdown
</Typography>
</Stack>
<Stack divider={<Divider />}>
{loading && (
<Typography variant="caption" sx={{ color: DT.textMuted, textAlign: 'center', py: 2 }}>
Loading…
</Typography>
)}
{!loading && (!tenantData || tenantData.length === 0) ? (
<Typography variant="caption" sx={{ color: DT.textMuted, textAlign: 'center', py: 2 }}>
No tenant breakdown available.
</Typography>
) : (
tenantData?.map((sub, sidx) => (
<Box key={`${sub.tenantname}-${sidx}`} sx={{ p: 1.5 }}>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mb: 1 }}>
<AccentAvatar color="#0ea5e9" size={24}>
<MdGroups size={12} />
</AccentAvatar>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{sub.tenantname || '—'}
</Typography>
</Stack>
<MobileFieldGrid columns={2}>
<MobileField label="All">
<CountCell value={sub.totalorders} color={BRAND} icon={<MdLocalShipping size={10} />} />
</MobileField>
<MobileField label="Completed">
<CountCell value={sub.deliveriescompleted} color="#10b981" icon={<MdCheckCircle size={10} />} />
</MobileField>
<MobileField label="Pending">
<CountCell value={sub.deliveriespending} color="#f59e0b" icon={<MdHourglassEmpty size={10} />} />
</MobileField>
<MobileField label="Cancelled">
<CountCell value={sub.deliveriescancelled} color="#ef4444" icon={<MdCancel size={10} />} />
</MobileField>
<MobileField label="KMS">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={10} />}
label={`${Number(sub.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</MobileField>
<MobileField label="Actual KMS">
<MetricPill
color="#10b981"
icon={<MdStraighten size={10} />}
label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</MobileField>
<MobileField label="Amount" full>
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={10} />}
label={formatNumberToRupees(
Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0)
)
.replace('₹', '')
.trim()}
tooltip="Total Amount"
minWidth={100}
/>
</MobileField>
</MobileFieldGrid>
</Box>
))
)}
</Stack>
</Paper>
</Collapse>
)}
</MobileCard>
);
})
)}
</MobileCardList>
) : (
<TableContainer
sx={{
maxHeight: { xs: 'calc(100vh - 220px)', md: 'calc(100vh - 190px)' },
'&::-webkit-scrollbar': { width: 10, height: 10 },
'&::-webkit-scrollbar-thumb': {
backgroundColor: edge(BRAND),
borderRadius: 8,
'&:hover': { backgroundColor: BRAND }
},
'&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt }
}}
>
<Table stickyHeader sx={{ minWidth: { xs: 880, md: 1080 } }}>
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: { xs: 10, md: 11 },
fontWeight: 800,
letterSpacing: 0.6,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: { xs: 1, md: 1.25 },
px: { xs: 1, md: 2 }
}
}}
>
<TableCell>#</TableCell>
<TableCell>Rider</TableCell>
<TableCell align="center">Orders</TableCell>
<TableCell align="center">Accepted</TableCell>
<TableCell align="center">Cancelled</TableCell>
<TableCell align="center">Delivered</TableCell>
<TableCell align="center">KMS</TableCell>
<TableCell align="center">Amount</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
{isLoadingReports && <OrdersTableSkeleton col={5} />}
{(!filteredRows || filteredRows.length === 0) && !isLoadingReports ? (
<TableRow>
<TableCell colSpan={9} sx={{ py: 6 }}>
<Stack alignItems="center" spacing={1.5}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdDirectionsBike size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No riders to show
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
{searchword ? 'Try a different keyword.' : 'Pick a zone or date range to load the summary.'}
</Typography>
</Stack>
</TableCell>
</TableRow>
) : (
filteredRows.map((row, index) => {
const isOpen = openRow === row.userid;
const amount = Number(row.ridercharges) || 0;
return (
<React.Fragment key={row.userid || index}>
<TableRow
sx={{
transition: 'background-color 0.15s',
'& td': {
borderBottom: isOpen ? 'none' : `1px solid ${DT.divider}`,
py: { xs: 1, md: 1.5 },
px: { xs: 1, md: 2 }
},
'&:hover': { backgroundColor: isOpen ? '#fff' : DT.surfaceAlt },
bgcolor: isOpen ? tint(BRAND) : 'transparent'
}}
>
<TableCell>
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted }}>
{String(index + 1).padStart(2, '0')}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={1}>
<AccentAvatar color={BRAND} size={36}>
<MdPerson size={18} />
</AccentAvatar>
<Stack>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
{row?.displayname || '—'}
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
ID #{row.userid}
</Typography>
</Stack>
</Stack>
</TableCell>
<TableCell align="center">
<CountCell value={row.assigned} color={BRAND} icon={<MdLocalShipping size={11} />} />
</TableCell>
<TableCell align="center">
<CountCell value={row.accepted} color="#f59e0b" icon={<MdHourglassEmpty size={11} />} />
</TableCell>
<TableCell align="center">
<CountCell value={row.cancelled} color="#ef4444" icon={<MdCancel size={11} />} />
</TableCell>
<TableCell align="center">
<CountCell value={row.delivered} color="#10b981" icon={<MdCheckCircle size={11} />} />
</TableCell>
<TableCell align="center">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={11} />}
label={`${Number(row.riderkms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
</TableCell>
<TableCell align="center">
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={11} />}
label={formatNumberToRupees(amount).replace('₹', '').trim()}
tooltip="Total Amount"
minWidth={100}
/>
</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.75} justifyContent="flex-end">
<Tooltip title="View planned route" placement="top">
<IconButton
size="small"
onClick={() => {
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' }
}}
>
<MdMap size={14} />
</IconButton>
</Tooltip>
<Tooltip title={isOpen ? 'Collapse breakdown' : 'Expand breakdown'} placement="top">
<IconButton
size="small"
onClick={() => {
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 ? <MdExpandLess size={16} /> : <MdExpandMore size={16} />}
</IconButton>
</Tooltip>
</Stack>
</TableCell>
</TableRow>
{/* ============================================= || Collapsible per-tenant breakdown || ============================================= */}
{isOpen && (
<TableRow>
<TableCell
colSpan={9}
sx={{
p: 0,
borderBottom: `1px solid ${DT.divider}`,
bgcolor: tint(BRAND)
}}
>
<Collapse in={isOpen} timeout="auto" unmountOnExit>
<Box sx={{ p: { xs: 1, md: 2 } }}>
<Paper
elevation={0}
sx={{
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
boxShadow: DT.shadowSoft
}}
>
<Stack
direction="row"
alignItems="center"
spacing={1}
sx={{
px: 2,
py: 1.25,
background: '#ffffff',
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
<AccentAvatar color={BRAND} size={28} selected>
<MdGroups size={14} />
</AccentAvatar>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textPrimary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Tenant Breakdown · {row?.displayname || ''}
</Typography>
</Stack>
<Table size="small">
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10,
fontWeight: 800,
letterSpacing: 0.6,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: 1,
px: 1.5
}
}}
>
<TableCell>#</TableCell>
<TableCell>Client</TableCell>
<TableCell align="center">All</TableCell>
<TableCell align="center">Pending</TableCell>
<TableCell align="center">Completed</TableCell>
<TableCell align="center">Cancelled</TableCell>
<TableCell align="center">KMS</TableCell>
<TableCell align="center">Amount</TableCell>
</TableRow>
</TableHead>
<TableBody>
{loading && <OrdersTableSkeleton col={4} />}
{!loading && (!tenantData || tenantData.length === 0) ? (
<TableRow>
<TableCell colSpan={8} sx={{ py: 3 }}>
<Typography variant="caption" sx={{ color: DT.textMuted, textAlign: 'center', display: 'block' }}>
No tenant breakdown available.
</Typography>
</TableCell>
</TableRow>
) : (
tenantData?.map((sub, sidx) => (
<TableRow
key={`${sub.tenantname}-${sidx}`}
sx={{
'& td': {
borderBottom: `1px solid ${DT.divider}`,
py: 1,
px: 1.5
},
'&:hover': { backgroundColor: DT.surfaceAlt }
}}
>
<TableCell>
<Typography variant="caption" sx={{ fontWeight: 700, color: DT.textMuted }}>
{String(sidx + 1).padStart(2, '0')}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={0.75}>
<AccentAvatar color="#0ea5e9" size={24}>
<MdGroups size={12} />
</AccentAvatar>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary, whiteSpace: 'nowrap' }}>
{sub.tenantname || '—'}
</Typography>
</Stack>
</TableCell>
<TableCell align="center">
<CountCell value={sub.totalorders} color={BRAND} icon={<MdLocalShipping size={10} />} />
</TableCell>
<TableCell align="center">
<CountCell value={sub.deliveriespending} color="#f59e0b" icon={<MdHourglassEmpty size={10} />} />
</TableCell>
<TableCell align="center">
<CountCell value={sub.deliveriescompleted} color="#10b981" icon={<MdCheckCircle size={10} />} />
</TableCell>
<TableCell align="center">
<CountCell value={sub.deliveriescancelled} color="#ef4444" icon={<MdCancel size={10} />} />
</TableCell>
<TableCell align="center">
<Stack direction="column" spacing={0.5} alignItems="center">
<MetricPill
color="#ef4444"
icon={<MdStraighten size={10} />}
label={`${Number(sub.kms || 0).toFixed(2)} km`}
tooltip="KMS"
/>
<MetricPill
color="#10b981"
icon={<MdStraighten size={10} />}
label={`${Number(sub.cumulativekms || 0).toFixed(2)} km`}
tooltip="Actual KMS"
/>
</Stack>
</TableCell>
<TableCell align="center">
<MetricPill
color={BRAND}
icon={<MdCurrencyRupee size={10} />}
label={formatNumberToRupees(
Math.max(Number(sub.charges) || 0, Number(sub.deliveryamt) || 0)
)
.replace('₹', '')
.trim()}
tooltip="Total Amount"
minWidth={100}
/>
</TableCell>
</TableRow>
))
)}
</TableBody>
</Table>
</Paper>
</Box>
</Collapse>
</TableCell>
</TableRow>
)}
</React.Fragment>
);
})
)}
</TableBody>
</Table>
</TableContainer>
)}
{/* ============================================= || Total Bar || ============================================= */}
{filteredRows.length > 0 && (
<>
<Divider />
<Stack
direction="row"
alignItems="center"
justifyContent="flex-end"
spacing={1.5}
sx={{
px: 2,
py: 1.5,
background: '#ffffff'
}}
>
<Typography
variant="caption"
sx={{ fontWeight: 800, color: DT.textSecondary, letterSpacing: 0.6, textTransform: 'uppercase' }}
>
Grand Total
</Typography>
<Chip
label={formatNumberToRupees(total)}
sx={{
fontWeight: 800,
fontSize: 13,
color: '#fff',
background: `linear-gradient(135deg, ${BRAND} 0%, ${BRAND_LIGHT} 100%)`,
boxShadow: `0 6px 16px ${ring(BRAND)}`
}}
/>
</Stack>
</>
)}
</Paper>
{/* ============================================= || Map Dialog || ============================================= */}
<Dialog
maxWidth="xl"
fullScreen
open={mapOpen}
onClose={() => {
setMapOpen(false);
setLogDetails(null);
setSelectedRider(null);
}}
>
<DialogContent sx={{ p: 0, height: '100vh' }}>
<RidersRoutes
details={logDetails}
loading={routeLoading}
riderName={selectedRider?.name}
dateRange={`${dayjs(startdate).format('DD/MM/YY')} – ${dayjs(enddate).format('DD/MM/YY')}`}
onClose={() => {
setMapOpen(false);
setLogDetails(null);
setSelectedRider(null);
}}
/>
</DialogContent>
</Dialog>
{/* ============================================= || Date Filter Dialog || ============================================= */}
<DateFilterDialog
open={open}
onClose={() => setOpen(false)}
onSelect={(range) => {
setStartdate(range.startDate);
setEnddate(range.endDate);
setDatestatus(range.label);
}}
/>
</>
);
}