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

1653 lines
64 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 { useInfiniteQuery, useQuery } from '@tanstack/react-query';
import { useTheme } from '@mui/material/styles';
import { enqueueSnackbar } from 'notistack';
import {
Avatar,
Box,
Button,
CircularProgress,
Dialog,
DialogContent,
Grid,
IconButton,
InputBase,
Paper,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
Tooltip,
Typography,
Autocomplete,
TextField,
Skeleton,
Menu,
Switch
} from '@mui/material';
import {
MdLocalShipping,
MdHourglassEmpty,
MdCheckCircle,
MdCancel,
MdMyLocation,
MdPlace,
MdSearch,
MdClear,
MdCalendarMonth,
MdReceiptLong,
MdStraighten,
MdCurrencyRupee,
MdInventory2,
MdHistoryToggleOff,
MdAccessTime,
MdInsights,
MdLocalOffer,
MdAssignmentTurnedIn,
MdFilterList,
MdViewWeek,
MdRestartAlt,
MdLock,
MdDoneAll
} 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 { fetchDeliverySummary, fetchorderdetails } from '../api/api';
import { CSVExport } from 'components/third-party/ReactTable';
import Loader from 'components/Loader';
import { useDebounce } from 'components/nearle_components/useDebounce';
import MapWithRoute from './mapWithRoute';
// ============================================================================
// 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 per-status palette — drives both status badges and the timeline cells.
// Colors per brand standard: green=delivered, amber=pending, blue=processing,
// red=cancelled, dark-red=failed, purple=on-hold.
const STATUS_META = {
created: { label: 'Created', color: '#3b82f6', icon: MdLocalShipping },
pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty },
accepted: { label: 'Accepted', color: '#6366f1', icon: MdAssignmentTurnedIn },
arrived: { label: 'Arrived', color: '#06b6d4', icon: MdCheckCircle },
picked: { label: 'Picked', color: '#8b5cf6', icon: MdLocalShipping },
active: { label: 'Active', color: '#0ea5e9', icon: MdLocalShipping },
processing: { label: 'Processing', color: '#3b82f6', icon: MdAccessTime },
onhold: { label: 'On Hold', color: '#8b5cf6', icon: MdHistoryToggleOff },
'on hold': { label: 'On Hold', color: '#8b5cf6', icon: MdHistoryToggleOff },
delivered: { label: 'Delivered', color: '#10b981', icon: MdCheckCircle },
skipped: { label: 'Skipped', color: '#f97316', icon: MdCancel },
failed: { label: 'Failed', color: '#991b1b', icon: MdCancel },
cancelled: { label: 'Cancelled', color: '#ef4444', icon: MdCancel }
};
const STATUS_OPTIONS = [
{ id: 0, status: 'All', statusLow: 'All', color: BRAND, icon: MdInsights },
{ id: 1, status: 'Pending', statusLow: 'pending', color: '#f59e0b', icon: MdHourglassEmpty },
{ id: 2, status: 'Accepted', statusLow: 'accepted', color: '#6366f1', icon: MdAssignmentTurnedIn },
{ id: 3, status: 'Arrived', statusLow: 'arrived', color: '#06b6d4', icon: MdCheckCircle },
{ id: 4, status: 'Picked', statusLow: 'picked', color: '#8b5cf6', icon: MdLocalShipping },
{ id: 5, status: 'Active', statusLow: 'active', color: '#14b8a6', icon: MdLocalShipping },
{ id: 6, status: 'Delivered', statusLow: 'delivered', color: '#10b981', icon: MdCheckCircle },
{ id: 7, status: 'Skipped', statusLow: 'skipped', color: '#f97316', icon: MdCancel },
{ id: 8, status: 'Cancelled', statusLow: 'cancelled', color: '#ef4444', icon: MdCancel }
];
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 }
}
});
// Filled status badge — high-contrast pill (white text on solid color).
const StatusBadge = ({ status }) => {
const meta = STATUS_META[String(status || '').toLowerCase()] || {
label: status || '—',
color: DT.textMuted,
icon: MdHistoryToggleOff
};
const Icon = meta.icon;
return (
<Box
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.5,
px: 1.125,
py: 0.375,
borderRadius: 999,
bgcolor: meta.color,
color: '#fff',
fontSize: 11,
fontWeight: 700,
letterSpacing: 0.2,
whiteSpace: 'nowrap',
minWidth: 86,
justifyContent: 'center',
boxShadow: `0 1px 2px ${ring(meta.color)}`
}}
>
<Icon size={12} /> {meta.label}
</Box>
);
};
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>
);
};
// Timeline cell — time dominant (bold/high-contrast), date secondary (muted).
// No decorative dot; the column header already names the event.
const TimelineCell = ({ value }) => {
if (!value) {
return (
<Typography sx={{ fontSize: 12, color: DT.textMuted, fontWeight: 700 }}>
</Typography>
);
}
return (
<Stack spacing={0} sx={{ lineHeight: 1.1 }}>
<Typography
sx={{
fontSize: 12.5,
fontWeight: 800,
color: DT.textPrimary,
letterSpacing: 0.1,
lineHeight: 1.15
}}
noWrap
>
{dayjs(value).format('hh:mm A')}
</Typography>
<Typography
sx={{
fontSize: 10.5,
fontWeight: 600,
color: DT.textMuted,
lineHeight: 1.2
}}
noWrap
>
{dayjs(value).format('DD MMM YYYY')}
</Typography>
</Stack>
);
};
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
});
};
// ==============================|| OrdersDetails ||============================== //
export default function OrdersDetails() {
const theme = useTheme();
const tenId = localStorage.getItem('tenantid');
const textFieldRef = useRef(null);
const loadMoreRef = useRef();
const containerRef = useRef();
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
const [open, setOpen] = useState(false);
const [datestatus, setDatestatus] = useState('Today');
const [totalCharge, settotalCharge] = useState(0);
const [totalAmount, settotalAmount] = useState(0);
const [searchword, setSearchword] = useState('');
// Density toggle — operators choose how many rows to surface per screen.
// Default 'compact' (rows ≈ 32px) so the table starts dense like Linear / Vercel.
const [density, setDensity] = useState(() => {
try { return localStorage.getItem('ordersDetails.density') || 'compact'; } catch { return 'compact'; }
});
const isCompact = density === 'compact';
const rowPadY = isCompact ? 0.5 : 1;
useEffect(() => {
try { localStorage.setItem('ordersDetails.density', density); } catch {}
}, [density]);
// Column visibility — eliminates horizontal scroll on smaller desktops by letting
// operators hide columns they don't need. Persists per browser.
const ALL_COLUMNS = [
{ key: 'index', label: '#', group: 'Core', required: true, defaultVisible: true, width: 36 },
{ key: 'location', label: 'Location / Order', group: 'Core', required: true, defaultVisible: true, minWidth: 150 },
{ key: 'pickup', label: 'Pickup', group: 'Core', defaultVisible: true, minWidth: 140 },
{ key: 'drop', label: 'Drop', group: 'Core', defaultVisible: true, minWidth: 140 },
{ key: 'status', label: 'Status', group: 'Core', required: true, defaultVisible: true, width: 110 },
{ key: 'assigned', label: 'Assigned', group: 'Lifecycle', defaultVisible: true, minWidth: 110 },
{ key: 'accepted', label: 'Accepted', group: 'Lifecycle', defaultVisible: false, minWidth: 90 },
{ key: 'arrived', label: 'Arrived', group: 'Lifecycle', defaultVisible: false, minWidth: 90 },
{ key: 'picked', label: 'Picked', group: 'Lifecycle', defaultVisible: true, minWidth: 90 },
{ key: 'delivered', label: 'Delivered', group: 'Lifecycle', defaultVisible: true, minWidth: 90 },
{ key: 'cancelled', label: 'Cancelled', group: 'Lifecycle', defaultVisible: false, minWidth: 90 },
{ key: 'kms', label: 'Kms', group: 'Metrics', defaultVisible: true, width: 70, align: 'center' },
{ key: 'charges', label: 'Charges', group: 'Metrics', defaultVisible: true, width: 100, align: 'right' }
];
const COLUMN_GROUPS = ['Core', 'Lifecycle', 'Metrics'];
const COLUMN_DEFAULTS = ALL_COLUMNS.reduce((acc, c) => ({ ...acc, [c.key]: c.defaultVisible }), {});
const [colVis, setColVis] = useState(() => {
try {
const saved = JSON.parse(localStorage.getItem('ordersDetails.cols') || 'null');
return saved && typeof saved === 'object' ? { ...COLUMN_DEFAULTS, ...saved } : COLUMN_DEFAULTS;
} catch { return COLUMN_DEFAULTS; }
});
useEffect(() => {
try { localStorage.setItem('ordersDetails.cols', JSON.stringify(colVis)); } catch {}
}, [colVis]);
const isVisible = (key) => {
const col = ALL_COLUMNS.find((c) => c.key === key);
return col?.required || colVis[key] !== false;
};
const visibleCount = ALL_COLUMNS.filter((c) => isVisible(c.key)).length;
const hiddenCount = ALL_COLUMNS.length - visibleCount;
const [colMenuAnchor, setColMenuAnchor] = useState(null);
const toggleCol = (key) => setColVis((prev) => ({ ...prev, [key]: !(prev[key] !== false) }));
const resetCols = () => setColVis(COLUMN_DEFAULTS);
const showAllCols = () => setColVis(ALL_COLUMNS.reduce((acc, c) => ({ ...acc, [c.key]: true }), {}));
const isDefaultCols = ALL_COLUMNS.every((c) => isVisible(c.key) === !!c.defaultVisible);
const [riderCoordinates, setRiderCoordinates] = useState([]);
const [riderStart, setRiderStart] = useState();
const [riderEnd, setRiderEnd] = useState();
const [mapOpen, setMapOpen] = useState(false);
const [mapTenant, setMapTenant] = useState({});
const [currentStatus, setCurrentStatus] = useState('All');
const [statusCount, setStatusCount] = useState(0);
const [statusValue, setStatusValue] = useState(STATUS_OPTIONS[0]);
const [tenantLocations, setTenantlocations] = useState([]);
const [locationId, setLocationId] = useState(0);
const [locoName, setLocoName] = useState('All Locations');
const [selectedLocation, setSelectedLocation] = useState(null);
const debouncedSearch = useDebounce(searchword, 500);
// ============================================= || gettenantlocations || =============================================
const gettenantlocations = async (id) => {
try {
const res = await axios.get(`${process.env.REACT_APP_URL}/tenants/gettenantlocations/?tenantid=${id}`);
setTenantlocations(res.data.details || []);
} catch (err) {
console.log('gettenantlocations', err);
}
};
useEffect(() => {
gettenantlocations(tenId);
}, []);
// ============================================= || getdeliverylogs (for map) || =============================================
const getdeliverylogs = async (id) => {
try {
const res = await axios.get(`${process.env.REACT_APP_URL}/deliveries/getdeliverylogs/?deliveryid=${id}`);
const datas = res.data.details;
if (datas.length != 0) {
setRiderStart(datas[0].logdate);
setRiderEnd(datas[datas.length - 1].logdate);
const coData = datas.map((data) => ({ lat: data.latitude, lng: data.longitude }));
setRiderCoordinates(coData);
} else {
opentoast('No Logs Found ', 'error', 2000);
}
} catch (error) {
console.log('getdeliverylogs', error);
}
};
// ============================================= || ctrl/cmd+k focuses 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);
}, []);
// ============================================= || fetchorderdetails (infinite) || =============================================
const {
data: rowdata,
isError: isErrorOrderDetails,
error: orderDetailsError,
fetchNextPage,
isLoading: isLoadingOrderDetails,
hasNextPage,
isFetchingNextPage
} = useInfiniteQuery({
queryKey: [startdate, enddate, currentStatus, locationId, debouncedSearch],
queryFn: fetchorderdetails,
getNextPageParam: (lastPage) => lastPage.nextPage
});
const rows = rowdata?.pages.flatMap((page) => page.details) || [];
useEffect(() => {
if (!hasNextPage) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
fetchNextPage();
}
},
{
// The page (viewport) is now the scroll container, not the table.
// Prefetch the next page ~400px before the sentinel reaches the bottom.
root: null,
rootMargin: '0px 0px 400px 0px',
threshold: 0
}
);
if (loadMoreRef.current) observer.observe(loadMoreRef.current);
return () => {
if (loadMoreRef.current) observer.unobserve(loadMoreRef.current);
};
}, [hasNextPage, fetchNextPage]);
const handleScroll = (event) => {
const { scrollTop, scrollHeight, clientHeight } = event.currentTarget;
if (scrollTop + clientHeight >= scrollHeight - 50) {
if (hasNextPage && !isFetchingNextPage) {
fetchNextPage();
}
}
};
// ============================================= || fetchDeliverySummary || =============================================
const { data: deliverycount } = useQuery({
queryKey: ['deliverycount', startdate, enddate, currentStatus, locationId],
queryFn: fetchDeliverySummary
});
useEffect(() => {
const map = {
All: deliverycount?.total,
pending: deliverycount?.pending,
accepted: deliverycount?.accepted,
arrived: deliverycount?.arrived,
picked: deliverycount?.picked,
active: deliverycount?.active,
delivered: deliverycount?.delivered,
cancelled: deliverycount?.cancelled
};
setStatusCount(map[currentStatus] ?? 0);
}, [currentStatus, deliverycount]);
// ============================================= || calculate totals || =============================================
useEffect(() => {
let totalC = 0;
let totalA = 0;
rows &&
rows.forEach((row) => {
totalC += row.deliverycharges;
totalA += row.deliveryamt;
});
settotalCharge(totalC);
settotalAmount(totalA);
}, [rows]);
if (isErrorOrderDetails) return 'An error has occurred:(isErrorOrderDetails) ' + orderDetailsError.message;
// CSV export rows.
const csvData = rows.map((order) => ({
tenantname: order.tenantname,
tenantcity: order.tenantcity,
tenantcontactno: order.tenantcontactno,
rider: order.rider,
orderid: order.orderid,
orderdate: order.orderdate,
deliverydate: order.deliverydate,
orderstatus: order.orderstatus,
deliverystatus: order.deliverystatus,
ordernotes: order.ordernotes,
kms: order.kms,
actualkms: order.actualkms,
assigntime: order.assigntime,
starttime: order.starttime,
arrivaltime: order.arrivaltime,
pickuptime: order.pickuptime,
deliverytime: order.deliverytime,
canceltime: order.canceltime,
deliverycharge: order.deliverycharge,
deliveryamt: order.deliveryamt,
pickupcustomer: order.pickupcustomer,
pickupcontactno: order.pickupcontactno,
pickupaddress: order.pickupaddress,
pickupsuburb: order.pickupsuburb,
pickupcity: order.pickupcity,
pickuplat: order.pickuplat,
pickuplong: order.pickuplong,
deliverycustomer: order.deliverycustomer,
deliverycontactno: order.deliverycontactno,
deliveryaddress: order.deliveryaddress,
deliverysuburb: order.deliverysuburb,
deliverylat: order.deliverylat,
deliverylong: order.deliverylong,
locationname: order.locationname,
locationsuburb: order.locationsuburb,
locationcity: order.locationcity,
locationcontactno: order.locationcontactno
}));
// KPI tiles row — clearer financial terminology with tooltips.
const kpiCards = [
{ key: 'total', label: `${currentStatus === 'All' ? 'Total' : currentStatus} Orders`, color: BRAND, icon: MdInsights, value: statusCount, hint: 'Orders matching the current filters.' },
{ key: 'pending', label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty, value: deliverycount?.pending ?? 0, hint: 'Orders awaiting rider assignment or pickup.' },
{ key: 'delivered', label: 'Delivered', color: '#10b981', icon: MdCheckCircle, value: deliverycount?.delivered ?? 0, hint: 'Successfully delivered orders.' },
{ key: 'charges', label: 'Delivery Charges', color: '#6366f1', icon: MdLocalOffer, value: totalCharge, isMoney: true, hint: 'Total platform / delivery fees billed across the filtered orders.' },
{ key: 'amount', label: 'Order Value', color: '#10b981', icon: MdCurrencyRupee, value: totalAmount, isMoney: true, hint: 'Total customer order value across the filtered orders.' }
];
return (
<>
{isLoadingOrderDetails && <Loader />}
{/* ============================================= || Header (compact, standardized actions) || ============================================= */}
<Paper
elevation={0}
sx={{
mb: { xs: 1, md: 1.25 },
px: { xs: 1.5, sm: 2 },
py: { xs: 1, sm: 1.25 },
borderRadius: 2,
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, sm: 1.5 }}
>
<Stack direction="row" alignItems="center" spacing={1.25}>
<Avatar
variant="rounded"
sx={{
width: 36,
height: 36,
bgcolor: BRAND,
color: '#fff',
borderRadius: 1.5,
boxShadow: `0 4px 12px ${ring(BRAND)}`
}}
>
<MdReceiptLong size={19} />
</Avatar>
<Stack spacing={0.125}>
<Typography
variant="h3"
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.1,
fontSize: { xs: '1.1rem', sm: '1.25rem', md: '1.375rem' }
}}
>
Orders Details
</Typography>
<Stack direction="row" alignItems="center" spacing={0.75}>
<Box
sx={{
width: 7,
height: 7,
borderRadius: '50%',
bgcolor: '#10b981',
boxShadow: '0 0 0 3px rgba(16,185,129,0.18)'
}}
/>
<Typography sx={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 600 }}>
Live · {locoName} · {datestatus}
</Typography>
</Stack>
</Stack>
</Stack>
{/* Action bar — unified button system: 32px height, 8px radius, identical typography */}
<Stack direction="row" alignItems="center" spacing={0.75} flexWrap="wrap" useFlexGap>
<Tooltip title={isCompact ? 'Switch to comfortable density' : 'Switch to compact density'} arrow>
<Button
onClick={() => setDensity((d) => (d === 'compact' ? 'comfortable' : 'compact'))}
disableElevation
variant="outlined"
size="small"
startIcon={<MdFilterList size={15} style={{ transform: isCompact ? 'none' : 'scaleY(1.4)' }} />}
sx={{
height: 32,
px: 1.25,
borderRadius: 1.5,
textTransform: 'none',
fontSize: 12.5,
fontWeight: 700,
bgcolor: '#fff',
borderColor: DT.borderSubtle,
color: DT.textPrimary,
'&:hover': {
bgcolor: '#fff',
borderColor: BRAND,
color: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
}
}}
>
{isCompact ? 'Compact' : 'Comfortable'}
</Button>
</Tooltip>
<Tooltip title="Show or hide columns" arrow>
<Button
onClick={(e) => setColMenuAnchor(e.currentTarget)}
disableElevation
variant="outlined"
size="small"
startIcon={<MdViewWeek size={15} />}
endIcon={
hiddenCount > 0 ? (
<Box
component="span"
sx={{
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
minWidth: 30,
height: 18,
px: 0.5,
borderRadius: 999,
bgcolor: soft(BRAND),
color: BRAND,
fontSize: 10.5,
fontWeight: 800,
lineHeight: 1
}}
>
{visibleCount}/{ALL_COLUMNS.length}
</Box>
) : null
}
sx={{
height: 32,
px: 1.25,
borderRadius: 1.5,
textTransform: 'none',
fontSize: 12.5,
fontWeight: 700,
bgcolor: hiddenCount > 0 ? tint(BRAND) : '#fff',
borderColor: hiddenCount > 0 ? edge(BRAND) : DT.borderSubtle,
color: hiddenCount > 0 ? BRAND : DT.textPrimary,
'&:hover': {
bgcolor: hiddenCount > 0 ? tint(BRAND) : '#fff',
borderColor: BRAND,
color: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
}
}}
>
Columns
</Button>
</Tooltip>
<Tooltip title="Filter by date range" arrow>
<Button
onClick={() => setOpen(true)}
disableElevation
variant="outlined"
size="small"
startIcon={<MdCalendarMonth size={15} />}
sx={{
height: 32,
px: 1.25,
borderRadius: 1.5,
textTransform: 'none',
fontSize: 12.5,
fontWeight: 700,
letterSpacing: 0.1,
bgcolor: '#fff',
borderColor: DT.borderSubtle,
color: DT.textPrimary,
'&:hover': {
bgcolor: '#fff',
borderColor: BRAND,
color: BRAND,
boxShadow: `0 0 0 3px ${ring(BRAND)}`
},
'&:focus-visible': { boxShadow: `0 0 0 3px ${ring(BRAND)}` }
}}
>
{startdate && enddate
? `${dayjs(startdate).format('DD MMM')} ${dayjs(enddate).format('DD MMM')}`
: 'All time'}
</Button>
</Tooltip>
{/* Download — CSVExport already renders its own Button; style it directly so
only ONE button paints in the action bar. Matches the contained brand look. */}
<Box
sx={{
display: 'inline-flex',
'& a': { textDecoration: 'none', lineHeight: 0 }
}}
>
<CSVExport
data={csvData}
filename={`Orders_Detail_${dayjs().format('YYYY-MM-DD_HHmmss')}.csv`}
label="Download"
style={{
height: 32,
minHeight: 32,
px: 1.5,
borderRadius: 1.5,
bgcolor: BRAND,
color: '#fff',
textTransform: 'none',
fontSize: 12.5,
fontWeight: 700,
letterSpacing: 0.1,
boxShadow: `0 2px 6px ${ring(BRAND)}`,
'&:hover': { bgcolor: BRAND_LIGHT, boxShadow: `0 4px 12px ${ring(BRAND)}` },
'&:focus-visible': { boxShadow: `0 0 0 3px ${ring(BRAND)}` },
'& .MuiButton-startIcon': { mr: 0.75, '& svg': { fontSize: 16 } }
}}
/>
</Box>
</Stack>
</Stack>
</Paper>
{/* ============================================= || KPI Cards (compact) || ============================================= */}
<Grid container spacing={{ xs: 1, sm: 1.25, md: 1.5 }}>
{kpiCards.map((item) => {
const Icon = item.icon;
return (
<Grid item key={item.key} xs={6} sm={4} md={item.isMoney ? 3 : 2}>
<Tooltip title={item.hint || ''} placement="top" arrow>
<Paper
elevation={0}
sx={{
position: 'relative',
overflow: 'hidden',
px: { xs: 1.25, sm: 1.5 },
py: { xs: 0.875, sm: 1.125 },
borderRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
transition: 'transform 0.15s, box-shadow 0.15s, border-color 0.15s',
'&:hover': {
transform: 'translateY(-1px)',
boxShadow: DT.shadowMd,
borderColor: edge(item.color)
}
}}
>
<Box
sx={{
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
width: 3,
background: item.color
}}
/>
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1} sx={{ pl: 0.5 }}>
<Stack spacing={0.125} sx={{ minWidth: 0, flex: 1 }}>
<Typography
sx={{
color: DT.textSecondary,
fontWeight: 700,
letterSpacing: 0.4,
textTransform: 'uppercase',
fontSize: 10.5,
whiteSpace: 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
lineHeight: 1.2
}}
>
{item.label}
</Typography>
<Typography
sx={{
fontWeight: 800,
color: DT.textPrimary,
lineHeight: 1.15,
fontSize: { xs: '0.95rem', sm: '1.1rem', md: '1.2rem' }
}}
noWrap
>
{isLoadingOrderDetails && !item.value ? (
<Skeleton sx={{ width: 40 }} animation="wave" />
) : item.isMoney ? (
formatNumberToRupees(item.value)
) : (
item.value ?? 0
)}
</Typography>
</Stack>
<Avatar
variant="rounded"
sx={{
width: 30,
height: 30,
bgcolor: soft(item.color),
color: item.color,
borderRadius: 1.25,
flexShrink: 0
}}
>
<Icon size={15} />
</Avatar>
</Stack>
</Paper>
</Tooltip>
</Grid>
);
})}
</Grid>
{/* ============================================= || Filter Bar (compact) || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1, md: 1.25 },
p: { xs: 1, md: 1.125 },
borderRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
background: '#fff',
boxShadow: DT.shadowSoft
}}
>
<Grid container spacing={1} alignItems="center">
{/* Location filter */}
<Grid item xs={12} sm={6} md={4}>
{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,
width: '100%'
}}
>
<MdMyLocation size={14} /> {tenantLocations[0].locationname}
</Box>
) : (
<Autocomplete
fullWidth
options={tenantLocations || []}
value={selectedLocation}
getOptionLabel={(option) => (option ? `${option.locationname} (${option.suburb || ''})` : '')}
PaperComponent={SoftPaper}
onChange={(event, value, reason) => {
if (value) {
setSelectedLocation(value);
setLocationId(value.locationid);
setLocoName(value.locationname);
}
if (reason === 'clear') {
setSelectedLocation(null);
setLocationId(0);
setLocoName('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>
)
}}
/>
)}
/>
)}
</Grid>
{/* Status filter */}
<Grid item xs={12} sm={6} md={4}>
<Autocomplete
fullWidth
options={STATUS_OPTIONS}
value={statusValue}
getOptionLabel={(option) => option?.status || ''}
PaperComponent={SoftPaper}
onChange={(event, value, reason) => {
if (reason === 'clear') {
setCurrentStatus('All');
setStatusValue(STATUS_OPTIONS[0]);
} else {
setCurrentStatus(value.statusLow);
setStatusValue(value);
}
}}
renderInput={(params) => (
<TextField
{...params}
placeholder="Status"
size="small"
sx={pillFieldSx(statusValue?.color || BRAND)}
InputProps={{
...params.InputProps,
startAdornment: (
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5 }}>
<AccentAvatar color={statusValue?.color || BRAND} size={22} selected>
{statusValue?.icon ? React.createElement(statusValue.icon, { size: 13 }) : <MdFilterList size={13} />}
</AccentAvatar>
</Stack>
)
}}
/>
)}
/>
</Grid>
{/* Search */}
<Grid item xs={12} sm={12} md={4}>
<Box
sx={{
display: 'flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.75,
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 (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>
</Grid>
</Grid>
</Paper>
{/* ============================================= || Table (dense, sticky header, no h-scroll) || ============================================= */}
<Paper
elevation={0}
sx={{
mt: { xs: 1, md: 1.25 },
borderRadius: 2,
border: '1px solid',
borderColor: DT.borderSubtle,
overflow: 'hidden',
background: '#fff'
}}
>
<TableContainer
ref={containerRef}
onScroll={handleScroll}
sx={{
// Single page scroll: the table is NOT height-capped, so it renders at its
// full height and the whole page scrolls as one. Scrolling down moves the
// KPI cards + header + filter bar off-screen and reveals the full table.
// Only horizontal overflow scrolls inside the container (for wide column sets).
overflowX: 'auto',
overflowY: 'visible',
'&::-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
size="small"
sx={{
// minWidth shrinks as columns are hidden, so horizontal scroll disappears entirely
// once the visible column set fits the viewport. Sum of declared widths.
minWidth: ALL_COLUMNS.filter((c) => isVisible(c.key))
.reduce((acc, c) => acc + (c.width || c.minWidth || 100), 0),
tableLayout: 'auto'
}}
>
<TableHead>
<TableRow
sx={{
'& th': {
backgroundColor: DT.surfaceAlt,
color: DT.textSecondary,
fontSize: 10.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
whiteSpace: 'nowrap',
borderBottom: `1px solid ${DT.borderSubtle}`,
py: 0.75,
px: 1
}
}}
>
{isVisible('index') && <TableCell sx={{ width: 36 }}>#</TableCell>}
{isVisible('location') && <TableCell sx={{ minWidth: 150 }}>Location / Order</TableCell>}
{isVisible('pickup') && <TableCell sx={{ minWidth: 140 }}>Pickup</TableCell>}
{isVisible('drop') && <TableCell sx={{ minWidth: 140 }}>Drop</TableCell>}
{isVisible('status') && <TableCell sx={{ width: 110 }}>Status</TableCell>}
{isVisible('assigned') && <TableCell sx={{ minWidth: 110 }}>Assigned</TableCell>}
{isVisible('accepted') && <TableCell sx={{ minWidth: 90 }}>Accepted</TableCell>}
{isVisible('arrived') && <TableCell sx={{ minWidth: 90 }}>Arrived</TableCell>}
{isVisible('picked') && <TableCell sx={{ minWidth: 90 }}>Picked</TableCell>}
{isVisible('delivered') && <TableCell sx={{ minWidth: 90 }}>Delivered</TableCell>}
{isVisible('cancelled') && <TableCell sx={{ minWidth: 90 }}>Cancelled</TableCell>}
{isVisible('kms') && <TableCell align="center" sx={{ width: 70 }}>Kms</TableCell>}
{isVisible('charges') && <TableCell align="right" sx={{ width: 100 }}>Charges</TableCell>}
</TableRow>
</TableHead>
<TableBody>
{rows?.length === 0 && !isLoadingOrderDetails && (
<TableRow>
<TableCell colSpan={visibleCount} sx={{ py: 7, borderBottom: 'none' }}>
<Stack alignItems="center" spacing={1.25}>
<Avatar
sx={{
width: 56,
height: 56,
bgcolor: soft('#94a3b8'),
color: DT.textMuted,
borderRadius: 2
}}
variant="rounded"
>
<MdReceiptLong size={26} />
</Avatar>
<Typography sx={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>
No orders match these filters
</Typography>
<Typography sx={{ color: DT.textSecondary, fontSize: 12 }}>
{searchword ? 'Try a different keyword or clear the search.' : 'Adjust the location, status, or date range above.'}
</Typography>
{searchword && (
<Button
size="small"
onClick={() => setSearchword('')}
sx={{
mt: 0.5,
height: 28,
textTransform: 'none',
fontSize: 12,
fontWeight: 700,
color: BRAND,
borderRadius: 1.25,
'&:hover': { bgcolor: tint(BRAND) }
}}
startIcon={<MdClear size={14} />}
>
Clear search
</Button>
)}
</Stack>
</TableCell>
</TableRow>
)}
{isLoadingOrderDetails &&
rows.length === 0 &&
Array.from({ length: 10 }).map((_, idx) => (
<TableRow key={`sk-${idx}`}>
{Array.from({ length: visibleCount }).map((__, ci) => (
<TableCell key={ci} sx={{ borderBottom: `1px solid ${DT.divider}`, py: 0.625, px: 1 }}>
<Skeleton animation="wave" height={20} />
</TableCell>
))}
</TableRow>
))}
{rows.map((row, index) => (
<TableRow
key={`${row.orderid}-${index}`}
sx={{
cursor: 'pointer',
transition: 'background-color 0.12s, box-shadow 0.12s',
'& td': {
borderBottom: `1px solid ${DT.divider}`,
py: rowPadY,
px: 1,
verticalAlign: 'top'
},
'&:hover': {
backgroundColor: tint(BRAND),
boxShadow: `inset 3px 0 0 ${BRAND}`
}
}}
>
{isVisible('index') && (
<TableCell>
<Typography sx={{ fontWeight: 700, fontSize: 12, color: DT.textMuted }}>{index + 1}</Typography>
</TableCell>
)}
{/* Location */}
{isVisible('location') && (
<TableCell>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
{row.locationname}
</Typography>
<Tooltip title="Order Id">
<Typography sx={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.orderid}
</Typography>
</Tooltip>
<Tooltip title="Delivery Date">
<Stack direction="row" alignItems="center" spacing={0.5} sx={{ mt: 0.125 }}>
<MdAccessTime size={10} style={{ color: DT.textMuted, flexShrink: 0 }} />
<Typography sx={{ fontSize: 10.5, color: DT.textSecondary, fontWeight: 700 }} noWrap>
{dayjs(row.deliverydate).utc().format('hh:mm A')}
</Typography>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600 }} noWrap>
· {dayjs(row.deliverydate).utc().format('DD MMM YY')}
</Typography>
</Stack>
</Tooltip>
</TableCell>
)}
{/* Pickup */}
{isVisible('pickup') && (
<TableCell>
<Stack spacing={0.125}>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
{row.pickupcustomer}
</Typography>
<Typography sx={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.pickupcontactno}
</Typography>
<Tooltip title={row.Pickupaddress || row.pickupaddress}>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.pickupsuburb ||
row.pickuplocation ||
(row.Pickupaddress ? `${row.Pickupaddress.slice(0, 20)}` : '—')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
)}
{/* Drop */}
{isVisible('drop') && (
<TableCell>
<Stack spacing={0.125}>
<Typography sx={{ fontSize: 12.5, fontWeight: 700, color: DT.textPrimary, lineHeight: 1.25 }} noWrap>
{row.deliverycustomer}
</Typography>
<Typography sx={{ fontSize: 11, color: DT.textSecondary, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.deliverycontactno}
</Typography>
<Tooltip title={row.deliveryaddress}>
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600, lineHeight: 1.3 }} noWrap>
{row.deliverysuburb ||
row.deliverylocation ||
(row.deliveryaddress ? `${row.deliveryaddress.slice(0, 20)}` : '—')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
)}
{/* Order Status */}
{isVisible('status') && (
<TableCell>
<StatusBadge status={row.orderstatus} />
</TableCell>
)}
{/* Assigned */}
{isVisible('assigned') && (
<TableCell>
<Stack spacing={0.25}>
{row.ridername && (
<Typography sx={{ fontSize: 11, fontWeight: 800, color: BRAND }} noWrap>
{row.ridername}
</Typography>
)}
<TimelineCell value={row.assigntime} />
</Stack>
</TableCell>
)}
{/* Accepted */}
{isVisible('accepted') && (
<TableCell>
<TimelineCell value={row.starttime} />
</TableCell>
)}
{/* Arrived */}
{isVisible('arrived') && (
<TableCell>
<TimelineCell value={row.arrivaltime} />
</TableCell>
)}
{/* Picked */}
{isVisible('picked') && (
<TableCell>
<TimelineCell value={row.pickuptime} />
</TableCell>
)}
{/* Delivered */}
{isVisible('delivered') && (
<TableCell>
<TimelineCell value={row.deliverytime} />
</TableCell>
)}
{/* Cancelled */}
{isVisible('cancelled') && (
<TableCell>
<TimelineCell value={row.canceltime} />
</TableCell>
)}
{/* Kms */}
{isVisible('kms') && (
<TableCell align="center">
<MetricPill
value={
row.orderstatus === 'cancelled' || row.kms === ''
? 0
: Number(parseFloat(row.kms || 0).toFixed(1))
}
color="#f59e0b"
icon={<MdStraighten size={11} />}
/>
</TableCell>
)}
{/* Charges */}
{isVisible('charges') && (
<TableCell align="right">
<MetricPill
value={
row.orderstatus === 'cancelled' || row.deliverycharges === ''
? 0
: Number(row.deliverycharges)
}
color={BRAND}
icon={<MdCurrencyRupee size={11} />}
isMoney
/>
</TableCell>
)}
</TableRow>
))}
{rows.length !== 0 && (
<TableRow sx={{ '&:hover': { backgroundColor: 'transparent !important' } }}>
<TableCell colSpan={visibleCount} sx={{ borderBottom: 'none', py: 1, bgcolor: DT.surfaceAlt }}>
<Stack
ref={loadMoreRef}
direction="row"
alignItems="center"
justifyContent="center"
spacing={1}
sx={{ minHeight: 32 }}
>
{isFetchingNextPage || hasNextPage ? (
<>
<CircularProgress size={14} sx={{ color: BRAND }} />
<Typography sx={{ fontSize: 11.5, color: DT.textSecondary, fontWeight: 700 }}>
Loading more orders
</Typography>
</>
) : (
<Typography sx={{ fontSize: 11.5, color: DT.textMuted, fontWeight: 700, letterSpacing: 0.3 }}>
{rows.length} order{rows.length === 1 ? '' : 's'} · End of list
</Typography>
)}
</Stack>
</TableCell>
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
</Paper>
{/* ============================================= || Columns Visibility Menu || ============================================= */}
<Menu
anchorEl={colMenuAnchor}
open={Boolean(colMenuAnchor)}
onClose={() => setColMenuAnchor(null)}
anchorOrigin={{ vertical: 'bottom', horizontal: 'right' }}
transformOrigin={{ vertical: 'top', horizontal: 'right' }}
slotProps={{
paper: {
sx: {
mt: 0.75,
width: 300,
borderRadius: 2.5,
border: `1px solid ${DT.borderSubtle}`,
boxShadow: DT.shadowPop,
overflow: 'hidden'
}
}
}}
MenuListProps={{ sx: { py: 0 } }}
>
{/* Header — title, live count, quick actions */}
<Box
sx={{
px: 1.5,
pt: 1.25,
pb: 1.125,
background: `linear-gradient(135deg, ${tint(BRAND)} 0%, ${tint(BRAND_LIGHT)} 100%)`,
borderBottom: `1px solid ${DT.borderSubtle}`
}}
>
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1}>
<Avatar variant="rounded" sx={{ width: 28, height: 28, bgcolor: BRAND, color: '#fff', borderRadius: 1.25 }}>
<MdViewWeek size={15} />
</Avatar>
<Stack spacing={0}>
<Typography sx={{ fontSize: 13, fontWeight: 800, color: DT.textPrimary, lineHeight: 1.15 }}>
Columns
</Typography>
<Typography sx={{ fontSize: 10.5, fontWeight: 700, color: DT.textSecondary, lineHeight: 1.2 }}>
{visibleCount} of {ALL_COLUMNS.length} shown
</Typography>
</Stack>
</Stack>
</Stack>
<Stack direction="row" spacing={0.75} sx={{ mt: 1.125 }}>
<Button
size="small"
onClick={showAllCols}
disabled={hiddenCount === 0}
startIcon={<MdDoneAll size={13} />}
sx={{
flex: 1,
height: 28,
px: 1,
fontSize: 11.5,
fontWeight: 800,
textTransform: 'none',
borderRadius: 1.25,
color: BRAND,
bgcolor: '#fff',
border: `1px solid ${edge(BRAND)}`,
'&:hover': { bgcolor: tint(BRAND), borderColor: BRAND },
'&.Mui-disabled': { color: DT.textMuted, bgcolor: '#fff', borderColor: DT.borderSubtle, opacity: 0.7 }
}}
>
Show all
</Button>
<Button
size="small"
onClick={resetCols}
disabled={isDefaultCols}
startIcon={<MdRestartAlt size={14} />}
sx={{
flex: 1,
height: 28,
px: 1,
fontSize: 11.5,
fontWeight: 800,
textTransform: 'none',
borderRadius: 1.25,
color: DT.textSecondary,
bgcolor: '#fff',
border: `1px solid ${DT.borderSubtle}`,
'&:hover': { bgcolor: DT.surfaceAlt, borderColor: DT.textMuted, color: DT.textPrimary },
'&.Mui-disabled': { color: DT.textMuted, bgcolor: '#fff', borderColor: DT.borderSubtle, opacity: 0.7 }
}}
>
Reset
</Button>
</Stack>
</Box>
{/* Grouped, switch-driven column list */}
<Box sx={{ maxHeight: 340, overflowY: 'auto', py: 0.5 }}>
{COLUMN_GROUPS.map((group) => {
const cols = ALL_COLUMNS.filter((c) => c.group === group);
const shownInGroup = cols.filter((c) => isVisible(c.key)).length;
return (
<Box key={group} sx={{ px: 0.5, pb: 0.25 }}>
<Stack
direction="row"
alignItems="center"
justifyContent="space-between"
sx={{ px: 1, pt: 0.875, pb: 0.375 }}
>
<Typography
sx={{ fontSize: 10, fontWeight: 800, color: DT.textMuted, letterSpacing: 0.7, textTransform: 'uppercase' }}
>
{group}
</Typography>
<Typography sx={{ fontSize: 10, fontWeight: 700, color: DT.textMuted }}>
{shownInGroup}/{cols.length}
</Typography>
</Stack>
{cols.map((c) => {
const checked = isVisible(c.key);
const disabled = !!c.required;
return (
<Box
key={c.key}
onClick={() => !disabled && toggleCol(c.key)}
sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
gap: 1,
mx: 0.5,
px: 1,
py: 0.375,
borderRadius: 1.5,
cursor: disabled ? 'default' : 'pointer',
bgcolor: checked ? tint(BRAND) : 'transparent',
border: `1px solid ${checked ? edge(BRAND) : 'transparent'}`,
transition: 'background-color 0.15s, border-color 0.15s',
'&:hover': { bgcolor: disabled ? (checked ? tint(BRAND) : 'transparent') : soft(BRAND) }
}}
>
<Stack direction="row" alignItems="center" spacing={0.625} sx={{ minWidth: 0 }}>
<Typography
noWrap
sx={{ fontSize: 13, fontWeight: 700, color: checked ? DT.textPrimary : DT.textSecondary }}
>
{c.label}
</Typography>
{disabled && (
<Tooltip title="Always visible" arrow>
<Box sx={{ display: 'inline-flex', color: DT.textMuted }}>
<MdLock size={11} />
</Box>
</Tooltip>
)}
</Stack>
<Switch
size="small"
checked={checked}
disabled={disabled}
onClick={(e) => e.stopPropagation()}
onChange={() => !disabled && toggleCol(c.key)}
sx={{
'& .MuiSwitch-switchBase.Mui-checked': { color: BRAND },
'& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': { backgroundColor: BRAND, opacity: 1 },
'& .MuiSwitch-switchBase.Mui-disabled.Mui-checked': { color: BRAND_LIGHT },
'& .MuiSwitch-switchBase.Mui-disabled.Mui-checked + .MuiSwitch-track': { backgroundColor: BRAND_LIGHT, opacity: 0.6 }
}}
/>
</Box>
);
})}
</Box>
);
})}
</Box>
</Menu>
{/* ============================================= || 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 order details 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>
{/* ============================================= || MapWithRoute Dialog || ============================================= */}
<Dialog
open={mapOpen}
onClose={() => setMapOpen(false)}
maxWidth="lg"
fullWidth
PaperProps={{ sx: { borderRadius: 3, overflow: 'hidden' } }}
>
{riderCoordinates && (
<Box>
<MapWithRoute
coordinates={riderCoordinates}
additionalProps={{ riderStart, riderEnd }}
order={mapTenant}
setMapOpen={setMapOpen}
/>
</Box>
)}
</Dialog>
</>
);
}