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) => ( ); 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 } } }); // 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 ( {display} ); } return ( {icon} {display} ); }; 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) && } {(isLoadingReports || tenantLocationsIsLoading) && } {/* ============================================= || Header || ============================================= */} Orders Summary Live · {locoName} · {datestatus} 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')}` } }} > {startdate && enddate ? `${dayjs(startdate).format('DD/MM/YY')} – ${dayjs(enddate).format('DD/MM/YY')}` : 'All time'} {/* ============================================= || KPI Cards || ============================================= */} {kpiCards.map((item) => { const Icon = item.icon; return ( {item.label} {isLoadingReports ? ( ) : item.isMoney ? ( formatNumberToRupees(item.value) ) : ( item.value )} ); })} {/* ============================================= || Filter Bar || ============================================= */} {/* 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 }}> )} {/* Location filter */} {tenantLocations?.length === 1 ? ( {tenantLocations[0]?.locationname} ) : ( (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) => ( ) }} /> )} sx={{ width: { xs: '100%', md: 320 } }} /> )} {/* ============================================= || Table || ============================================= */} # Location All Orders Deliveries Kms Amount Action Pending Completed Cancelled Pending Completed Cancelled {rows && rows.length !== 0 ? ( rows.map((row, index) => ( {/* ===================== || Main row || ===================== */} {index + 1} {row.locationname} Id : {row.locationid} } /> } /> } /> } /> } /> } /> } /> } /> } isMoney /> { 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 ? : } {/* ===================== || Collapsible riders row || ===================== */} {openRow === row.locationid && ( Riders Summary · {row.locationname}
# Rider Deliveries Pending Assigned Accepted Arrived Picked Skipped Delivered Kms Charges {ridersdata && ridersdata.length > 0 ? ( ridersdata.map((rider, ri) => ( {ri + 1} {rider?.firstname} {rider?.status == 'Active' ? ( ) : ( )} } /> } /> } /> } /> } /> } /> } /> } /> } /> } isMoney /> )) ) : ( No rider data for this location )}
)} )) ) : ( No summary data Adjust the date range or location filter above. )} {/* ===================== || Totals row || ===================== */} {rows && rows.length !== 0 && ( Total {totalOrders} {totalOrderPend} {totalOrderComplete} {totalOrderCancel} {totalDeliPend} {totalDeliComplete} {totalDeliCancel} {formatNumberToRupees(total)} )}
{/* ============================================= || Date Filter Dialog || ============================================= */} setOpen(false)} PaperProps={{ sx: { borderRadius: 3 } }}> Select Date Range Filter the summary by a date range or preset 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) } ]} /> ); }