Files
dailygrubs_console/src/pages/nearle/reports/orderSummary.js

1234 lines
52 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, useEffect, useRef, Fragment } from 'react';
import axios from 'axios';
import { useQuery } from '@tanstack/react-query';
import { enqueueSnackbar } from 'notistack';
import {
Avatar,
Box,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Dialog,
DialogContent,
Typography,
Stack,
Button,
IconButton,
Tooltip,
Autocomplete,
TextField,
Collapse,
Skeleton,
Paper,
Grid,
InputBase
} from '@mui/material';
import {
MdLocalShipping,
MdHourglassEmpty,
MdCheckCircle,
MdCancel,
MdMyLocation,
MdPlace,
MdSearch,
MdClear,
MdCalendarMonth,
MdReceiptLong,
MdStraighten,
MdCurrencyRupee,
MdInventory2,
MdKeyboardArrowDown,
MdKeyboardArrowUp,
MdTaskAlt,
MdHighlightOff,
MdInsights,
MdLocalOffer
} from 'react-icons/md';
import dayjs from 'dayjs';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
import { DateRangePicker } from 'mui-daterange-picker';
import { addDays, addMonths, addWeeks, endOfMonth, endOfWeek, startOfMonth, startOfWeek } from 'date-fns';
import Loader from 'components/Loader';
import { useTheme } from '@mui/material/styles';
import { getreportlocationsummary, gettenantlocations } from '../api/api';
import CircularLoader from 'components/nearle_components/CircularLoader';
// ============================================================================
// Design tokens — shared with the rest of the redesigned operator pages.
// ============================================================================
const DT = {
radiusPill: 999,
radiusCard: 16,
shadowSoft: '0 14px 40px rgba(15, 23, 42, 0.10)',
shadowMd: '0 8px 24px rgba(15, 23, 42, 0.08)',
shadowPop: '0 18px 50px rgba(15, 23, 42, 0.18)',
textPrimary: '#0f172a',
textSecondary: '#64748b',
textMuted: '#94a3b8',
borderSubtle: '#e2e8f0',
divider: '#f1f5f9',
surface: '#ffffff',
surfaceAlt: '#f8fafc'
};
const dtA = (c, suffix) => `${c}${suffix}`;
const tint = (c) => dtA(c, '08');
const soft = (c) => dtA(c, '18');
const ring = (c) => dtA(c, '26');
const edge = (c) => dtA(c, '55');
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
// Semantic palette per metric column.
const C_ORDERS = '#0ea5e9';
const C_DELIVERIES = BRAND;
const C_PENDING = '#f59e0b';
const C_COMPLETED = '#10b981';
const C_CANCELLED = '#ef4444';
const C_ACCEPTED = '#6366f1';
const C_PICKED = '#8b5cf6';
const C_ARRIVED = '#06b6d4';
const C_SKIPPED = '#f97316';
const C_KMS = '#f59e0b';
const SoftPaper = (props) => (
<Paper
{...props}
sx={{
mt: 0.75,
borderRadius: 2,
boxShadow: DT.shadowPop,
border: '1px solid',
borderColor: 'divider',
overflow: 'hidden'
}}
/>
);
const AccentAvatar = ({ color, selected, size = 24, children }) => (
<Avatar
sx={{
width: size,
height: size,
bgcolor: selected ? color : soft(color),
color: selected ? '#fff' : color,
transition: 'background-color 0.15s, color 0.15s'
}}
>
{children}
</Avatar>
);
const pillFieldSx = (color) => ({
'& .MuiOutlinedInput-root': {
borderRadius: DT.radiusPill + 'px',
bgcolor: tint(color),
fontWeight: 600,
'& fieldset': { borderColor: edge(color), borderWidth: 1.5 },
'&:hover fieldset': { borderColor: color },
'&.Mui-focused': { boxShadow: `0 0 0 3px ${ring(color)}` },
'&.Mui-focused fieldset': { borderColor: color, borderWidth: 2 }
}
});
// Inline metric pill used in cells; faded slate when zero.
const MetricPill = ({ value, color, icon, isMoney = false }) => {
const n = Number(value);
const display = isMoney ? formatNumberToRupees(n) : Number.isFinite(n) ? n : value || 0;
const isZero = !Number.isFinite(n) || n === 0;
if (isZero) {
return (
<Typography variant="caption" sx={{ color: DT.textMuted, fontWeight: 700 }}>
{display}
</Typography>
);
}
return (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 0.875,
py: 0.25,
borderRadius: 999,
bgcolor: tint(color),
border: `1px solid ${edge(color)}`,
color,
fontSize: 12,
fontWeight: 800,
whiteSpace: 'nowrap'
}}
>
{icon}
{display}
</Box>
);
};
function formatNumberToRupees(value) {
return new Intl.NumberFormat('en-IN', {
style: 'currency',
currency: 'INR',
minimumFractionDigits: 2
}).format(value || 0);
}
const opentoast = (message, variant, time) => {
enqueueSnackbar(message, {
variant: variant,
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: time ? time : 1500
});
};
// ==============================|| OrdersReport ||============================== //
export default function OrdersReport() {
const theme = useTheme();
const tenantid = localStorage.getItem('tenantid');
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
const [open, setOpen] = useState(false);
const [openRow, setOpenRow] = useState(null);
const [datestatus, setDatestatus] = useState('Today');
const [total, settotal] = useState(0);
const [totalOrders, settotalOrders] = useState(0);
const [totalOrderPend, setTotalOrderPend] = useState(0);
const [totalOrderComplete, setTotalOrderComplete] = useState(0);
const [totalOrderCancel, setTotalOrderCancel] = useState(0);
const [totalDeliPend, setTotalDeliPend] = useState(0);
const [totalDeliComplete, setTotalDeliComplete] = useState(0);
const [totalDeliCancel, setTotalDeliCancel] = useState(0);
const [searchword, setSearchword] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
const textFieldRef = useRef(null);
const [ridersdata, setRidersdata] = useState(null);
const [selectedLocation, setSelectedLocation] = useState(null);
const [locationId, setLocationId] = useState(0);
const [locoName, setLocoName] = useState('All Locations');
const [searchLocation] = useState('');
// Debounce search.
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(searchword);
}, 400);
return () => clearTimeout(handler);
}, [searchword]);
// Ctrl/Cmd+K to focus the search.
useEffect(() => {
const handleKeyPress = (event) => {
if (event.key === 'k' && (event.metaKey || event.ctrlKey)) {
event.preventDefault();
textFieldRef.current && textFieldRef.current.focus();
}
if (event.key === 'Escape' && document.activeElement === textFieldRef.current) {
textFieldRef.current.blur();
}
};
document.addEventListener('keydown', handleKeyPress);
return () => document.removeEventListener('keydown', handleKeyPress);
}, []);
// ============================================= || gettenantlocations || =============================================
const {
data: tenantLocations,
isLoading: tenantLocationsIsLoading,
isError: tenantLocationsIsError,
error: tenantLocationsError
} = useQuery({
queryKey: ['tenantlocations', searchLocation],
queryFn: gettenantlocations
});
// ============================================= || getreportlocationsummary || =============================================
const {
isLoading: isLoadingReports,
isError: isErrorReports,
data: rows,
error: reportsError
} = useQuery({
queryKey: [startdate, enddate, locationId, debouncedSearch],
queryFn: getreportlocationsummary
});
// ============================================= || getriderlocationsummary || =============================================
const getriderlocationsummary = async (id) => {
try {
const riderRes = await axios.get(
`${process.env.REACT_APP_URL}/deliveries/getriderlocationsummary/?&tenantid=${tenantid}&locationid=${id}&fromdate=${startdate}&todate=${enddate}`
);
setRidersdata(riderRes.data.details);
} catch (error) {
console.log('riderRes', error);
}
};
// ============================================= || calculate totals || =============================================
const calculate = () => {
let calculatedTotal = 0;
let ordersTotal = 0;
let Orderpending = 0;
let OrderComplete = 0;
let OrderCancel = 0;
let deliverypending = 0;
let deliverycomplete = 0;
let deliverycancel = 0;
rows &&
rows.forEach((row) => {
calculatedTotal += row.charges;
ordersTotal += row.totalorders;
Orderpending += row.Orderspending;
OrderComplete += row.orderscompleted;
OrderCancel += row.orderscancelled;
deliverypending += row.deliveriespending;
deliverycomplete += row.deliveriescompleted;
deliverycancel += row.deliveriescancelled;
});
settotal(calculatedTotal);
settotalOrders(ordersTotal);
setTotalOrderPend(Orderpending);
setTotalOrderComplete(OrderComplete);
setTotalOrderCancel(OrderCancel);
setTotalDeliPend(deliverypending);
setTotalDeliComplete(deliverycomplete);
setTotalDeliCancel(deliverycancel);
};
useEffect(() => {
calculate();
}, [rows]);
let errormessage = '';
if (isErrorReports && reportsError?.message) {
errormessage = `An error has occurred: (isErrorReports) ${reportsError.message}`;
} else if (tenantLocationsIsError && tenantLocationsError?.message) {
errormessage = `An error has occurred: (tenantLocationsIsError) ${tenantLocationsError.message}`;
}
useEffect(() => {
if (errormessage) opentoast(errormessage, 'warning', 2000);
}, [errormessage]);
// KPI tiles — derived from the calculated totals.
const kpiCards = [
{ key: 'total', label: 'Total Orders', color: BRAND, icon: MdLocalShipping, value: totalOrders },
{ key: 'pending', label: 'Pending', color: C_PENDING, icon: MdHourglassEmpty, value: totalOrderPend },
{ key: 'completed', label: 'Completed', color: C_COMPLETED, icon: MdCheckCircle, value: totalOrderComplete },
{ key: 'cancelled', label: 'Cancelled', color: C_CANCELLED, icon: MdCancel, value: totalOrderCancel },
{ key: 'charges', label: 'Total Charges', color: C_ACCEPTED, icon: MdLocalOffer, value: total, isMoney: true }
];
return (
<>
{(isLoadingReports || tenantLocationsIsLoading) && <Loader />}
{(isLoadingReports || tenantLocationsIsLoading) && <CircularLoader />}
{/* ============================================= || Header || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1.5, md: 2 },
p: { xs: 1.5, sm: 2, md: 2.5 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
boxShadow: DT.shadowMd
}}
>
<Stack
direction={{ xs: 'column', sm: 'row' }}
alignItems={{ xs: 'flex-start', sm: 'center' }}
justifyContent="space-between"
spacing={{ xs: 1.5, sm: 2 }}
>
<Stack direction="row" alignItems="center" spacing={{ xs: 1.25, sm: 1.75 }}>
<Avatar
sx={{
width: { xs: 40, sm: 48 },
height: { xs: 40, sm: 48 },
bgcolor: BRAND,
color: '#fff',
boxShadow: `0 6px 18px ${ring(BRAND)}`
}}
>
<MdInsights size={22} />
</Avatar>
<Stack>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.25rem', sm: '1.5rem', md: '1.75rem' }
}}
>
Orders Summary
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ mt: 0.5 }}>
<Box
sx={{
width: 8,
height: 8,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 4px rgba(16,185,129,0.18)'
}}
/>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Live · {locoName} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
<Tooltip title="Date Filter">
<Box
onClick={() => setOpen(true)}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.75,
px: 1.5,
py: 0.875,
borderRadius: 999,
cursor: 'pointer',
bgcolor: '#fff',
border: `1.5px solid ${edge('#f59e0b')}`,
color: '#f59e0b',
fontWeight: 800,
fontSize: 12,
transition: 'all 0.18s',
'&:hover': { borderColor: '#f59e0b', boxShadow: `0 0 0 3px ${ring('#f59e0b')}` }
}}
>
<MdCalendarMonth size={14} />
{startdate && enddate
? `${dayjs(startdate).format('DD/MM/YY')} ${dayjs(enddate).format('DD/MM/YY')}`
: 'All time'}
</Box>
</Tooltip>
</Stack>
</Paper>
{/* ============================================= || KPI Cards || ============================================= */}
<Grid container spacing={{ xs: 1.25, sm: 1.5, md: 2 }}>
{kpiCards.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={6} md={item.key === 'charges' ? 4 : 2}>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
p: { xs: 1.25, sm: 1.75, md: 2.25 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.2s, box-shadow 0.2s, border-color 0.2s',
'&:hover': {
transform: 'translateY(-3px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
height: 3,
background: `linear-gradient(90deg, ${item.color} 0%, ${soft(item.color)} 100%)`
}}
/>
<Stack direction="row" alignItems="flex-start" justifyContent="space-between" spacing={1}>
<Stack spacing={0.5} sx={{ minWidth: 0, flex: 1 }}>
<Typography
variant="caption"
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: { xs: 10, sm: 11 },
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis'
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.1rem', sm: '1.35rem', md: '1.55rem' }
}}
>
{isLoadingReports ? (
<Skeleton sx={{ width: 40 }} animation="wave" />
) : item.isMoney ? (
formatNumberToRupees(item.value)
) : (
item.value
)}
</Typography>
</Stack>
<Avatar
sx={{
width: { xs: 32, sm: 38, md: 44 },
height: { xs: 32, sm: 38, md: 44 },
bgcolor: soft(item.color),
color: item.color,
boxShadow: `inset 0 0 0 1px ${edge(item.color)}`,
flexShrink: 0
}}
>
<Icon size={18} />
</Avatar>
</Stack>
</Paper>
</Grid>
);
})}
</Grid>
{/* ============================================= || Filter Bar || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1.5, md: 2 },
p: { xs: 1.25, md: 1.75 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
boxShadow: DT.shadowSoft
}}
>
<Stack
direction={{ xs: 'column', md: 'row' }}
spacing={1.25}
alignItems={{ xs: 'stretch', md: 'center' }}
justifyContent="space-between"
>
{/* Search */}
<Box sx={{ width: { xs: '100%', md: 320 } }}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.5,
borderRadius: 999,
bgcolor: tint(BRAND),
border: `1.5px solid ${edge(BRAND)}`,
transition: 'all 0.18s',
'&:focus-within': {
borderColor: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
}
}}
>
<MdSearch size={16} style={{ color: BRAND, flexShrink: 0 }} />
<InputBase
inputRef={textFieldRef}
placeholder="Search location (ctrl+k)"
value={searchword}
onChange={(e) => setSearchword(e.target.value)}
autoComplete="off"
sx={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: DT.textPrimary,
'& input::placeholder': { color: DT.textMuted, opacity: 1 }
}}
/>
{searchword && (
<IconButton size="small" onClick={() => setSearchword('')} sx={{ p: 0.25, color: BRAND }}>
<MdClear size={14} />
</IconButton>
)}
</Box>
</Box>
{/* Location filter */}
{tenantLocations?.length === 1 ? (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.75,
px: 1.5,
py: 0.875,
borderRadius: 999,
bgcolor: tint(BRAND),
border: `1.5px solid ${edge(BRAND)}`,
color: BRAND,
fontWeight: 800,
fontSize: 13
}}
>
<MdMyLocation size={14} /> {tenantLocations[0]?.locationname}
</Box>
) : (
<Autocomplete
options={tenantLocations || []}
value={selectedLocation}
getOptionLabel={(o) => (o ? `${o.locationname} (${o.suburb || ''})` : '')}
PaperComponent={SoftPaper}
onChange={(event, value) => {
setSelectedLocation(value);
setLocationId(value ? value.locationid : 0);
setLocoName(value ? value.locationname : 'All Locations');
}}
renderInput={(params) => (
<TextField
{...params}
placeholder="All Locations"
size="small"
sx={pillFieldSx('#10b981')}
InputProps={{
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5 }}>
<AccentAvatar color="#10b981" size={22} selected>
<MdPlace size={13} />
</AccentAvatar>
</Stack>
)
}}
/>
)}
sx={{ width: { xs: '100%', md: 320 } }}
/>
)}
</Stack>
</Paper>
{/* ============================================= || Table || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1.5, md: 2 },
borderRadius: DT.radiusCard / 8,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
background: '#fff'
}}
>
<TableContainer
sx={{
overflow: 'auto',
'&::-webkit-scrollbar': { width: 10, height: 10 },
'&::-webkit-scrollbar-thumb': {
backgroundColor: edge(BRAND),
borderRadius: 8,
'&:hover': { backgroundColor: BRAND }
},
'&::-webkit-scrollbar-track': { backgroundColor: DT.surfaceAlt }
}}
>
<Table sx={{ minWidth: 1100 }}>
<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: 1.5 }
}
}}
>
<TableCell rowSpan={2}>#</TableCell>
<TableCell rowSpan={2}>Location</TableCell>
<TableCell rowSpan={2} align="center">All</TableCell>
<TableCell
colSpan={3}
align="center"
sx={{
bgcolor: `${soft(C_ORDERS)} !important`,
borderLeft: `2px solid ${edge(C_ORDERS)} !important`,
borderRight: `2px solid ${edge(C_ORDERS)} !important`,
py: '6px !important'
}}
>
<Stack direction="row" alignItems="center" justifyContent="center" spacing={0.75}>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 20,
height: 20,
borderRadius: 999,
bgcolor: C_ORDERS,
color: '#fff',
boxShadow: `0 4px 10px ${ring(C_ORDERS)}`
}}
>
<MdInventory2 size={12} />
</Box>
<Typography sx={{ fontWeight: 900, fontSize: 12, letterSpacing: 1, color: C_ORDERS }}>
Orders
</Typography>
</Stack>
</TableCell>
<TableCell
colSpan={3}
align="center"
sx={{
bgcolor: `${soft(C_DELIVERIES)} !important`,
borderLeft: `2px solid ${edge(C_DELIVERIES)} !important`,
borderRight: `2px solid ${edge(C_DELIVERIES)} !important`,
py: '6px !important'
}}
>
<Stack direction="row" alignItems="center" justifyContent="center" spacing={0.75}>
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
width: 20,
height: 20,
borderRadius: 999,
bgcolor: C_DELIVERIES,
color: '#fff',
boxShadow: `0 4px 10px ${ring(C_DELIVERIES)}`
}}
>
<MdLocalShipping size={12} />
</Box>
<Typography sx={{ fontWeight: 900, fontSize: 12, letterSpacing: 1, color: C_DELIVERIES }}>
Deliveries
</Typography>
</Stack>
</TableCell>
<TableCell rowSpan={2} align="center">Kms</TableCell>
<TableCell rowSpan={2} align="right">Amount</TableCell>
<TableCell rowSpan={2} align="center">Action</TableCell>
</TableRow>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: { xs: 9.5, md: 10.5 },
fontWeight: 800,
letterSpacing: 0.6,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: { xs: 0.75, md: 1 },
px: { xs: 1, md: 1.5 }
}
}}
>
<TableCell
align="center"
sx={{
color: `${C_PENDING} !important`,
bgcolor: `${tint(C_ORDERS)} !important`,
borderLeft: `2px solid ${edge(C_ORDERS)} !important`
}}
>
Pending
</TableCell>
<TableCell align="center" sx={{ color: `${C_COMPLETED} !important`, bgcolor: `${tint(C_ORDERS)} !important` }}>
Completed
</TableCell>
<TableCell
align="center"
sx={{
color: `${C_CANCELLED} !important`,
bgcolor: `${tint(C_ORDERS)} !important`,
borderRight: `2px solid ${edge(C_ORDERS)} !important`
}}
>
Cancelled
</TableCell>
<TableCell
align="center"
sx={{
color: `${C_PENDING} !important`,
bgcolor: `${tint(C_DELIVERIES)} !important`,
borderLeft: `2px solid ${edge(C_DELIVERIES)} !important`
}}
>
Pending
</TableCell>
<TableCell align="center" sx={{ color: `${C_COMPLETED} !important`, bgcolor: `${tint(C_DELIVERIES)} !important` }}>
Completed
</TableCell>
<TableCell
align="center"
sx={{
color: `${C_CANCELLED} !important`,
bgcolor: `${tint(C_DELIVERIES)} !important`,
borderRight: `2px solid ${edge(C_DELIVERIES)} !important`
}}
>
Cancelled
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{rows && rows.length !== 0 ? (
rows.map((row, index) => (
<React.Fragment key={row.locationid}>
{/* ===================== || Main row || ===================== */}
<TableRow
sx={{
cursor: 'pointer',
transition: 'background-color 0.15s',
'& td': {
borderBottom: `1px solid ${DT.divider}`,
py: { xs: 1, md: 1.25 },
px: { xs: 1, md: 1.5 }
},
'& td.band-o': { backgroundColor: tint(C_ORDERS) },
'& td.band-d': { backgroundColor: tint(C_DELIVERIES) },
'& td.band-o-first': { borderLeft: `2px solid ${edge(C_ORDERS)}` },
'& td.band-o-last': { borderRight: `2px solid ${edge(C_ORDERS)}` },
'& td.band-d-first': { borderLeft: `2px solid ${edge(C_DELIVERIES)}` },
'& td.band-d-last': { borderRight: `2px solid ${edge(C_DELIVERIES)}` },
backgroundColor: openRow === row.locationid ? tint(BRAND) : 'transparent',
'&:hover': { backgroundColor: openRow === row.locationid ? soft(BRAND) : DT.surfaceAlt },
'&:hover td.band-o': { backgroundColor: soft(C_ORDERS) },
'&:hover td.band-d': { backgroundColor: soft(C_DELIVERIES) }
}}
>
<TableCell>
<Typography sx={{ fontWeight: 700, color: DT.textSecondary }}>{index + 1}</Typography>
</TableCell>
<TableCell>
<Stack>
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
{row.locationname}
</Typography>
<Typography variant="caption" sx={{ color: DT.textMuted }}>
Id : {row.locationid}
</Typography>
</Stack>
</TableCell>
<TableCell align="center">
<MetricPill value={row.totalorders} color={BRAND} icon={<MdInventory2 size={11} />} />
</TableCell>
<TableCell align="center" className="band-o band-o-first">
<MetricPill value={row.Orderspending} color={C_PENDING} icon={<MdHourglassEmpty size={11} />} />
</TableCell>
<TableCell align="center" className="band-o">
<MetricPill value={row.orderscompleted} color={C_COMPLETED} icon={<MdCheckCircle size={11} />} />
</TableCell>
<TableCell align="center" className="band-o band-o-last">
<MetricPill value={row.orderscancelled} color={C_CANCELLED} icon={<MdCancel size={11} />} />
</TableCell>
<TableCell align="center" className="band-d band-d-first">
<MetricPill value={row.deliveriespending} color={C_PENDING} icon={<MdHourglassEmpty size={11} />} />
</TableCell>
<TableCell align="center" className="band-d">
<MetricPill value={row.deliveriescompleted} color={C_COMPLETED} icon={<MdCheckCircle size={11} />} />
</TableCell>
<TableCell align="center" className="band-d band-d-last">
<MetricPill value={row.deliveriescancelled} color={C_CANCELLED} icon={<MdCancel size={11} />} />
</TableCell>
<TableCell align="center">
<Tooltip title="Cumulative Kms" placement="top">
<Box sx={{ display: 'inline-block' }}>
<MetricPill
value={parseFloat(row.cumulativekms).toFixed(2)}
color={C_KMS}
icon={<MdStraighten size={11} />}
/>
</Box>
</Tooltip>
</TableCell>
<TableCell align="right">
<Tooltip title="Total Charges" placement="top">
<Box sx={{ display: 'inline-block' }}>
<MetricPill value={row.charges} color={BRAND} icon={<MdCurrencyRupee size={11} />} isMoney />
</Box>
</Tooltip>
</TableCell>
<TableCell align="center">
<Tooltip title={openRow === row.locationid ? 'Collapse riders' : 'View riders'}>
<IconButton
size="small"
onClick={() => {
getriderlocationsummary(row.locationid);
setOpenRow(openRow === row.locationid ? null : row.locationid);
}}
sx={{
bgcolor: openRow === row.locationid ? BRAND : tint(BRAND),
border: `1px solid ${openRow === row.locationid ? BRAND : edge(BRAND)}`,
color: openRow === row.locationid ? '#fff' : BRAND,
borderRadius: 999,
p: 0.75,
transition: 'all 0.18s',
'&:hover': {
bgcolor: openRow === row.locationid ? BRAND : soft(BRAND),
borderColor: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
}
}}
>
{openRow === row.locationid ? <MdKeyboardArrowUp size={14} /> : <MdKeyboardArrowDown size={14} />}
</IconButton>
</Tooltip>
</TableCell>
</TableRow>
{/* ===================== || Collapsible riders row || ===================== */}
{openRow === row.locationid && (
<TableRow>
<TableCell colSpan={13} sx={{ p: 0, borderBottom: `1px solid ${DT.divider}`, bgcolor: DT.surfaceAlt }}>
<Collapse in={openRow === row.locationid} timeout="auto" unmountOnExit>
<Box sx={{ p: { xs: 1.5, md: 2 } }}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ mb: 1.25 }}>
<AccentAvatar color={BRAND} size={22} selected>
<MdLocalShipping size={12} />
</AccentAvatar>
<Typography variant="subtitle2" sx={{ fontWeight: 800, color: DT.textPrimary }}>
Riders Summary · {row.locationname}
</Typography>
</Stack>
<Paper
elevation={0}
sx={{
borderRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
background: '#fff'
}}
>
<Table size="small">
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: tint(BRAND),
color: BRAND,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.6,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${edge(BRAND)}`,
py: 0.75
}
}}
>
<TableCell align="center">#</TableCell>
<TableCell>Rider</TableCell>
<TableCell align="center">Deliveries</TableCell>
<TableCell align="center">Pending</TableCell>
<TableCell align="center">Assigned</TableCell>
<TableCell align="center">Accepted</TableCell>
<TableCell align="center">Arrived</TableCell>
<TableCell align="center">Picked</TableCell>
<TableCell align="center">Skipped</TableCell>
<TableCell align="center">Delivered</TableCell>
<TableCell align="center">Kms</TableCell>
<TableCell align="center">Charges</TableCell>
</TableRow>
</TableHead>
<TableBody>
{ridersdata && ridersdata.length > 0 ? (
ridersdata.map((rider, ri) => (
<TableRow
key={`${rider?.firstname}-${ri}`}
sx={{
'& td': { borderBottom: `1px solid ${DT.divider}`, py: 0.875, px: 1 },
'&:hover': { backgroundColor: DT.surfaceAlt }
}}
>
<TableCell align="center">
<Typography sx={{ fontWeight: 700, color: DT.textSecondary, fontSize: 12 }}>
{ri + 1}
</Typography>
</TableCell>
<TableCell>
<Stack direction="row" alignItems="center" spacing={0.75}>
<Typography
variant="caption"
sx={{ fontWeight: 700, color: DT.textPrimary }}
noWrap
>
{rider?.firstname}
</Typography>
{rider?.status == 'Active' ? (
<Tooltip title="Active">
<span style={{ display: 'inline-flex', color: C_COMPLETED }}>
<MdTaskAlt size={14} />
</span>
</Tooltip>
) : (
<Tooltip title="Inactive">
<span style={{ display: 'inline-flex', color: C_CANCELLED }}>
<MdHighlightOff size={14} />
</span>
</Tooltip>
)}
</Stack>
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.totalorders} color={BRAND} icon={<MdInventory2 size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.pending} color={C_PENDING} icon={<MdHourglassEmpty size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.assigned} color={C_ORDERS} icon={<MdCheckCircle size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.accepted} color={C_ACCEPTED} icon={<MdCheckCircle size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.arrived} color={C_ARRIVED} icon={<MdCheckCircle size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.picked} color={C_PICKED} icon={<MdLocalShipping size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.skipped} color={C_SKIPPED} icon={<MdCancel size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.delivered} color={C_COMPLETED} icon={<MdCheckCircle size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.cumulativekms} color={C_KMS} icon={<MdStraighten size={10} />} />
</TableCell>
<TableCell align="center">
<MetricPill value={rider?.deliveryamt} color={BRAND} icon={<MdCurrencyRupee size={10} />} isMoney />
</TableCell>
</TableRow>
))
) : (
<TableRow>
<TableCell colSpan={12} sx={{ py: 3, borderBottom: 'none' }}>
<Stack alignItems="center" spacing={1}>
<Avatar sx={{ width: 40, height: 40, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdLocalShipping size={18} />
</Avatar>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 700 }}>
No rider data for this location
</Typography>
</Stack>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</Paper>
</Box>
</Collapse>
</TableCell>
</TableRow>
)}
</React.Fragment>
))
) : (
<TableRow>
<TableCell colSpan={13} sx={{ py: 6, borderBottom: 'none' }}>
<Stack alignItems="center" spacing={1.5}>
<Avatar sx={{ width: 64, height: 64, bgcolor: soft('#94a3b8'), color: DT.textMuted }}>
<MdInsights size={28} />
</Avatar>
<Typography variant="subtitle1" sx={{ fontWeight: 700, color: DT.textPrimary }}>
No summary data
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
Adjust the date range or location filter above.
</Typography>
</Stack>
</TableCell>
</TableRow>
)}
{/* ===================== || Totals row || ===================== */}
{rows && rows.length !== 0 && (
<TableRow
sx={{
'& td': {
backgroundColor: tint(BRAND),
borderTop: `2px solid ${edge(BRAND)}`,
borderBottom: 'none',
py: 1.25,
px: 1.5
},
'& td.band-o': { backgroundColor: soft(C_ORDERS) },
'& td.band-d': { backgroundColor: soft(C_DELIVERIES) },
'& td.band-o-first': { borderLeft: `2px solid ${edge(C_ORDERS)}` },
'& td.band-o-last': { borderRight: `2px solid ${edge(C_ORDERS)}` },
'& td.band-d-first': { borderLeft: `2px solid ${edge(C_DELIVERIES)}` },
'& td.band-d-last': { borderRight: `2px solid ${edge(C_DELIVERIES)}` }
}}
>
<TableCell colSpan={2}>
<Stack direction="row" alignItems="center" spacing={1}>
<AccentAvatar color={BRAND} size={24} selected>
<MdReceiptLong size={13} />
</AccentAvatar>
<Typography sx={{ fontWeight: 800, color: BRAND, fontSize: 13 }}>Total</Typography>
</Stack>
</TableCell>
<TableCell align="center">
<Typography sx={{ fontWeight: 800, color: DT.textPrimary }}>{totalOrders}</Typography>
</TableCell>
<TableCell align="center" className="band-o band-o-first">
<Typography sx={{ fontWeight: 800, color: C_PENDING }}>{totalOrderPend}</Typography>
</TableCell>
<TableCell align="center" className="band-o">
<Typography sx={{ fontWeight: 800, color: C_COMPLETED }}>{totalOrderComplete}</Typography>
</TableCell>
<TableCell align="center" className="band-o band-o-last">
<Typography sx={{ fontWeight: 800, color: C_CANCELLED }}>{totalOrderCancel}</Typography>
</TableCell>
<TableCell align="center" className="band-d band-d-first">
<Typography sx={{ fontWeight: 800, color: C_PENDING }}>{totalDeliPend}</Typography>
</TableCell>
<TableCell align="center" className="band-d">
<Typography sx={{ fontWeight: 800, color: C_COMPLETED }}>{totalDeliComplete}</Typography>
</TableCell>
<TableCell align="center" className="band-d band-d-last">
<Typography sx={{ fontWeight: 800, color: C_CANCELLED }}>{totalDeliCancel}</Typography>
</TableCell>
<TableCell />
<TableCell align="right">
<Typography sx={{ fontWeight: 800, color: BRAND, fontSize: 14 }}>
{formatNumberToRupees(total)}
</Typography>
</TableCell>
<TableCell />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
{/* ============================================= || Date Filter Dialog || ============================================= */}
<Dialog open={open} onClose={() => setOpen(false)} PaperProps={{ sx: { borderRadius: 3 } }}>
<Box
sx={{
p: 2.5,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
<Stack direction="row" alignItems="center" spacing={1.5}>
<Avatar sx={{ bgcolor: BRAND, color: '#fff', width: 40, height: 40, boxShadow: `0 6px 18px ${ring(BRAND)}` }}>
<MdCalendarMonth size={20} />
</Avatar>
<Stack>
<Typography variant="h5" sx={{ fontWeight: 800, color: DT.textPrimary }}>
Select Date Range
</Typography>
<Typography variant="caption" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
Filter the summary by a date range or preset
</Typography>
</Stack>
</Stack>
</Box>
<DialogContent sx={{ width: '100%' }} className="datedialog">
<DateRangePicker
open={open}
toggle={() => setOpen(!open)}
id="daterange1"
onChange={(range) => {
if (range.label === 'All') {
setStartdate('');
setEnddate('');
setDatestatus('All');
setOpen(false);
} else {
setStartdate(dayjs(range.startDate).format('YYYY-MM-DD'));
setEnddate(dayjs(range.endDate).format('YYYY-MM-DD'));
setDatestatus(range.label || '');
}
}}
definedRanges={[
{ label: 'Today', startDate: new Date(), endDate: new Date() },
{ label: 'Yesterday', startDate: addDays(new Date(), -1), endDate: addDays(new Date(), -1) },
{ label: 'Tomorrow', startDate: addDays(new Date(), +1), endDate: addDays(new Date(), +1) },
{ label: 'This Week', startDate: startOfWeek(new Date()), endDate: endOfWeek(new Date()) },
{ label: 'Last Week', startDate: startOfWeek(addWeeks(new Date(), -1)), endDate: endOfWeek(addWeeks(new Date(), -1)) },
{ label: 'Last 7 Days', startDate: addWeeks(new Date(), -1), endDate: new Date() },
{ label: 'This Month', startDate: startOfMonth(new Date()), endDate: endOfMonth(new Date()) },
{ label: 'Last Month', startDate: startOfMonth(addMonths(new Date(), -1)), endDate: endOfMonth(addMonths(new Date(), -1)) },
{ label: 'All', startDate: new Date(), endDate: addDays(new Date(), -1) }
]}
/>
</DialogContent>
<Stack direction="row" justifyContent="flex-end" spacing={1} sx={{ width: '100%', p: 2, borderTop: `1px solid ${DT.divider}` }}>
<Button
variant="outlined"
onClick={() => setOpen(false)}
sx={{
borderRadius: 999,
px: 2.5,
borderColor: DT.borderSubtle,
color: DT.textSecondary,
fontWeight: 700,
'&:hover': { borderColor: DT.textSecondary, bgcolor: DT.surfaceAlt }
}}
>
Cancel
</Button>
<Button
variant="contained"
onClick={() => setOpen(false)}
sx={{
borderRadius: 999,
px: 3,
bgcolor: BRAND,
fontWeight: 700,
boxShadow: `0 6px 18px ${ring(BRAND)}`,
'&:hover': { bgcolor: '#4D1C61' }
}}
>
Apply
</Button>
</Stack>
</Dialog>
</>
);
}