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) => (
);
const AccentAvatar = ({ color, selected, size = 24, children }) => (
{children}
);
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 (
{meta.label}
);
};
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 (
{display}
);
}
return (
{icon}
{display}
);
};
// 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 (
—
);
}
return (
{dayjs(value).format('hh:mm A')}
{dayjs(value).format('DD MMM YYYY')}
);
};
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 && }
{/* ============================================= || Header (compact, standardized actions) || ============================================= */}
Orders Details
Live · {locoName} · {datestatus}
{/* Action bar — unified button system: 32px height, 8px radius, identical typography */}
{/* Download — CSVExport already renders its own Button; style it directly so
only ONE button paints in the action bar. Matches the contained brand look. */}
{/* ============================================= || KPI Cards (compact) || ============================================= */}
{kpiCards.map((item) => {
const Icon = item.icon;
return (
{item.label}
{isLoadingOrderDetails && !item.value ? (
) : item.isMoney ? (
formatNumberToRupees(item.value)
) : (
item.value ?? 0
)}
);
})}
{/* ============================================= || Filter Bar (compact) || ============================================= */}
{/* Location filter */}
{tenantLocations.length === 1 ? (
{tenantLocations[0].locationname}
) : (
(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) => (
)
}}
/>
)}
/>
)}
{/* Status filter */}
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) => (
{statusValue?.icon ? React.createElement(statusValue.icon, { size: 13 }) : }
)
}}
/>
)}
/>
{/* Search */}
setSearchword(e.target.value)}
autoComplete="off"
sx={{
flex: 1,
fontSize: 13,
fontWeight: 600,
color: DT.textPrimary,
'& input::placeholder': { color: DT.textMuted, opacity: 1 }
}}
/>
{searchword && (
setSearchword('')} sx={{ p: 0.25, color: BRAND }}>
)}
{/* ============================================= || Table (dense, sticky header, no h-scroll) || ============================================= */}
isVisible(c.key))
.reduce((acc, c) => acc + (c.width || c.minWidth || 100), 0),
tableLayout: 'auto'
}}
>
{isVisible('index') && #}
{isVisible('location') && Location / Order}
{isVisible('pickup') && Pickup}
{isVisible('drop') && Drop}
{isVisible('status') && Status}
{isVisible('assigned') && Assigned}
{isVisible('accepted') && Accepted}
{isVisible('arrived') && Arrived}
{isVisible('picked') && Picked}
{isVisible('delivered') && Delivered}
{isVisible('cancelled') && Cancelled}
{isVisible('kms') && Kms}
{isVisible('charges') && Charges}
{rows?.length === 0 && !isLoadingOrderDetails && (
No orders match these filters
{searchword ? 'Try a different keyword or clear the search.' : 'Adjust the location, status, or date range above.'}
{searchword && (
)}
)}
{isLoadingOrderDetails &&
rows.length === 0 &&
Array.from({ length: 10 }).map((_, idx) => (
{Array.from({ length: visibleCount }).map((__, ci) => (
))}
))}
{rows.map((row, index) => (
{isVisible('index') && (
{index + 1}
)}
{/* Location */}
{isVisible('location') && (
{row.locationname}
{row.orderid}
{dayjs(row.deliverydate).utc().format('hh:mm A')}
· {dayjs(row.deliverydate).utc().format('DD MMM YY')}
)}
{/* Pickup */}
{isVisible('pickup') && (
{row.pickupcustomer}
{row.pickupcontactno}
{row.pickupsuburb ||
row.pickuplocation ||
(row.Pickupaddress ? `${row.Pickupaddress.slice(0, 20)}…` : '—')}
)}
{/* Drop */}
{isVisible('drop') && (
{row.deliverycustomer}
{row.deliverycontactno}
{row.deliverysuburb ||
row.deliverylocation ||
(row.deliveryaddress ? `${row.deliveryaddress.slice(0, 20)}…` : '—')}
)}
{/* Order Status */}
{isVisible('status') && (
)}
{/* Assigned */}
{isVisible('assigned') && (
{row.ridername && (
{row.ridername}
)}
)}
{/* Accepted */}
{isVisible('accepted') && (
)}
{/* Arrived */}
{isVisible('arrived') && (
)}
{/* Picked */}
{isVisible('picked') && (
)}
{/* Delivered */}
{isVisible('delivered') && (
)}
{/* Cancelled */}
{isVisible('cancelled') && (
)}
{/* Kms */}
{isVisible('kms') && (
}
/>
)}
{/* Charges */}
{isVisible('charges') && (
}
isMoney
/>
)}
))}
{rows.length !== 0 && (
{isFetchingNextPage || hasNextPage ? (
<>
Loading more orders…
>
) : (
{rows.length} order{rows.length === 1 ? '' : 's'} · End of list
)}
)}
{/* ============================================= || Columns Visibility Menu || ============================================= */}
{/* ============================================= || Date Filter Dialog || ============================================= */}
{/* ============================================= || MapWithRoute Dialog || ============================================= */}
>
);
}