import * as React from 'react';
import { enqueueSnackbar } from 'notistack';
import { useState, useEffect, Fragment, useRef } from 'react';
import dayjs from 'dayjs';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
import axios from 'axios';
import { useTheme } from '@mui/material/styles';
import {
Avatar,
Box,
Button,
Grid,
IconButton,
Paper,
Stack,
Typography,
Table,
TableCell,
TableBody,
TableHead,
Dialog,
TableRow,
DialogContent,
Tooltip,
Skeleton,
Autocomplete,
TextField,
CircularProgress,
InputBase,
InputAdornment
} from '@mui/material';
import {
MdAccessTime,
MdCancel,
MdCheckCircle,
MdClose,
MdCurrencyRupee,
MdDeleteOutline,
MdHistoryToggleOff,
MdHourglassEmpty,
MdInventory2,
MdLocalShipping,
MdLocationOn,
MdMyLocation,
MdNote,
MdPlace,
MdSearch,
MdStraighten,
MdCalendarMonth,
MdReceiptLong,
MdClear
} from 'react-icons/md';
import TableContainer from '@mui/material/TableContainer';
import Loader from 'components/Loader';
import { useHotkeyFocus } from 'components/nearle_components/useHotkeyFocus';
import DateFilterDialog from 'components/nearle_components/DateFilterDialog';
import CircularLoader from 'components/nearle_components/CircularLoader';
import { useInfiniteQuery } from '@tanstack/react-query';
// ============================================================================
// 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';
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 }
}
});
// Semantic per-row status palette — colors per brand standard:
// green=delivered, amber=pending, blue=created/processing, red=cancelled,
// dark-red=failed, purple=on-hold.
const ROW_STATUS_META = {
created: { label: 'Created', color: '#3b82f6', icon: MdLocalShipping },
pending: { label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty },
processing: { label: 'Processing', color: '#3b82f6', icon: MdAccessTime },
modified: { label: 'Confirmed', color: '#10b981', icon: MdCheckCircle },
confirmed: { label: 'Confirmed', color: '#10b981', icon: MdCheckCircle },
ready: { label: 'Accepted', color: '#6366f1', icon: MdCheckCircle },
active: { label: 'Picked', color: '#8b5cf6', icon: MdLocalShipping },
onhold: { label: 'On Hold', color: '#8b5cf6', icon: MdHistoryToggleOff },
closed: { label: 'Closed', color: '#06b6d4', icon: MdCheckCircle },
delivered: { label: 'Delivered', color: '#10b981', icon: MdCheckCircle },
failed: { label: 'Failed', color: '#991b1b', icon: MdCancel },
cancelled: { label: 'Cancelled', color: '#ef4444', icon: MdCancel }
};
// Top-level pill tabs.
const ORDERS_STATUS_TABS = [
{ idx: 0, status: 'created', label: 'Created', color: BRAND, icon: MdLocalShipping, countKey: 'created' },
{ idx: 1, status: 'pending', label: 'Pending', color: '#f59e0b', icon: MdHourglassEmpty, countKey: 'pending' },
{ idx: 2, status: 'delivered', label: 'Delivered', color: '#10b981', icon: MdCheckCircle, countKey: 'delivered' },
{ idx: 3, status: 'cancelled', label: 'Cancelled', color: '#ef4444', icon: MdCancel, countKey: 'cancelled' }
];
// Filled status badge — high-contrast pill (white text on solid color).
const StatusBadge = ({ status }) => {
const meta = ROW_STATUS_META[String(status || '').toLowerCase()] || {
label: status || '—',
color: DT.textMuted,
icon: MdHistoryToggleOff
};
const Icon = meta.icon;
return (
{meta.label}
);
};
const MetricCell = ({ value, color, icon, isMoney = false }) => {
const n = Number(value);
const display = isMoney ? `₹${Number.isFinite(n) ? n.toFixed(2) : '0.00'}` : Number.isFinite(n) ? n : value || 0;
const isZero = !Number.isFinite(n) || n === 0;
if (isZero) {
return (
{display}
);
}
return (
{icon}
{display}
);
};
const Orders = () => {
const tid = localStorage.getItem('tenantid');
const tenId = localStorage.getItem('tenantid');
const loadMoreRef = useRef();
const containerRef = useRef();
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [pageCount, setPageCount] = useState(0);
const [percentage1, setPercentage1] = useState('0');
const [percentage2, setPercentage2] = useState('0');
const [percentage3, setPercentage3] = useState('0');
const [percentage4, setPercentage4] = useState('0');
const [tenantLocations, setTenantlocations] = useState([]);
const [coveredorders, setCoveredorders] = useState('');
const [uncoveredorders, setUncoveredorders] = useState('');
const [cancelled, setCancelled] = useState('');
const [created, setCreated] = useState('');
const [loading, setLoading] = useState(false);
const theme = useTheme();
const [tabvalue, setTabvalue] = useState(0);
const [tabstatus, setTabstatus] = useState('Created');
const [currentStatus, setCurrentStatus] = useState('created');
const [createdLenght, setCreatedLenght] = useState();
const [pendingLenght, setPendingLenght] = useState();
const [deliveredlenght, setDeliveredlenght] = useState();
const [cancelledLenght, setCancelledLenght] = useState();
const [cancelOpen, setCancelOpen] = useState(false);
const [orderheaderid, setOrderheaderid] = useState('');
const [locationId, setLocationId] = useState(0);
const [locoName, setLocoName] = useState('All Locations');
const [dateOpen, setDateOpen] = useState(false);
const [datestatus, setDatestatus] = useState('Today');
const [startdate, setStartdate] = useState(dayjs().format('YYYY-MM-DD'));
const [enddate, setEnddate] = useState(dayjs().format('YYYY-MM-DD'));
const [searchword, setSearchword] = useState('');
const [debouncedSearch, setDebouncedSearch] = useState('');
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(searchword);
}, 400);
return () => clearTimeout(handler);
}, [searchword]);
const tabCounts = {
created: createdLenght,
pending: pendingLenght,
delivered: deliveredlenght,
cancelled: cancelledLenght
};
const handleChangetab = (e, i) => {
setSearchword('');
setRowsPerPage(10);
setTabvalue(i);
const tab = ORDERS_STATUS_TABS[i];
setTabstatus(tab.label);
setCurrentStatus(tab.status);
setPage(0);
};
const textFieldRef = useRef(null);
useHotkeyFocus(textFieldRef, 'k');
const cancelorder = async () => {
setLoading(true);
await axios
.put(`${process.env.REACT_APP_URL}/orders/updateorder`, {
orderheaderid: orderheaderid,
orderstatus: 'cancelled',
cancelled: dayjs().format('YYYY-MM-DD HH:mm:ss')
})
.then((res) => {
if (res.data.status) {
enqueueSnackbar('Order Cancelled Successfully', {
variant: 'success',
anchorOrigin: { vertical: 'top', horizontal: 'right' },
autoHideDuration: 2000
});
refetchOrders();
fetchorderscount();
setCancelOpen(false);
}
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
};
const fetchOrders = async ({ pageParam = 1 }) => {
const res = await axios.get(
`${process.env.REACT_APP_URL}/orders/tenant/getorders/?tenantid=${tid}&locationid=${locationId}&status=${currentStatus}&fromdate=${startdate}&todate=${enddate}&pageno=${pageParam}&pagesize=${rowsPerPage}&keyword=${debouncedSearch}`
);
return {
data: res.data.details,
nextPage: res.data.details.length === rowsPerPage ? pageParam + 1 : undefined
};
};
const {
data: rowdata,
fetchNextPage,
isLoading: isLoadingGetOrders,
hasNextPage,
isFetchingNextPage,
refetch: refetchOrders
} = useInfiniteQuery({
queryKey: [tabstatus, startdate, enddate, page, rowsPerPage, debouncedSearch, locationId],
queryFn: fetchOrders,
getNextPageParam: (lastPage) => lastPage.nextPage
});
const rows = rowdata ? rowdata.pages.flatMap((p) => p.data) : [];
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();
}
}
};
const fetchpercentage = async () => {
setLoading(true);
try {
await axios
.get(`${process.env.REACT_APP_URL}/orders/getordersummary/?tenantid=${tid}`)
.then((res) => {
setCoveredorders(res.data.details.delivered.toString());
setCancelled(res.data.details.cancelled.toString());
setUncoveredorders(res.data.details.pending.toString());
setCreated(res.data.details.created.toString());
setPercentage1((Math.round((res.data.details.created / res.data.details.total) * 100) || 0).toString());
setPercentage3((Math.round((res.data.details.delivered / res.data.details.total) * 100) || 0).toString());
setPercentage4((Math.round((res.data.details.cancelled / res.data.details.total) * 100) || 0).toString());
setPercentage2((Math.round((res.data.details.pending / res.data.details.total) * 100) || 0).toString());
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
} catch (err) {
console.log(err);
setLoading(false);
}
};
useEffect(() => {
fetchpercentage();
}, []);
const fetchorderscount = async () => {
setLoading(true);
try {
await axios
.get(
`${process.env.REACT_APP_URL}/orders/getordersummary/?tenantid=${tid}&locationid=${locationId}&fromdate=${startdate}&todate=${enddate}`
)
.then((res) => {
setCreatedLenght(res.data.details.created);
setPendingLenght(res.data.details.pending);
setDeliveredlenght(res.data.details.delivered);
setCancelledLenght(res.data.details.cancelled);
tabvalue === 0 && setPageCount(res.data.details.created);
tabvalue === 1 && setPageCount(res.data.details.pending);
tabvalue === 2 && setPageCount(res.data.details.delivered);
tabvalue === 3 && setPageCount(res.data.details.cancelled);
setLoading(false);
})
.catch((err) => {
console.log(err);
setLoading(false);
});
} catch (err) {
console.log(err);
setLoading(false);
}
};
useEffect(() => {
fetchorderscount();
}, [tabvalue, locationId, startdate, enddate]);
// ============================================= || gettenantlocations (branches) || =============================================
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);
}, []);
// KPI tile definitions.
const kpiCards = [
{ key: 'created', label: 'Created Orders', color: BRAND, icon: MdLocalShipping, value: created, percentage: percentage1 },
{ key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdHourglassEmpty, value: uncoveredorders, percentage: percentage2 },
{ key: 'delivered', label: 'Delivered Orders', color: '#10b981', icon: MdCheckCircle, value: coveredorders, percentage: percentage3 },
{ key: 'cancelled', label: 'Cancelled Orders', color: '#ef4444', icon: MdCancel, value: cancelled, percentage: percentage4 }
];
return (
{loading && (
<>
>
)}
{/* ============================================= || Header (compact) || ============================================= */}
Orders
Live · {locoName} · {datestatus}
{/* Location picker */}
{tenantLocations.length === 1 ? (
{tenantLocations[0].locationname}
) : (
(option ? `${option.locationname} (${option.suburb || ''})` : '')}
PaperComponent={SoftPaper}
onChange={(event, value, reason) => {
if (value) {
setLocationId(value.locationid);
setLocoName(value.locationname);
}
if (reason === 'clear') {
setLocationId(0);
setLocoName('All Locations');
}
}}
renderInput={(params) => (
)
}}
/>
)}
sx={{ width: { xs: '100%', sm: 280 } }}
/>
)}
{/* ============================================= || KPI Cards (compact) || ============================================= */}
{kpiCards.map((item) => {
const Icon = item.icon;
return (
{item.label}
{item.value === '' ? : item.value}
{item.percentage != null && item.value !== '' && (
{item.percentage}%
)}
);
})}
{/* ============================================= || Filter Bar (compact) || ============================================= */}
setDateOpen(true)}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: 0.75,
px: 1.25,
py: 0.75,
borderRadius: 999,
cursor: 'pointer',
bgcolor: tint('#f59e0b'),
border: `1.5px solid ${edge('#f59e0b')}`,
color: '#f59e0b',
fontWeight: 800,
fontSize: 12,
transition: 'all 0.18s',
'&:hover': { borderColor: '#f59e0b', boxShadow: `0 0 0 3px ${ring('#f59e0b')}` }
}}
>
{dayjs(startdate).format('DD/MM/YY')} – {dayjs(enddate).format('DD/MM/YY')}
{datestatus}
{tenantLocations.length > 1 && (
(option ? `${option.locationname} (${option.suburb || ''})` : '')}
PaperComponent={SoftPaper}
onChange={(event, value, reason) => {
if (value) {
setLocationId(value.locationid);
setLocoName(value.locationname);
}
if (reason === 'clear') {
setLocationId(0);
setLocoName('All Locations');
}
}}
renderInput={(params) => (
)
}}
/>
)}
sx={{ width: { xs: '100%', md: 320 } }}
/>
)}
{/* ============================================= || Status Tabs + Search (compact) || ============================================= */}
{ORDERS_STATUS_TABS.map((t) => {
const Icon = t.icon;
const active = tabvalue === t.idx;
const count = tabCounts[t.countKey] ?? 0;
return (
handleChangetab(e, t.idx)}
sx={{
display: 'inline-flex',
alignItems: 'center',
gap: { xs: 0.625, md: 0.875 },
pl: 0.5,
pr: { xs: 1, md: 1.25 },
py: 0.5,
flexShrink: 0,
cursor: 'pointer',
borderRadius: 999,
border: `1.5px solid ${active ? t.color : edge(t.color)}`,
bgcolor: active ? t.color : tint(t.color),
color: active ? '#fff' : t.color,
fontWeight: 700,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : 'none',
transition: 'all 0.18s',
'&:hover': {
borderColor: t.color,
boxShadow: active ? `0 6px 18px ${ring(t.color)}` : `0 0 0 3px ${ring(t.color)}`
}
}}
>
{t.label}
{count ?? 0}
);
})}
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('');
refetchOrders();
fetchorderscount();
}}
sx={{ p: 0.25, color: BRAND }}
>
)}
{/* ============================================= || Table (dense, sticky header) || ============================================= */}
#
Order Location
Pickup
Drop
Qty
COD
Kms
Charges
Notes
Status
{currentStatus === 'created' && Actions}
{(isLoadingGetOrders || loading) &&
rows.length === 0 &&
Array.from({ length: 10 }).map((_, idx) => (
{Array.from({ length: currentStatus === 'created' ? 11 : 10 }).map((__, ci) => (
))}
))}
{!isLoadingGetOrders && rows.length === 0 && (
No {currentStatus} orders
{searchword ? 'Try a different keyword or clear the search.' : 'Adjust the location, status, or date range above.'}
{searchword && (
)}
)}
{rows.map((row, index) => (
{page * rowsPerPage + index + 1}
{row.locationname}
{row.locationsuburb && ` - ${row.locationsuburb}`}
{row.orderid}
{(() => {
const dateObj = row.pickupslot && dayjs(row.pickupslot).isValid()
? dayjs(row.pickupslot)
: dayjs(row.deliverydate || row.orderdate);
return (
<>
{dateObj.format('hh:mm A')}
· {dateObj.format('DD MMM YY')}
>
);
})()}
{row.pickupcustomer}
{row.pickupcontactno}
{row.pickupsuburb || (row.pickupaddress ? `${row.pickupaddress.slice(0, 20)}…` : '—')}
{row.deliverycustomer}
{row.deliverycontactno}
{row.deliverysuburb ||
(row.deliveryaddress?.length > 20 ? `${row.deliveryaddress.slice(0, 20)}…` : row.deliveryaddress || '—')}
} />
} isMoney />
} />
} isMoney />
{row.ordernotes ? (
{row.ordernotes}
) : (
—
)}
{currentStatus === 'created' && (
{row.orderstatus === 'created' && (
{
e.stopPropagation();
setOrderheaderid(row.orderheaderid);
setCancelOpen(true);
}}
sx={{
bgcolor: tint('#ef4444'),
border: `1px solid ${edge('#ef4444')}`,
color: '#ef4444',
borderRadius: 999,
p: 0.75,
'&:hover': {
bgcolor: soft('#ef4444'),
borderColor: '#ef4444'
}
}}
>
)}
)}
))}
{rows.length !== 0 && (
{isFetchingNextPage || hasNextPage ? (
<>
Loading more orders…
>
) : (
{rows.length} order{rows.length === 1 ? '' : 's'} · End of list
)}
)}
{/* ============================================= || Cancel Order Dialog || ============================================= */}
{/* ============================================= || Date Filter || ============================================= */}
setDateOpen(false)}
onApply={({ startDate, endDate, label }) => {
setStartdate(startDate);
setEnddate(endDate);
setDatestatus(label);
}}
/>
);
};
export default Orders;