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 {
Avatar,
Box,
Button,
Grid,
IconButton,
Paper,
Stack,
Typography,
Table,
TableCell,
TableBody,
TableHead,
Dialog,
TableRow,
DialogContent,
Tooltip,
Skeleton,
CircularProgress,
InputBase,
Backdrop,
SpeedDial,
SpeedDialIcon,
SpeedDialAction,
Badge,
TableContainer,
Checkbox
} from '@mui/material';
import {
MdAccessTime,
MdCancel,
MdCheckCircle,
MdClose,
MdCurrencyRupee,
MdDeleteOutline,
MdHistoryToggleOff,
MdHourglassEmpty,
MdInventory2,
MdLocalShipping,
MdSearch,
MdStraighten,
MdCalendarMonth,
MdReceiptLong,
MdClear,
MdNotes
} from 'react-icons/md';
import { DeleteOutlined } from '@ant-design/icons';
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 AiImage from '../../../assets/images/aiImage.png';
import { useQuery, useMutation, useInfiniteQuery } from '@tanstack/react-query';
import { useNavigate, useLocation } from 'react-router-dom';
import {
fetchPercentageData,
createAutomationDeliveries,
cancelMultipleOrder,
getallriders,
fetchorderscount
} from '../api/api';
// ============================================================================
// 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 dtTint = tint;
const dtSoft = soft;
const dtRing = ring;
const dtEdge = edge;
const BRAND = '#662582';
const BRAND_LIGHT = '#9255AB';
// 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' }
];
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 navigate = useNavigate();
const location = useLocation();
const tid = localStorage.getItem('tenantid');
const loadMoreRef = useRef();
const containerRef = useRef();
const [page, setPage] = useState(0);
const [rowsPerPage, setRowsPerPage] = useState(10);
const [tabvalue, setTabvalue] = useState(0);
const [tabstatus, setTabstatus] = useState('Created');
const [currentStatus, setCurrentStatus] = useState('created');
const [cancelOpen, setCancelOpen] = useState(false);
const [orderheaderid, setOrderheaderid] = useState('');
const locationId = 0;
const locoName = '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('');
// Floating button and Dialog states
const [speedDialOpen, setSpeedDialOpen] = useState(false);
const [multiDeleteDialog, setMultiDeleteDialog] = useState(false);
const [createloader, setCreateloader] = useState(false);
const [isLoading, setIsLoading] = useState(false);
const [deliverylist, setDeliverylist] = useState([]);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedSearch(searchword);
}, 400);
return () => clearTimeout(handler);
}, [searchword]);
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');
// React Queries
const {
data: percentageData,
isLoading: fetchpercentageIsLoading,
refetch: percentagedataRefetch
} = useQuery({
queryKey: ['percentageData', locationId, startdate, enddate, tid, locationId],
queryFn: fetchPercentageData,
enabled: true,
refetchInterval: 15000
});
const {
data: ordersCountData,
refetch: orderscountRefetch
} = useQuery({
queryKey: ['ordersCount', locationId, startdate, enddate, currentStatus, tid, locationId],
queryFn: fetchorderscount,
refetchOnMount: true,
refetchOnWindowFocus: true,
refetchInterval: 15000
});
const {
data: autoRiders
} = useQuery({
queryKey: ['getallriders'],
queryFn: getallriders,
refetchOnMount: true,
refetchOnWindowFocus: true
});
const cancelorder = async () => {
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();
orderscountRefetch();
percentagedataRefetch();
setCancelOpen(false);
}
})
.catch((err) => {
console.log(err);
});
};
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,
refetchInterval: 15000
});
const rows = rowdata ? rowdata.pages.flatMap((p) => p.data) : [];
useEffect(() => {
if (!hasNextPage) return;
const observer = new IntersectionObserver(
(entries) => {
if (entries[0].isIntersecting) {
fetchNextPage();
}
},
{
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();
}
}
};
// ==============================|| Mutations ||============================== //
const createDeliveryMutation = useMutation({
mutationFn: createAutomationDeliveries,
onSuccess: (data, variables) => {
enqueueSnackbar('Orders Optimised Successfully', { variant: 'success', autoHideDuration: 2000, anchorOrigin: { vertical: 'top', horizontal: 'right' } });
orderscountRefetch();
refetchOrders();
setCreateloader(false);
navigate('/nearle/dispatch/preview', {
state: {
dispatchPreviewData: data,
aiMode: 1,
selectedMode: { value: 1 },
deliveryData: variables?.deliveries || [],
appId: locationId,
startdate: startdate,
tenantId: tid,
autoRiders: autoRiders || []
}
});
},
onError: (error) => {
enqueueSnackbar(error.message, { variant: 'error', autoHideDuration: 4000 });
setCreateloader(false);
},
onSettled: () => {
setCreateloader(false);
setIsLoading(false);
}
});
const cancelMultipleOrderMutation = useMutation({
mutationFn: cancelMultipleOrder,
onSuccess: (data) => {
if (data.status) {
setMultiDeleteDialog(false);
enqueueSnackbar('Orders Cancelled Successfully', { variant: 'success', autoHideDuration: 2000 });
refetchOrders();
orderscountRefetch();
percentagedataRefetch();
setDeliverylist([]);
}
},
onError: (error) => {
enqueueSnackbar(error.message, { variant: 'error', autoHideDuration: 4000 });
}
});
const handleCreateDelivery = async () => {
if (rows.length === 0) return;
setIsLoading(true);
setCreateloader(true);
const deliveryData = rows.map((val) => ({
...val,
deliveryid: 0,
deliverydate: dayjs(val.deliverydate).utc().format('YYYY-MM-DD HH:mm:ss'),
assigntime: dayjs().format('YYYY-MM-DD HH:mm:ss'),
orderstatus: 'pending',
orderamount: val.deliverycharge,
droplat: val.deliverylat,
droplon: val.deliverylong,
pickuplat: val.pickuplat,
pickuplon: val.pickuplong,
ordernotes: val.ordernotes,
deliverycharges: val.deliverycharge,
pickuplocation: val.pickupsuburb,
deliverylocation: val.deliverysuburb
}));
createDeliveryMutation.mutate({
deliveries: deliveryData,
selectedMode: { value: 1 },
hypertuning_params: 'balanced',
absent_riders: []
});
};
// KPI tile definitions.
const kpiCards = [
{ key: 'created', label: 'Created Orders', color: BRAND, icon: MdLocalShipping, value: percentageData?.created, percentage: percentageData?.percentage1 },
{ key: 'pending', label: 'Pending Orders', color: '#f59e0b', icon: MdHourglassEmpty, value: percentageData?.uncoveredOrders, percentage: percentageData?.percentage2 }
];
return (
{(fetchpercentageIsLoading || isLoadingGetOrders || isLoading || createloader) && (
<>
>
)}
{rows.length > 0 && currentStatus === 'created' && (
}
>
Assign Orders ({rows.length})
)}
{/* ============================================= || Header (compact) || ============================================= */}
Orders
Live · {locoName} · {datestatus}
{/* ============================================= || Status Tabs + Search (compact) || ============================================= */}
{ORDERS_STATUS_TABS.map((t) => {
const Icon = t.icon;
const active = tabvalue === t.idx;
const count = ordersCountData?.[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}
);
})}
{/* ============================================= || Table (dense, sticky header) || ============================================= */}
#
Order Location
Pickup
Drop
Qty
COD
Kms
Charges
Notes
Status
{currentStatus === 'created' && Actions}
{(isLoadingGetOrders || createloader) &&
rows.length === 0 &&
Array.from({ length: 10 }).map((_, idx) => (
{Array.from({ length: currentStatus === 'created' ? 12 : 11 }).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) => {
const isItemSelected = !!deliverylist.find((res) => res.orderheaderid === row.orderheaderid);
const handleCheckbox = (e) => {
if (e.target.checked) {
setDeliverylist((prev) => [...prev, { ...row, sno: prev.length + 1 }]);
} else {
setDeliverylist((prev) =>
prev.filter((item) => item.orderheaderid !== row.orderheaderid).map((item, i) => ({ ...item, sno: i + 1 }))
);
}
};
return (
{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);
}}
/>
{/* ============================================= || Cancel Multiple Orders Dialog || ============================================= */}
);
};
export default Orders;