1318 lines
48 KiB
JavaScript
1318 lines
48 KiB
JavaScript
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) => (
|
||
<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 }
|
||
}
|
||
});
|
||
|
||
// 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 (
|
||
<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 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 (
|
||
<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>
|
||
);
|
||
};
|
||
|
||
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 (
|
||
<Fragment>
|
||
{loading && (
|
||
<>
|
||
<Loader />
|
||
<CircularLoader />
|
||
</>
|
||
)}
|
||
|
||
{/* ============================================= || Header (compact) || ============================================= */}
|
||
<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)}`
|
||
}}
|
||
>
|
||
<MdLocalShipping 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
|
||
</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>
|
||
|
||
{/* Location picker */}
|
||
{tenantLocations.length === 1 ? (
|
||
<Box
|
||
sx={{
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
gap: 0.75,
|
||
px: 1.5,
|
||
py: 0.75,
|
||
borderRadius: 999,
|
||
bgcolor: tint(BRAND),
|
||
border: `1.5px solid ${edge(BRAND)}`,
|
||
color: BRAND,
|
||
fontWeight: 800,
|
||
fontSize: 13
|
||
}}
|
||
>
|
||
<MdMyLocation size={14} /> {tenantLocations[0].locationname}
|
||
</Box>
|
||
) : (
|
||
<Autocomplete
|
||
options={tenantLocations || []}
|
||
getOptionLabel={(option) => (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) => (
|
||
<TextField
|
||
{...params}
|
||
placeholder="Select Location"
|
||
size="small"
|
||
sx={{ ...pillFieldSx(BRAND), width: { xs: '100%', sm: 260 } }}
|
||
InputProps={{
|
||
...params.InputProps,
|
||
startAdornment: (
|
||
<Stack direction="row" alignItems="center" spacing={0.75} sx={{ pl: 0.5 }}>
|
||
<AccentAvatar color={BRAND} size={22} selected>
|
||
<MdMyLocation size={13} />
|
||
</AccentAvatar>
|
||
</Stack>
|
||
)
|
||
}}
|
||
/>
|
||
)}
|
||
sx={{ width: { xs: '100%', sm: 280 } }}
|
||
/>
|
||
)}
|
||
</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={6} md={3}>
|
||
<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>
|
||
<Stack direction="row" alignItems="baseline" spacing={0.75}>
|
||
<Typography
|
||
sx={{
|
||
fontWeight: 800,
|
||
color: DT.textPrimary,
|
||
lineHeight: 1.15,
|
||
fontSize: { xs: '0.95rem', sm: '1.1rem', md: '1.2rem' }
|
||
}}
|
||
noWrap
|
||
>
|
||
{item.value === '' ? <Skeleton sx={{ width: 40 }} animation="wave" /> : item.value}
|
||
</Typography>
|
||
{item.percentage != null && item.value !== '' && (
|
||
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 700 }}>
|
||
{item.percentage}%
|
||
</Typography>
|
||
)}
|
||
</Stack>
|
||
</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>
|
||
</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
|
||
}}
|
||
>
|
||
<Stack
|
||
direction={{ xs: 'column', md: 'row' }}
|
||
spacing={1.25}
|
||
alignItems={{ xs: 'stretch', md: 'center' }}
|
||
justifyContent={{ xs: 'flex-start', md: 'space-between' }}
|
||
>
|
||
<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap" useFlexGap>
|
||
<Tooltip title="Date Filter" placement="top">
|
||
<Box
|
||
onClick={() => 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')}` }
|
||
}}
|
||
>
|
||
<MdCalendarMonth size={14} />
|
||
{dayjs(startdate).format('DD/MM/YY')} – {dayjs(enddate).format('DD/MM/YY')}
|
||
</Box>
|
||
</Tooltip>
|
||
<Box
|
||
sx={{
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
gap: 0.5,
|
||
px: 1,
|
||
py: 0.5,
|
||
borderRadius: 999,
|
||
bgcolor: tint(BRAND),
|
||
border: `1px solid ${edge(BRAND)}`,
|
||
color: BRAND,
|
||
fontSize: 11,
|
||
fontWeight: 800
|
||
}}
|
||
>
|
||
<MdReceiptLong size={12} /> {datestatus}
|
||
</Box>
|
||
</Stack>
|
||
|
||
{tenantLocations.length > 1 && (
|
||
<Autocomplete
|
||
options={tenantLocations || []}
|
||
getOptionLabel={(option) => (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) => (
|
||
<TextField
|
||
{...params}
|
||
placeholder="Select Location"
|
||
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>
|
||
)
|
||
}}
|
||
/>
|
||
)}
|
||
sx={{ width: { xs: '100%', md: 320 } }}
|
||
/>
|
||
)}
|
||
</Stack>
|
||
</Paper>
|
||
|
||
{/* ============================================= || Status Tabs + Search (compact) || ============================================= */}
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
mt: { xs: 1, md: 1.25 },
|
||
p: { xs: 0.875, md: 1.125 },
|
||
borderTopLeftRadius: 2,
|
||
borderTopRightRadius: 2,
|
||
borderBottomLeftRadius: 0,
|
||
borderBottomRightRadius: 0,
|
||
border: '1px solid',
|
||
borderColor: DT.borderSubtle,
|
||
borderBottom: 0,
|
||
background: '#fff'
|
||
}}
|
||
>
|
||
<Stack
|
||
direction="row"
|
||
alignItems="center"
|
||
justifyContent="space-between"
|
||
gap={1.5}
|
||
sx={{ flexWrap: 'wrap-reverse' }}
|
||
>
|
||
<Stack
|
||
direction="row"
|
||
spacing={0.75}
|
||
sx={{
|
||
flex: 1,
|
||
overflowX: 'auto',
|
||
py: 0.5,
|
||
px: 0.25,
|
||
'&::-webkit-scrollbar': { height: 6 },
|
||
'&::-webkit-scrollbar-thumb': { backgroundColor: DT.borderSubtle, borderRadius: 4 }
|
||
}}
|
||
>
|
||
{ORDERS_STATUS_TABS.map((t) => {
|
||
const Icon = t.icon;
|
||
const active = tabvalue === t.idx;
|
||
const count = tabCounts[t.countKey] ?? 0;
|
||
return (
|
||
<Box
|
||
key={t.status}
|
||
onClick={(e) => 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)}`
|
||
}
|
||
}}
|
||
>
|
||
<Avatar
|
||
sx={{
|
||
width: { xs: 22, md: 26 },
|
||
height: { xs: 22, md: 26 },
|
||
bgcolor: active ? 'rgba(255,255,255,0.22)' : soft(t.color),
|
||
color: active ? '#fff' : t.color
|
||
}}
|
||
>
|
||
<Icon size={13} />
|
||
</Avatar>
|
||
<Typography variant="caption" sx={{ fontWeight: 800, fontSize: { xs: 11.5, md: 13 }, lineHeight: 1 }}>
|
||
{t.label}
|
||
</Typography>
|
||
<Box
|
||
sx={{
|
||
minWidth: { xs: 22, md: 26 },
|
||
height: { xs: 18, md: 22 },
|
||
px: 0.625,
|
||
display: 'inline-flex',
|
||
alignItems: 'center',
|
||
justifyContent: 'center',
|
||
borderRadius: 999,
|
||
fontSize: { xs: 10, md: 11 },
|
||
fontWeight: 800,
|
||
bgcolor: active ? 'rgba(255,255,255,0.22)' : '#fff',
|
||
color: active ? '#fff' : t.color,
|
||
border: active ? 'none' : `1px solid ${edge(t.color)}`
|
||
}}
|
||
>
|
||
{count ?? 0}
|
||
</Box>
|
||
</Box>
|
||
);
|
||
})}
|
||
</Stack>
|
||
|
||
<Box sx={{ width: { xs: '100%', sm: 240, lg: 280 }, flex: { xs: '1 1 100%', sm: '0 0 auto' } }}>
|
||
<Box
|
||
sx={{
|
||
display: 'flex',
|
||
alignItems: 'center',
|
||
gap: 0.75,
|
||
px: 1.25,
|
||
py: 0.5,
|
||
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('');
|
||
refetchOrders();
|
||
fetchorderscount();
|
||
}}
|
||
sx={{ p: 0.25, color: BRAND }}
|
||
>
|
||
<MdClear size={14} />
|
||
</IconButton>
|
||
)}
|
||
</Box>
|
||
</Box>
|
||
</Stack>
|
||
</Paper>
|
||
|
||
{/* ============================================= || Table (dense, sticky header) || ============================================= */}
|
||
<Paper
|
||
elevation={0}
|
||
sx={{
|
||
borderTopLeftRadius: 0,
|
||
borderTopRightRadius: 0,
|
||
borderBottomLeftRadius: 2,
|
||
borderBottomRightRadius: 2,
|
||
border: '1px solid',
|
||
borderColor: DT.borderSubtle,
|
||
overflow: 'hidden',
|
||
background: '#fff'
|
||
}}
|
||
>
|
||
<TableContainer
|
||
onScroll={handleScroll}
|
||
ref={containerRef}
|
||
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: { xs: 960, md: 1100 } }}>
|
||
<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
|
||
}
|
||
}}
|
||
>
|
||
<TableCell>#</TableCell>
|
||
<TableCell>Order Location</TableCell>
|
||
<TableCell>Pickup</TableCell>
|
||
<TableCell>Drop</TableCell>
|
||
<TableCell align="center">Qty</TableCell>
|
||
<TableCell align="right">COD</TableCell>
|
||
<TableCell align="center">Kms</TableCell>
|
||
<TableCell align="right">Charges</TableCell>
|
||
<TableCell>Notes</TableCell>
|
||
<TableCell>Status</TableCell>
|
||
{currentStatus === 'created' && <TableCell align="right">Actions</TableCell>}
|
||
</TableRow>
|
||
</TableHead>
|
||
|
||
<TableBody>
|
||
{(isLoadingGetOrders || loading) &&
|
||
rows.length === 0 &&
|
||
Array.from({ length: 10 }).map((_, idx) => (
|
||
<TableRow key={`sk-${idx}`}>
|
||
{Array.from({ length: currentStatus === 'created' ? 11 : 10 }).map((__, ci) => (
|
||
<TableCell key={ci} sx={{ borderBottom: `1px solid ${DT.divider}`, py: 0.625, px: 1 }}>
|
||
<Skeleton animation="wave" height={20} />
|
||
</TableCell>
|
||
))}
|
||
</TableRow>
|
||
))}
|
||
|
||
{!isLoadingGetOrders && rows.length === 0 && (
|
||
<TableRow>
|
||
<TableCell colSpan={currentStatus === 'created' ? 11 : 10} sx={{ py: 7, borderBottom: 'none' }}>
|
||
<Stack alignItems="center" spacing={1.25}>
|
||
<Avatar
|
||
variant="rounded"
|
||
sx={{
|
||
width: 56,
|
||
height: 56,
|
||
bgcolor: soft('#94a3b8'),
|
||
color: DT.textMuted,
|
||
borderRadius: 2
|
||
}}
|
||
>
|
||
<MdLocalShipping size={26} />
|
||
</Avatar>
|
||
<Typography sx={{ fontWeight: 700, color: DT.textPrimary, fontSize: 14 }}>
|
||
No {currentStatus} orders
|
||
</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>
|
||
)}
|
||
|
||
{rows.map((row, index) => (
|
||
<TableRow
|
||
key={`${row.orderheaderid}-${index}`}
|
||
sx={{
|
||
cursor: 'pointer',
|
||
transition: 'background-color 0.12s, box-shadow 0.12s',
|
||
'& td': {
|
||
borderBottom: `1px solid ${DT.divider}`,
|
||
py: 0.5,
|
||
px: 1,
|
||
verticalAlign: 'top'
|
||
},
|
||
'&:hover': {
|
||
backgroundColor: tint(BRAND),
|
||
boxShadow: `inset 3px 0 0 ${BRAND}`
|
||
}
|
||
}}
|
||
>
|
||
<TableCell>
|
||
<Typography sx={{ fontWeight: 700, color: DT.textSecondary }}>
|
||
{page * rowsPerPage + index + 1}
|
||
</Typography>
|
||
</TableCell>
|
||
|
||
<TableCell>
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
|
||
{row.locationname}
|
||
{row.locationsuburb && ` - ${row.locationsuburb}`}
|
||
</Typography>
|
||
<Tooltip title="Order Id">
|
||
<Typography variant="caption" sx={{ color: DT.textSecondary }} noWrap>
|
||
{row.orderid}
|
||
</Typography>
|
||
</Tooltip>
|
||
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ mt: 0.125 }}>
|
||
<MdAccessTime size={10} style={{ color: DT.textMuted, flexShrink: 0 }} />
|
||
{(() => {
|
||
const dateObj = row.pickupslot && dayjs(row.pickupslot).isValid()
|
||
? dayjs(row.pickupslot)
|
||
: dayjs(row.deliverydate || row.orderdate);
|
||
return (
|
||
<>
|
||
<Typography sx={{ fontSize: 10.5, color: DT.textSecondary, fontWeight: 700 }} noWrap>
|
||
{dateObj.format('hh:mm A')}
|
||
</Typography>
|
||
<Typography sx={{ fontSize: 10.5, color: DT.textMuted, fontWeight: 600 }} noWrap>
|
||
· {dateObj.format('DD MMM YY')}
|
||
</Typography>
|
||
</>
|
||
);
|
||
})()}
|
||
</Stack>
|
||
</TableCell>
|
||
|
||
<TableCell>
|
||
<Stack direction="column">
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
|
||
{row.pickupcustomer}
|
||
</Typography>
|
||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||
{row.pickupcontactno}
|
||
</Typography>
|
||
<Tooltip title={row.pickupaddress}>
|
||
<Typography variant="caption" sx={{ color: DT.textMuted }} noWrap>
|
||
{row.pickupsuburb || (row.pickupaddress ? `${row.pickupaddress.slice(0, 20)}…` : '—')}
|
||
</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
</TableCell>
|
||
|
||
<TableCell>
|
||
<Stack direction="column">
|
||
<Typography variant="subtitle2" sx={{ fontWeight: 700, color: DT.textPrimary }} noWrap>
|
||
{row.deliverycustomer}
|
||
</Typography>
|
||
<Typography variant="caption" sx={{ color: DT.textSecondary }}>
|
||
{row.deliverycontactno}
|
||
</Typography>
|
||
<Tooltip title={row.deliveryaddress}>
|
||
<Typography variant="caption" sx={{ color: DT.textMuted }} noWrap>
|
||
{row.deliverysuburb ||
|
||
(row.deliveryaddress?.length > 20 ? `${row.deliveryaddress.slice(0, 20)}…` : row.deliveryaddress || '—')}
|
||
</Typography>
|
||
</Tooltip>
|
||
</Stack>
|
||
</TableCell>
|
||
|
||
<TableCell align="center">
|
||
<MetricCell value={row.quantity} color="#0ea5e9" icon={<MdInventory2 size={11} />} />
|
||
</TableCell>
|
||
|
||
<TableCell align="right">
|
||
<MetricCell value={row.collectionamt} color="#10b981" icon={<MdCurrencyRupee size={11} />} isMoney />
|
||
</TableCell>
|
||
|
||
<TableCell align="center">
|
||
<MetricCell value={row.kms} color="#f59e0b" icon={<MdStraighten size={11} />} />
|
||
</TableCell>
|
||
|
||
<TableCell align="right">
|
||
<MetricCell value={row.deliverycharge} color={BRAND} icon={<MdCurrencyRupee size={11} />} isMoney />
|
||
</TableCell>
|
||
|
||
<TableCell>
|
||
{row.ordernotes ? (
|
||
<Stack direction="row" spacing={0.5} alignItems="center" sx={{ color: DT.textSecondary }}>
|
||
<MdNote size={12} style={{ color: DT.textMuted, flexShrink: 0 }} />
|
||
<Typography
|
||
variant="caption"
|
||
sx={{
|
||
maxWidth: 140,
|
||
whiteSpace: 'nowrap',
|
||
overflow: 'hidden',
|
||
textOverflow: 'ellipsis',
|
||
fontWeight: 600
|
||
}}
|
||
>
|
||
{row.ordernotes}
|
||
</Typography>
|
||
</Stack>
|
||
) : (
|
||
<Typography variant="caption" sx={{ color: DT.textMuted }}>
|
||
—
|
||
</Typography>
|
||
)}
|
||
</TableCell>
|
||
|
||
<TableCell>
|
||
<StatusBadge status={row.orderstatus} />
|
||
</TableCell>
|
||
|
||
{currentStatus === 'created' && (
|
||
<TableCell align="right">
|
||
{row.orderstatus === 'created' && (
|
||
<Tooltip title="Cancel Order">
|
||
<IconButton
|
||
size="small"
|
||
onClick={(e) => {
|
||
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'
|
||
}
|
||
}}
|
||
>
|
||
<MdClose size={14} />
|
||
</IconButton>
|
||
</Tooltip>
|
||
)}
|
||
</TableCell>
|
||
)}
|
||
</TableRow>
|
||
))}
|
||
|
||
{rows.length !== 0 && (
|
||
<TableRow sx={{ '&:hover': { backgroundColor: 'transparent !important' } }}>
|
||
<TableCell colSpan={currentStatus === 'created' ? 11 : 10} 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>
|
||
|
||
{/* ============================================= || Cancel Order Dialog || ============================================= */}
|
||
<Dialog open={cancelOpen} onClose={() => setCancelOpen(false)} maxWidth="xs" PaperProps={{ sx: { borderRadius: 3 } }}>
|
||
<Box
|
||
sx={{
|
||
p: 2.5,
|
||
background: `linear-gradient(135deg, ${tint('#ef4444')} 0%, ${tint('#f59e0b')} 100%)`,
|
||
borderBottom: `1px solid ${DT.borderSubtle}`
|
||
}}
|
||
>
|
||
<Stack direction="row" alignItems="center" spacing={1.5}>
|
||
<Avatar sx={{ bgcolor: '#ef4444', color: '#fff', width: 40, height: 40 }}>
|
||
<MdDeleteOutline size={20} />
|
||
</Avatar>
|
||
<Typography variant="h5" sx={{ fontWeight: 800, color: DT.textPrimary }}>
|
||
Cancel Order
|
||
</Typography>
|
||
</Stack>
|
||
</Box>
|
||
<DialogContent sx={{ pt: 3 }}>
|
||
<Stack alignItems="center" spacing={3}>
|
||
<Typography variant="body1" align="center" sx={{ color: DT.textSecondary, fontWeight: 600 }}>
|
||
Are you sure you want to cancel this order? This action cannot be undone.
|
||
</Typography>
|
||
<Stack direction="row" spacing={1.5} sx={{ width: 1 }}>
|
||
<Button
|
||
fullWidth
|
||
onClick={() => setCancelOpen(false)}
|
||
variant="outlined"
|
||
sx={{
|
||
borderRadius: 999,
|
||
py: 1,
|
||
borderColor: DT.borderSubtle,
|
||
color: DT.textSecondary,
|
||
fontWeight: 700,
|
||
'&:hover': { borderColor: DT.textSecondary, bgcolor: DT.surfaceAlt }
|
||
}}
|
||
>
|
||
No
|
||
</Button>
|
||
<Button
|
||
fullWidth
|
||
variant="contained"
|
||
onClick={cancelorder}
|
||
autoFocus
|
||
sx={{
|
||
borderRadius: 999,
|
||
py: 1,
|
||
bgcolor: '#ef4444',
|
||
fontWeight: 700,
|
||
boxShadow: `0 6px 18px ${ring('#ef4444')}`,
|
||
'&:hover': { bgcolor: '#dc2626' }
|
||
}}
|
||
>
|
||
Yes, Cancel
|
||
</Button>
|
||
</Stack>
|
||
</Stack>
|
||
</DialogContent>
|
||
</Dialog>
|
||
|
||
{/* ============================================= || Date Filter || ============================================= */}
|
||
<DateFilterDialog
|
||
open={dateOpen}
|
||
onClose={() => setDateOpen(false)}
|
||
onApply={({ startDate, endDate, label }) => {
|
||
setStartdate(startDate);
|
||
setEnddate(endDate);
|
||
setDatestatus(label);
|
||
}}
|
||
/>
|
||
</Fragment>
|
||
);
|
||
};
|
||
|
||
export default Orders;
|