updates on the orders and assign page

This commit is contained in:
2026-06-25 20:05:09 +05:30
parent 30c0e184ce
commit 1ba28231a0
15 changed files with 3495 additions and 2071 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 759 KiB

View File

@@ -0,0 +1,34 @@
// LoaderWithImage.jsx
import React from 'react';
import { Box, CircularProgress } from '@mui/material';
import nelogo from '../../assets/images/logo-sm.png';
export default function LoaderWithImage({ size = 70, imgSize = 40, alt = 'loader' }) {
return (
<Box position="relative" display="inline-flex" justifyContent="center" alignItems="center">
<CircularProgress size={size} />
<Box
position="absolute"
display="flex"
justifyContent="center"
alignItems="center"
sx={{
width: imgSize,
height: imgSize
}}
>
<img
src={nelogo}
alt={alt}
style={{
width: '100%',
height: '100%',
borderRadius: '50%',
objectFit: 'contain'
}}
/>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,140 @@
import PropTypes from 'prop-types';
import { Box, Paper, Stack, Typography } from '@mui/material';
// ============================================================================
// MobileCard — shared primitives that turn a desktop data-table row into an
// app-style card on phones. Used by every operator list page (deliveries,
// orders, customers, riders, tenants, …) so the mobile experience is
// consistent. Purely presentational: pages keep their own data + handlers and
// just slot content into these shells. Desktop layouts are untouched — these
// only render inside an `isMobile` branch.
//
// Tokens mirror the `DT` block in deliveries.js so cards match page surfaces.
// ============================================================================
const BORDER = '#e2e8f0';
const MUTED = '#94a3b8';
const PRIMARY_TEXT = '#0f172a';
// Vertical list wrapper — drop-in replacement for <TableContainer>/<TableBody>
// on mobile. `scroll` makes it an internal scroll region (matches the table's
// maxHeight behaviour); omit it to let the page scroll naturally.
export const MobileCardList = ({ children, scroll = false, onScroll, sx, ...rest }) => (
<Stack
spacing={1.25}
onScroll={onScroll}
sx={{
p: 1.5,
...(scroll && { maxHeight: 'calc(100vh - 220px)', overflowY: 'auto', overflowX: 'hidden' }),
...sx
}}
{...rest}
>
{children}
</Stack>
);
MobileCardList.propTypes = {
children: PropTypes.node,
scroll: PropTypes.bool,
onScroll: PropTypes.func,
sx: PropTypes.object
};
// Card shell — coloured accent rail on the left, a header slot (status badge /
// title / action buttons), then any field grid / collapse content as children.
export const MobileCard = ({ accent = '#662582', header, footer, selected = false, onClick, children, sx }) => (
<Paper
elevation={0}
onClick={onClick}
sx={{
position: 'relative',
overflow: 'hidden',
// Cards live inside a flex-column list; without this, a scroll-capped
// list (maxHeight) would SHRINK each card to a sliver (flex-shrink:1)
// and clip its content instead of scrolling. Keep natural height.
flexShrink: 0,
borderRadius: 2.5,
border: '1px solid',
borderColor: selected ? accent : BORDER,
background: selected ? `${accent}0a` : '#fff',
boxShadow: '0 4px 14px rgba(15,23,42,0.05)',
transition: 'border-color 0.15s, box-shadow 0.15s',
...sx
}}
>
<Box sx={{ position: 'absolute', left: 0, top: 0, bottom: 0, width: 3, bgcolor: accent }} />
<Box sx={{ p: 1.5, pl: 2 }}>
{header}
{children}
{footer}
</Box>
</Paper>
);
MobileCard.propTypes = {
accent: PropTypes.string,
header: PropTypes.node,
footer: PropTypes.node,
selected: PropTypes.bool,
onClick: PropTypes.func,
children: PropTypes.node,
sx: PropTypes.object
};
// Grid wrapper for MobileField cells. Two columns by default; pass `columns`
// to change. Keeps every card's body alignment identical.
export const MobileFieldGrid = ({ children, columns = 2, sx }) => (
<Box
sx={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, minmax(0, 1fr))`,
gap: 1,
mt: 1.25,
...sx
}}
>
{children}
</Box>
);
MobileFieldGrid.propTypes = {
children: PropTypes.node,
columns: PropTypes.number,
sx: PropTypes.object
};
// A single label/value cell. `full` makes it span the whole row; `value` can be
// a string/number or any node (chip, stack, etc.).
export const MobileField = ({ label, value, children, full = false, align = 'left' }) => (
<Box sx={{ gridColumn: full ? '1 / -1' : 'auto', minWidth: 0, textAlign: align }}>
<Typography
sx={{
fontSize: 9.5,
fontWeight: 800,
letterSpacing: 0.5,
textTransform: 'uppercase',
color: MUTED,
lineHeight: 1.4
}}
>
{label}
</Typography>
<Box sx={{ mt: 0.25, minWidth: 0 }}>
{children !== undefined ? (
children
) : (
<Typography sx={{ fontSize: 13, fontWeight: 600, color: PRIMARY_TEXT }} noWrap>
{value ?? '—'}
</Typography>
)}
</Box>
</Box>
);
MobileField.propTypes = {
label: PropTypes.node,
value: PropTypes.node,
children: PropTypes.node,
full: PropTypes.bool,
align: PropTypes.string
};

View File

@@ -6,6 +6,7 @@ import { TbListDetails } from 'react-icons/tb';
import { LiaFileInvoiceSolid } from 'react-icons/lia'; import { LiaFileInvoiceSolid } from 'react-icons/lia';
import DirectionsBikeOutlinedIcon from '@mui/icons-material/DirectionsBikeOutlined'; import DirectionsBikeOutlinedIcon from '@mui/icons-material/DirectionsBikeOutlined';
import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined'; import RouteOutlinedIcon from '@mui/icons-material/RouteOutlined';
import MopedOutlinedIcon from '@mui/icons-material/MopedOutlined';
// assets // assets
import { import {
@@ -64,6 +65,13 @@ const nearle = {
url: '/nearle/orders', url: '/nearle/orders',
icon: AiOutlineDashboard icon: AiOutlineDashboard
}, },
{
id: 'deliveries',
title: <FormattedMessage id="Deliveries" />,
type: 'item',
url: '/nearle/deliveries',
icon: MopedOutlinedIcon
},
{ {
id: 'locations', id: 'locations',
title: <FormattedMessage id="Locations" />, title: <FormattedMessage id="Locations" />,

View File

@@ -4,6 +4,7 @@ import { AiOutlineBarChart } from 'react-icons/ai';
import { AiOutlineDashboard } from 'react-icons/ai'; import { AiOutlineDashboard } from 'react-icons/ai';
import { TbListDetails } from 'react-icons/tb'; import { TbListDetails } from 'react-icons/tb';
import { LiaFileInvoiceSolid } from 'react-icons/lia'; import { LiaFileInvoiceSolid } from 'react-icons/lia';
import MopedOutlinedIcon from '@mui/icons-material/MopedOutlined';
// assets // assets
import { import {
@@ -60,6 +61,13 @@ const other = {
url: 'nearle/orders', url: 'nearle/orders',
icon: AiOutlineDashboard icon: AiOutlineDashboard
}, },
{
id: 'deliveries',
title: <FormattedMessage id="Deliveries" />,
type: 'item',
url: 'nearle/deliveries',
icon: MopedOutlinedIcon
},
{ {
id: 'customers', id: 'customers',
title: <FormattedMessage id="Customers" />, title: <FormattedMessage id="Customers" />,

View File

@@ -24,6 +24,34 @@ export const fetchOrders = async ({ pageParam = 1, queryKey }) => {
}; };
}; };
// ==============================|| fetchPercentageData (orders) ||============================== //
export const fetchPercentageData = async ({ queryKey }) => {
const [, appId, startdate, enddate, tenantid, locationid] = queryKey;
const response = await axios.get(
`${process.env.REACT_APP_URL}/orders/getordersummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}`
);
const details = response.data.details;
return {
created: details.created.toString(),
uncoveredOrders: details.pending.toString(),
coveredOrders: details.delivered.toString(),
cancelled: details.cancelled.toString(),
percentage1: (Math.round((details.created / details.total) * 100) || 0).toString(),
percentage2: (Math.round((details.pending / details.total) * 100) || 0).toString(),
percentage3: (Math.round((details.delivered / details.total) * 100) || 0).toString(),
percentage4: (Math.round((details.cancelled / details.total) * 100) || 0).toString()
};
};
// ==============================|| fetchorderscount (orders) ||============================== //
export const fetchorderscount = async ({ queryKey }) => {
const [, appId, startdate, enddate, currentStatus, tenantid, locationid] = queryKey;
const url = `${process.env.REACT_APP_URL}/orders/getordersummary/?applocationid=${appId}&tenantid=${tenantid}&locationid=${locationid}&fromdate=${startdate}&todate=${enddate}&status=${currentStatus}`;
const response = await axios.get(url);
return response.data.details;
};
// ==============================|| fetchOrderSummary (orders)||============================== // // ==============================|| fetchOrderSummary (orders)||============================== //
export const fetchOrderSummary = async () => { export const fetchOrderSummary = async () => {
const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getordersummary`); const response = await axios.get(`${process.env.REACT_APP_URL}/orders/getordersummary`);
@@ -385,12 +413,12 @@ export const createAutomationDeliveries = async (variables) => {
const absentRiders = Array.isArray(variables.absent_riders) ? variables.absent_riders : []; const absentRiders = Array.isArray(variables.absent_riders) ? variables.absent_riders : [];
const url = const url =
variables.selectedMode.value == 1 variables.selectedMode?.value == 1
? `https://routes.workolik.com/api/v1/optimization/riderassign?hypertuning_params=${variables.hypertuning_params}` ? `https://routes.workolik.com/api/v1/optimization/riderassign?hypertuning_params=${variables.hypertuning_params}`
: `https://routemate.workolik.com/api/v1/optimization/riderassign?strategy=multi_trip`; : `https://routemate.workolik.com/api/v1/optimization/riderassign?strategy=multi_trip`;
const body = const body =
variables.selectedMode.value == 1 variables.selectedMode?.value == 1
? { deliveries: variables.deliveries, absent_riders: absentRiders } ? { deliveries: variables.deliveries, absent_riders: absentRiders }
: { ...(variables.data || {}), absent_riders: absentRiders }; : { ...(variables.data || {}), absent_riders: absentRiders };
@@ -431,3 +459,37 @@ export const fetchDeliveries = async ({ pageParam = 1, queryKey }) => {
nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined nextPage: response.data.details.length === Number(rowsPerPage) ? pageParam + 1 : undefined
}; };
}; };
// Fetch payment types.
export const fetchPaymentType = async () => {
const { data } = await axios.get(`${process.env.REACT_APP_URL}/utils/getapptypes/?tag=paymentmode`);
return data.details.map((val) => ({
...val,
label: val.typename
}));
};
// Fetch all riders.
export const getallriders = async () => {
try {
const res = await axios.get(`${process.env.REACT_APP_URL}/partners/getallriders?partnerid=64`);
return res.data.details;
} catch (err) {
console.log('getallriders', err.message);
}
};
// Cancel multiple orders.
export const cancelMultipleOrder = async (orderlist) => {
const data = orderlist?.map((e) => ({
orderheaderid: e.orderheaderid,
orderstatus: 'cancelled',
cancelled: dayjs().format('YYYY-MM-DD HH:mm:ss')
}));
const response = await axios.put(`${process.env.REACT_APP_URL}/orders/updatemultipleorders`, data);
return response.data;
};

File diff suppressed because it is too large Load Diff

View File

@@ -2287,6 +2287,10 @@
color: var(--ad-accent, var(--accent)); color: var(--ad-accent, var(--accent));
} }
.dispatch-container .adcard-m-time {
color: #10b981;
}
.dispatch-container .adcard-ic { .dispatch-container .adcard-ic {
flex-shrink: 0; flex-shrink: 0;
display: inline-flex; display: inline-flex;

View File

@@ -50,6 +50,7 @@ import {
MdInsights, MdInsights,
MdRefresh MdRefresh
} from 'react-icons/md'; } from 'react-icons/md';
import { CircularProgress } from '@mui/material';
import { fetchDeliveries, fetchAppLocations, getRiderPeriodicLogs, fetchRidersLogs, fetchBatchEfficiency } from '../api/api'; import { fetchDeliveries, fetchAppLocations, getRiderPeriodicLogs, fetchRidersLogs, fetchBatchEfficiency } from '../api/api';
import { import {
STATUS_STYLES, STATUS_STYLES,
@@ -2609,15 +2610,20 @@ const Dispatch = ({
<span /> <span />
)} )}
<span className="adcard-metrics"> <span className="adcard-metrics">
<span className="adcard-m adcard-m-km" title="Trip distance">
<span className="adcard-ic"><MdStraighten /></span>
{parseFloat(o.actualkms || o.kms || 0).toFixed(1)} km
</span>
{estMeters !== null && ( {estMeters !== null && (
<span className="adcard-m adcard-m-eta" title="Estimated distance to drop location"> <>
<span className="adcard-ic"><MdMyLocation /></span> <span className="adcard-m adcard-m-eta" title="Distance to drop">
{formatMeters(estMeters)} <span className="adcard-ic"><MdMyLocation /></span>
</span> {formatMeters(estMeters)}
</span>
<span className="adcard-m adcard-m-time" title="Estimated time to drop">
<span className="adcard-ic"><MdAccessTime /></span>
{(() => {
const etaMin = estMeters / 1000 / 20 * 60;
return etaMin < 1 ? '< 1 min' : `${Math.ceil(etaMin)} min`;
})()}
</span>
</>
)} )}
</span> </span>
</div> </div>
@@ -3465,7 +3471,9 @@ const Dispatch = ({
onClick={() => { logger.info('View mode changed: By Zone'); setViewMode('zones'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }} onClick={() => { logger.info('View mode changed: By Zone'); setViewMode('zones'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}
><span className="sbt-icon"><MdMap /></span> By Zone</button> ><span className="sbt-icon"><MdMap /></span> By Zone</button>
<button className={`sbt ${viewMode === 'riders' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: By Rider'); setViewMode('riders'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdDirectionsBike /></span> By Rider</button> <button className={`sbt ${viewMode === 'riders' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: By Rider'); setViewMode('riders'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdDirectionsBike /></span> By Rider</button>
<button className={`sbt ${viewMode === 'all' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: All Active Routes'); setViewMode('all'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPublic /></span>Active</button> {!embedded && (
<button className={`sbt ${viewMode === 'all' ? 'active' : ''}`} onClick={() => { logger.info('View mode changed: All Active Routes'); setViewMode('all'); handleRiderFocus(null); setFocusedKitchen(null); setFocusedZone(null); }}><span className="sbt-icon"><MdPublic /></span>Active</button>
)}
<button <button
type="button" type="button"
className={`sbt sbt-rider-info ${viewMode === 'rider-info' ? 'active' : ''}`} className={`sbt sbt-rider-info ${viewMode === 'rider-info' ? 'active' : ''}`}
@@ -4641,6 +4649,14 @@ const Dispatch = ({
// same `visibleRiders` set the map uses keeps the sidebar // same `visibleRiders` set the map uses keeps the sidebar
// and the map in lock-step (same count, same deliveries). // and the map in lock-step (same count, same deliveries).
(() => { (() => {
if (shouldFetchLive && liveIsFetching && visibleRiders.length === 0) {
return (
<div className="empty-slot" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', height: '100%', minHeight: '200px' }}>
<CircularProgress size={30} style={{ color: '#7b1fa2', marginBottom: '16px' }} />
<div className="empty-slot-title">Loading active deliveries...</div>
</div>
);
}
const activeDeliveries = visibleRiders const activeDeliveries = visibleRiders
.map((r) => getActiveOrder(r.orders)) .map((r) => getActiveOrder(r.orders))
.filter(Boolean) .filter(Boolean)

View File

@@ -552,62 +552,39 @@ const Preview = () => {
<CircularLoader color="inherit" /> <CircularLoader color="inherit" />
</Backdrop> </Backdrop>
<Box sx={{ py: 1.25, px: 2, borderBottom: '1px solid #eef2f6' }}> <Box
sx={{
py: 1.5,
px: 2.5,
borderBottom: '1px solid #e2e8f0',
background: 'linear-gradient(135deg, rgba(102,37,130,0.06) 0%, rgba(146,85,171,0.06) 100%)'
}}
>
<Stack direction="row" alignItems="center" justifyContent="space-between"> <Stack direction="row" alignItems="center" justifyContent="space-between">
<Stack direction="row" alignItems="center" spacing={1}> <Stack direction="row" alignItems="center" spacing={1.5}>
<Tooltip title="Back to orders" placement="top"> <Tooltip title="Back to orders" placement="top">
<IconButton <IconButton
onClick={() => navigate('/nearle/orders')} onClick={() => navigate('/nearle/orders')}
sx={{ bgcolor: 'action.hover', '&:hover': { bgcolor: 'action.selected' } }} sx={{
bgcolor: '#ffffff',
border: '1px solid #e2e8f0',
color: '#662582',
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)',
'&:hover': { bgcolor: '#f8fafc', borderColor: '#662582' }
}}
> >
<HiOutlineArrowLeft size={20} /> <HiOutlineArrowLeft size={18} />
</IconButton> </IconButton>
</Tooltip> </Tooltip>
<Typography variant="h3" fontWeight={600}> <Typography variant="h3" fontWeight={700} sx={{ color: '#0f172a' }}>
Assign Orders Assign Orders
</Typography> </Typography>
</Stack> </Stack>
<Stack direction="row" alignItems="center" spacing={1}>
<Autocomplete
options={tuningTypes || []}
getOptionLabel={(option) => option.type}
sx={{ minWidth: 250, maxWidth: 600, flex: 1 }}
renderInput={(params) => <TextField {...params} label="Hyper Tuning" />}
onChange={(e, val, reason) => {
if (reason === 'clear') handleCreateDelivery(null);
else handleCreateDelivery(val.value);
}}
/>
<Button
variant="contained"
color="primary"
startIcon={<IoReload />}
onClick={() => {
setIsLoading(true);
handleCreateDelivery('reshuffle');
}}
>
Re-Assign
</Button>
<CSVExport
data={csvExportData}
filename={`Orders_Detail_${dayjs().format('YYYY-MM-DD_HHmmss')}.csv`}
label=" CSV"
style={{ m: 1 }}
/>
</Stack>
</Stack> </Stack>
</Box> </Box>
<Box sx={{ px: 2, borderBottom: '1px solid #eef2f6' }}>
<Tabs value={tabValue} onChange={(e, v) => setTabValue(v)} sx={{ minHeight: 40 }}>
<Tab label="Dispatch" sx={{ minHeight: 40, textTransform: 'none', fontWeight: 600 }} />
<Tab label="Reconcile" sx={{ minHeight: 40, textTransform: 'none', fontWeight: 600 }} />
</Tabs>
</Box>
<Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}> <Box sx={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}>
{tabValue === 0 && dispatchPreviewData && ( {dispatchPreviewData && (
<Dispatch <Dispatch
// The key forces a full re-mount when the cache reference changes // The key forces a full re-mount when the cache reference changes
// (after Change Rider / Reconcile / Re-Assign) so Dispatch's // (after Change Rider / Reconcile / Re-Assign) so Dispatch's
@@ -620,144 +597,57 @@ const Preview = () => {
onChangeRider={(order, focusedRider) => openChangeRider(focusedRider, order)} onChangeRider={(order, focusedRider) => openChangeRider(focusedRider, order)}
/> />
)} )}
{tabValue === 1 && (
<Box sx={{ flex: 1, overflow: 'auto', p: 2, bgcolor: '#f8fafc' }}>
{reconcileRiders.length === 0 ? (
<Typography sx={{ color: '#94a3b8', textAlign: 'center', mt: 4 }}>
No rider data available to reconcile.
</Typography>
) : (
<Stack spacing={1.75}>
<Box
sx={{
bgcolor: hasReconciled ? '#ecfdf5' : '#fffbeb',
border: `1px solid ${hasReconciled ? '#a7f3d0' : '#fde68a'}`,
color: hasReconciled ? '#065f46' : '#92400e',
borderRadius: '10px',
px: 1.5,
py: 1,
fontSize: 13
}}
>
{hasReconciled
? 'Steps have been reconciled. The Dispatch tab and Assign payload are updated.'
: 'Click a numbered step to change its rider. Hit Reconcile to verify the corrected steps with the server.'}
</Box>
{reconcileRiders.map((r) => {
const totalKms = r.orders.reduce((s, o) => s + parseFloat(o.actualkms || o.kms || 0), 0);
return (
<Card key={r.rider_id} sx={{ p: 2, borderRadius: '12px', boxShadow: '0 1px 3px rgba(15,23,42,0.06)' }}>
<Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1.25 }}>
<Stack direction="row" alignItems="center" gap={1.25}>
<Box
sx={{
width: 32,
height: 32,
borderRadius: '8px',
bgcolor: '#eef2ff',
color: '#4f46e5',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<MdTwoWheeler size={18} />
</Box>
<Box>
<Typography sx={{ fontWeight: 700, fontSize: 14, color: '#1e293b' }}>
{r.rider_name}
</Typography>
<Typography sx={{ fontSize: 11.5, color: '#64748b' }}>
ID: {r.rider_id}
</Typography>
</Box>
</Stack>
<Stack direction="row" gap={1}>
<Chip size="small" label={`${r.orders.length} stops`} sx={{ fontWeight: 600 }} />
<Chip size="small" label={`${totalKms.toFixed(1)} km`} variant="outlined" />
</Stack>
</Stack>
<Stack direction="row" gap={1.25} sx={{ flexWrap: 'wrap', alignItems: 'center' }}>
{r.orders.map((o, idx) => {
const stepNum = o.step ?? idx + 1;
const color = stepColor(Number(stepNum) - 1);
return (
<Tooltip
key={`${o.orderid}-${idx}`}
title={
<Box>
<div>Order #{o.orderid}</div>
<div>{o.deliveryaddress || o.deliverysuburb || ''}</div>
<div style={{ marginTop: 4, opacity: 0.8 }}>Click to change rider</div>
</Box>
}
>
<Box
onClick={() => openChangeRider(r, o)}
sx={{
width: 36,
height: 36,
borderRadius: '50%',
bgcolor: color,
color: '#fff',
display: 'inline-flex',
alignItems: 'center',
justifyContent: 'center',
fontWeight: 800,
fontSize: 14,
cursor: 'pointer',
boxShadow:
'0 0 0 2px rgba(255,255,255,0.6), 0 1px 3px rgba(15,23,42,0.15)',
transition: 'transform 0.15s',
'&:hover': { transform: 'scale(1.08)' }
}}
>
{stepNum}
</Box>
</Tooltip>
);
})}
</Stack>
</Card>
);
})}
<Box sx={{ display: 'flex', justifyContent: 'center', pt: 1.5, pb: 2 }}>
<Button
variant="contained"
color="primary"
size="large"
startIcon={<MdSwapHoriz />}
onClick={handleReconcile}
disabled={reconcileLoading || dirtyRiderIds.size === 0}
sx={{ minWidth: 220, borderRadius: '10px', textTransform: 'none', fontWeight: 700 }}
>
{reconcileLoading
? 'Reconciling...'
: dirtyRiderIds.size === 0
? 'Reconcile'
: `Reconcile (${dirtyRiderIds.size})`}
</Button>
</Box>
</Stack>
)}
</Box>
)}
</Box> </Box>
<Box sx={{ px: 2, py: 1.25, borderTop: '1px solid #eef2f6' }}> <Box
sx={{
px: 3,
py: 1.75,
borderTop: '1px solid #e2e8f0',
bgcolor: '#ffffff',
boxShadow: '0 -4px 20px rgba(0, 0, 0, 0.03)'
}}
>
<Stack direction="row" gap={2} alignItems="center" justifyContent="end"> <Stack direction="row" gap={2} alignItems="center" justifyContent="end">
<Button <Button
variant="contained" variant="outlined"
color="secondary"
startIcon={<ArrowBackIcon />}
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
sx={{
borderRadius: 999,
px: 3,
py: 1,
borderColor: '#e2e8f0',
color: '#64748b',
textTransform: 'none',
fontWeight: 700,
'&:hover': {
borderColor: '#cbd5e1',
bgcolor: '#f8fafc'
}
}}
> >
Back Back
</Button> </Button>
<Button variant="contained" onClick={handleFinalCreateDelivery}> <Button
variant="contained"
onClick={handleFinalCreateDelivery}
sx={{
borderRadius: 999,
px: 4,
py: 1,
bgcolor: '#662582',
color: '#ffffff',
textTransform: 'none',
fontWeight: 800,
boxShadow: '0 4px 14px rgba(102, 37, 130, 0.25)',
'&:hover': {
bgcolor: '#9255ab',
transform: 'translateY(-1px)',
boxShadow: '0 6px 20px rgba(102, 37, 130, 0.35)'
},
transition: 'all 0.2s'
}}
>
Assign Orders Assign Orders
</Button> </Button>
</Stack> </Stack>

View File

@@ -0,0 +1,768 @@
import {
Autocomplete,
Button,
Chip,
Divider,
Grid,
Stack,
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow,
TextField,
Tooltip,
Typography,
Backdrop,
IconButton
} from '@mui/material';
import React, { Fragment, useEffect, useMemo, useState } from 'react';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import { useLocation, useNavigate } from 'react-router-dom';
import dayjs from 'dayjs';
import MainCard from 'components/MainCard';
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { fetchPaymentType, fetchRidersList, finalCreatedeliveries, notifyRider } from '../api/api';
import { OpenToast } from 'components/nearle_components/OpenToast';
import { useMutation, useQuery } from '@tanstack/react-query';
import Loader from 'components/Loader';
import CircularLoader from 'components/nearle_components/CircularLoader';
import { Empty } from 'antd';
import HoverSocialCard from 'components/cards/statistics/HoverSocialCard';
import { DashboardFilled } from '@ant-design/icons';
import { MdDirectionsBike } from 'react-icons/md';
import { FaMapLocationDot } from 'react-icons/fa6';
import { HiOutlineArrowLeft } from 'react-icons/hi';
var utc = require('dayjs/plugin/utc');
dayjs.extend(utc);
// Mobile-only rendering of the optimised-orders preview. Mirrors the exact same
// fields, chips and tooltips as the desktop table rows — no behaviour added,
// purely a card layout for phones. Renders for both aiMode 1 and normal mode;
// the Zone / Rider fields are gated on aiMode just like the table columns.
const MobileOrdersList = ({ list, aiMode }) => {
if (!list || list.length === 0) {
return (
<Stack alignItems="center" sx={{ py: 4 }}>
<Empty />
</Stack>
);
}
return (
<MobileCardList>
{list.map((val, index) => {
const typeColor =
val.ordertype == 'Economy' ? 'success' : val.ordertype == 'Risky' ? 'error' : 'primary';
return (
<MobileCard
key={index}
header={
<Stack direction="row" alignItems="center" justifyContent="space-between" spacing={1}>
<Stack direction="row" alignItems="center" spacing={1} sx={{ minWidth: 0 }}>
<Typography sx={{ fontWeight: 800, color: '#94a3b8' }}>#{index + 1}</Typography>
{aiMode == 1 && <Chip size="small" color="primary" label={val.zone_name} />}
</Stack>
<Chip size="small" label={val.ordertype} color={typeColor} />
</Stack>
}
>
<MobileFieldGrid>
<MobileField label="Tenant" full>
<Tooltip title={val.tenantaddress}>
<Stack>
<Typography variant="body1" noWrap>
{val.tenantname}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{val.tenantsuburb}
</Typography>
<Typography noWrap variant="body2">
{val.applocation}
</Typography>
</Stack>
</Tooltip>
</MobileField>
<MobileField label="Order Location" full>
<Tooltip title={val.locationaddress} placement="top">
<Typography variant="body1" noWrap>
{`${val.locationname}-(${val.locationsuburb})`}
</Typography>
</Tooltip>
<Tooltip title="Order Id">
<Typography variant="body2" noWrap>
{val.orderid}
</Typography>
</Tooltip>
<Stack display={'flex'} flexDirection={'row'} gap={3}>
<Tooltip title="Ordered date">
<Stack>
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.orderdate).utc().format('hh:mm A')}
</Typography>
</Stack>
</Tooltip>
-
<Tooltip title="Delivery date">
<Stack>
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.deliverydate).utc().format('hh:mm A')}
</Typography>
</Stack>
</Tooltip>
</Stack>
</MobileField>
<MobileField label="Pickup">
<Stack direction="column">
<Typography variant="caption">{val.pickupcustomer}</Typography>
<Typography variant="caption">{val.pickupcontactno}</Typography>
<Tooltip title={val.pickupaddress}>
<Typography variant="caption">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</MobileField>
<MobileField label="Delivery">
<Stack direction="column">
<Typography variant="caption">{val.deliverycustomer}</Typography>
<Typography variant="caption">{val.deliverycontactno}</Typography>
<Tooltip title={val.deliveryaddress}>
<Typography variant="caption">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</MobileField>
{val.ordernotes ? <MobileField label="Notes" value={val.ordernotes} full /> : null}
{aiMode == 1 && (
<MobileField label="Rider" full>
<Typography sx={{ whiteSpace: 'nowrap' }}>{val.username}</Typography>
<Typography>ID : {val.userid}</Typography>
</MobileField>
)}
<MobileField label="Profit">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="Charges" placement="top">
<Chip size="small" label={`${val.deliverycharge.toFixed(2)} `} color="error" />
</Tooltip>
<Tooltip title="Amount" placement="left">
<Chip size="small" label={`${val.deliveryamt.toFixed(2)} `} color="success" />
</Tooltip>
</Stack>
</MobileField>
<MobileField label="KMS">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="KMS" placement="top">
<Chip size="small" label={`${val.kms} km`} color="error" />
</Tooltip>
<Tooltip title="Cumulative Kms" placement="right">
<Chip size="small" label={`${val.cumulativekms} km`} color="success" />
</Tooltip>
</Stack>
</MobileField>
</MobileFieldGrid>
</MobileCard>
);
})}
</MobileCardList>
);
};
const OrdersPreview = () => {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const navigate = useNavigate();
const location = useLocation();
console.log('location.state', location.state);
const [rider, setRider] = useState(null);
const [payment, setPayment] = useState(null);
const [finaldeliveryList, setFinalDeliveryList] = useState([]);
const [isLoading, setIsLoading] = useState(false);
const deliverylist = location.state?.deliverylist;
const zoneData = location.state?.zoneData;
const metaData = location.state?.metaData;
const riderToken = location.state?.riderToken;
const appId = location.state?.appId;
const aiMode = location.state?.aiMode;
const reassignOrders = location.state?.reassignOrders;
useEffect(() => {
console.log('aiMode', aiMode);
console.log('riderToken', riderToken);
console.log('zoneData', zoneData);
console.log('metaData', metaData);
console.log('reassignOrders', reassignOrders);
}, []);
useEffect(() => {
if (!deliverylist?.length) return;
const updateDeliveryAmtList = deliverylist.map((list) => {
const cumulativeKms = Number(list.cumulativekms || 0);
const minKm = Number(list.minkm || 0);
const basePrice = Number(list.baseprice || 0);
const pricePerKm = Number(list.priceperkm || 0);
if (cumulativeKms <= minKm) {
return {
...list,
deliveryamt: basePrice
};
}
return {
...list,
deliveryamt: (cumulativeKms - minKm) * pricePerKm + basePrice
};
});
setFinalDeliveryList(updateDeliveryAmtList);
console.log('finaldeliveryList', updateDeliveryAmtList);
}, [deliverylist]);
// ==============================|| fetchPaymentType ||============================== //
const {
data: paymentModes = [],
isLoading: paymentModesLoading
} = useQuery({
queryKey: ['paymentmodes'],
queryFn: fetchPaymentType
});
// ==============================|| fetchRidersList ||============================== //
const {
data: ridersList = [],
isLoading: ridersListLoading
} = useQuery({
queryKey: ['ridersList', appId], // Unique key for caching & re-fetching
queryFn: fetchRidersList,
enabled: appId !== 0 // Ensures query runs only when appId is valid
});
const getRiderName = async (userid) => {
await ridersList.map((rider) => {
if (rider.userid == userid) {
return rider.firstname;
}
});
};
// ======================================================= || notifyRiderMutation || =======================================================
const notifyRiderMutation = useMutation({
mutationFn: notifyRider, // Using the separate function
onSuccess: () => {
OpenToast('Notification sent Successfully', 'success', 2000);
},
onError: (error) => {
OpenToast(error.message, 'error', 2000);
}
});
const createNormalDeliveryMutation = useMutation({
mutationFn: finalCreatedeliveries, // for optimised delivery create
onSuccess: (data, variables) => {
console.log('data', data);
console.log('varialbles', variables);
notifyRiderMutation.mutate(rider?.userfcmtoken || riderToken); // Call notifyRider after success
if (data.status == 'accepted') {
OpenToast('Delivery Created Successfully', 'success', 2000);
}
setTimeout(() => {
setIsLoading(false);
navigate('/nearle/orders');
}, 2000);
},
onError: (error) => {
OpenToast(error.message, 'error', 4000);
}
});
const handleManualCreateDelivery = async () => {
setIsLoading(true);
createNormalDeliveryMutation.mutate({
deliveries: finaldeliveryList
});
};
return (
<MainCard
content={false}
title={
<Stack direction="row" alignItems="center" spacing={1} sx={{ ml: 1 }}>
<Tooltip title="Back to orders" placement="top">
<IconButton
onClick={() => navigate('/nearle/orders')}
sx={{
backgroundColor: 'action.hover',
color: 'text.primary',
'&:hover': {
backgroundColor: 'action.selected'
}
}}
>
<HiOutlineArrowLeft size={22} />
</IconButton>{' '}
</Tooltip>
<Typography sx={{ m: 2 }} variant="h3">
Assign Orders
</Typography>
</Stack>
}
secondary={
<Button sx={{ m: 2 }} color="primary" variant="contained" startIcon={<ArrowBackIcon />}>
Re-Assign
</Button>
}
>
{(paymentModesLoading || ridersListLoading || isLoading) && (
<>
<Loader />
<CircularLoader />
</>
)}
{
<Backdrop
sx={{
color: '#fff',
zIndex: (theme) => theme.zIndex.drawer + 1
}}
open={paymentModesLoading || ridersListLoading || isLoading} // when loader = true, backdrop covers the page
>
<CircularLoader color="inherit" />
</Backdrop>
}
{aiMode == 1 && (
<Stack sx={{ m: 2 }}>
<Grid container spacing={2}>
<Grid item xs={12} sm={3}>
<HoverSocialCard
secondary={metaData?.total_orders}
primary={'Orders'}
percentage={<DashboardFilled />}
color={theme.palette.success.main}
sx={{ cursor: 'pointer' }}
/>
</Grid>
<Grid item xs={12} sm={3}>
<HoverSocialCard
secondary={metaData?.total_riders}
primary={'Riders'}
percentage={<MdDirectionsBike />}
color={theme.palette.warning.main}
/>
</Grid>
<Grid item xs={12} sm={3}>
<HoverSocialCard
secondary={zoneData?.length}
primary={'Zones'}
percentage={<FaMapLocationDot />}
color={theme.palette.info.main}
/>
</Grid>
<Grid item xs={12} sm={3}>
<HoverSocialCard
secondary={zoneData?.length}
primary={'Kilometer'}
percentage={<FaMapLocationDot />}
color={theme.palette.error.main}
/>
</Grid>
</Grid>
</Stack>
)}
{isMobile ? (
<MobileOrdersList list={finaldeliveryList} aiMode={aiMode} />
) : (
<TableContainer
sx={{
maxHeight: 'calc(100vh - 250px)',
overflow: 'auto',
'&::-webkit-scrollbar': {
width: '12px', // scroll bar width
cursor: 'pointer'
},
'&::-webkit-scrollbar-thumb': {
backgroundColor: theme.palette.primary.main, // thumb color
borderRadius: '8px',
cursor: 'pointer'
},
'&::-webkit-scrollbar-thumb:hover': {
backgroundColor: theme.palette.primary.dark, // hover color
cursor: 'pointer'
},
'&::-webkit-scrollbar-track': {
backgroundColor: theme.palette.primary.lighter,
cursor: 'pointer'
}
}}
>
<Table stickyHeader>
<TableHead>
<TableRow sx={{ backgroundColor: 'red' }}>
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>#</TableCell>
{aiMode == 1 && (
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Zone </TableCell>
)}
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Tenant </TableCell>
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>order Location</TableCell>
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Pickup </TableCell>
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Delivery</TableCell>
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Notes</TableCell>
{aiMode == 1 && (
<TableCell sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>Rider</TableCell>
)}
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
Type
</TableCell>
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
Profit
</TableCell>
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
Charges
</TableCell>
<TableCell align="center" sx={{ position: 'sticky !important', backgroundColor: theme.palette.secondary.light }}>
KMS
</TableCell>
</TableRow>
</TableHead>
<TableBody>
{finaldeliveryList?.length == 0 && (
<TableRow>
<TableCell colSpan={10}>
<Empty />
</TableCell>
</TableRow>
)}
{finaldeliveryList && aiMode == 1 // ai mode , ai automation
? finaldeliveryList?.map((val, index) => {
return (
<Fragment key={index}>
<TableRow sx={{}}>
<TableCell>
<Typography> {index + 1}</Typography>
</TableCell>
{aiMode == 1 && (
<TableCell>
<Chip color="primary" label={val.zone_name} />
</TableCell>
)}
<TableCell>
<Tooltip title={val.tenantaddress}>
<Typography variant="body1" noWrap>
{val.tenantname}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{val.tenantsuburb}
<br />
</Typography>
<Typography noWrap variant="body2">
{val.applocation}
</Typography>
</Tooltip>
</TableCell>
<TableCell align="left">
<Tooltip title={val.locationaddress} placement="top">
<Typography variant="body1" noWrap>
{`${val.locationname}-(${val.locationsuburb})`}
</Typography>
</Tooltip>
<Tooltip title="Order Id">
<Typography variant="body2" noWrap>
{val.orderid}
</Typography>
</Tooltip>
<Stack display={'flex'} flexDirection={'row'} gap={3}>
<Tooltip title="Ordered date">
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.orderdate).utc().format('hh:mm A')}
</Typography>
</Tooltip>
-
<Tooltip title="Delivery date">
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.deliverydate).utc().format('hh:mm A')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
<TableCell align="left">
<Stack direction={'row'} spacing={1}>
<Stack direction="column">
<Typography variant="caption">{val.pickupcustomer}</Typography>
<Typography variant="caption">{val.pickupcontactno}</Typography>
<Tooltip title={val.pickupaddress}>
<Typography variant="caption">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
<TableCell align="left">
<Stack direction={'row'} spacing={1}>
<Stack direction="column">
<Typography variant="caption">{val.deliverycustomer}</Typography>
<Typography variant="caption">{val.deliverycontactno}</Typography>
<Tooltip title={val.deliveryaddress}>
<Typography variant="caption">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
<TableCell align="left">{val.ordernotes}</TableCell>
{aiMode == 1 && (
<TableCell align="left">
<Typography sx={{ whiteSpace: 'nowrap' }}>{val.username}</Typography>
<Typography>ID : {val.userid}</Typography>
</TableCell>
)}
<TableCell align="center">
<Chip
size="small"
label={val.ordertype}
color={val.ordertype == 'Economy' ? 'success' : val.ordertype == 'Risky' ? 'error' : 'primary'}
/>
</TableCell>
<TableCell align="center">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="Charges" placement="top">
<Chip size="small" label={`${val.deliverycharge.toFixed(2)} `} color="error" />
</Tooltip>
<Tooltip title="Amount" placement="left">
<Chip size="small" label={`${val.deliveryamt.toFixed(2)} `} color="success" />
</Tooltip>
</Stack>
</TableCell>
<TableCell align="center">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="KMS" placement="top">
<Chip size="small" label={`${val.kms} km`} color="error" />
</Tooltip>
<Tooltip title="Cumulative Kms" placement="right">
<Chip size="small" label={`${val.cumulativekms} km`} color="success" />
</Tooltip>
</Stack>
</TableCell>
</TableRow>
</Fragment>
);
})
: // normal optimisation
finaldeliveryList?.map((val, index) => {
return (
<Fragment key={index}>
<TableRow sx={{}}>
<TableCell>
<Typography> {index + 1}</Typography>
</TableCell>
<TableCell>
<Tooltip title={val.tenantaddress}>
<Typography variant="body1" noWrap>
{val.tenantname}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{val.tenantsuburb}
<br />
</Typography>
<Typography noWrap variant="body2">
{val.applocation}
</Typography>
</Tooltip>
</TableCell>
<TableCell align="left">
<Tooltip title={val.locationaddress} placement="top">
<Typography variant="body1" noWrap>
{`${val.locationname}-(${val.locationsuburb})`}
</Typography>
</Tooltip>
<Tooltip title="Order Id">
<Typography variant="body2" noWrap>
{val.orderid}
</Typography>
</Tooltip>
<Stack display={'flex'} flexDirection={'row'} gap={3}>
<Tooltip title="Ordered date">
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.orderdate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.orderdate).utc().format('hh:mm A')}
</Typography>
</Tooltip>
-
<Tooltip title="Delivery date">
<Typography noWrap sx={{ fontSize: '12px' }}>
{dayjs(val.deliverydate).utc().format('DD/MM/YYYY')}
</Typography>
<Typography noWrap sx={{ fontSize: '11px' }}>
{dayjs(val.deliverydate).utc().format('hh:mm A')}
</Typography>
</Tooltip>
</Stack>
</TableCell>
<TableCell align="left">
<Stack direction={'row'} spacing={1}>
<Stack direction="column">
<Typography variant="caption">{val.pickupcustomer}</Typography>
<Typography variant="caption">{val.pickupcontactno}</Typography>
<Tooltip title={val.pickupaddress}>
<Typography variant="caption">{val.pickupsuburb || val.pickupaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
<TableCell align="left">
<Stack direction={'row'} spacing={1}>
<Stack direction="column">
<Typography variant="caption">{val.deliverycustomer}</Typography>
<Typography variant="caption">{val.deliverycontactno}</Typography>
<Tooltip title={val.deliveryaddress}>
<Typography variant="caption">{val.deliverysuburb || val.deliveryaddress.slice(0, 20)}</Typography>
</Tooltip>
</Stack>
</Stack>
</TableCell>
<TableCell align="left">{val.ordernotes}</TableCell>
<TableCell align="center">
<Chip
size="small"
label={val.ordertype}
color={val.ordertype == 'Economy' ? 'success' : val.ordertype == 'Risky' ? 'error' : 'primary'}
/>
</TableCell>
<TableCell align="center">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="Charges" placement="top">
<Chip size="small" label={`${val.deliverycharge.toFixed(2)} `} color="error" />
</Tooltip>
<Tooltip title="Amount" placement="left">
<Chip size="small" label={`${val.deliveryamt.toFixed(2)} `} color="success" />
</Tooltip>
</Stack>
</TableCell>
<TableCell align="center">
<Stack display={'flex'} flexDirection={'column'} gap={1} sx={{ cursor: 'pointer' }}>
<Tooltip title="KMS" placement="top">
<Chip size="small" label={`${val.kms} km`} color="error" />
</Tooltip>
<Tooltip title="Cumulative Kms" placement="right">
<Chip size="small" label={`${val.cumulativekms} km`} color="success" />
</Tooltip>
</Stack>
</TableCell>
</TableRow>
</Fragment>
);
})}
</TableBody>
</Table>
</TableContainer>
)}
<Divider />
{aiMode == 0 && (
<Grid container spacing={2} sx={{ p: 2 }}>
<Grid item xs={12} sm={6}>
<Autocomplete
id="free-solo-demo"
options={paymentModes}
renderInput={(params) => <TextField {...params} label="Choose Payment" />}
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
setPayment(null);
return;
}
if (newValue) {
console.log('Selected:', newValue);
setPayment(newValue);
const newList = finaldeliveryList?.map((list) => ({
...list,
paymenttype: newValue.apptypeid // merge selected rider into each list item
}));
setFinalDeliveryList(newList);
}
}}
/>
</Grid>
<Grid item xs={12} sm={6}>
<Autocomplete
id="free-solo-demo"
options={ridersList}
renderInput={(params) => <TextField {...params} label="Choose Rider" />}
onChange={(event, newValue, reason) => {
if (reason === 'clear') {
setRider(null);
return;
}
if (newValue) {
setRider(newValue);
console.log('Selected:', newValue);
const newList = finaldeliveryList?.map((list) => ({
...list,
userid: newValue.userid,
userfcmtoken: newValue.userfcmtoken
}));
setFinalDeliveryList(newList);
}
}}
/>
</Grid>
</Grid>
)}
<Divider />
<Stack
display={'flex'}
flexDirection={{ xs: 'column', sm: 'row' }}
gap={2}
alignItems={{ xs: 'stretch', sm: 'center' }}
justifyContent={'end'}
sx={{ p: 2 }}
>
<Button
fullWidth={isMobile}
variant="contained"
color="secondary"
startIcon={<ArrowBackIcon />}
onClick={() => {
navigate('/nearle/orders');
}}
>
Back
</Button>
<Button
fullWidth={isMobile}
sx={{ my: { xs: 0, sm: 2 } }}
variant="contained"
disabled={aiMode === 0 && (!rider || !payment)}
onClick={handleManualCreateDelivery}
>
Finalise
</Button>
</Stack>
</MainCard>
);
};
export default OrdersPreview;

View File

@@ -0,0 +1,49 @@
import { TableRow, TableCell, Skeleton, Stack } from '@mui/material';
export const OrdersTableSkeleton = ({ rowsPerPage = 5, col = 1 }) => {
return (
<>
{Array.from(new Array(rowsPerPage)).map((_, index) => (
<TableRow key={index}>
{/* Checkbox */}
<TableCell>
<Skeleton variant="circular" width={24} height={24} />
</TableCell>
{/* Serial Number */}
<TableCell>
<Skeleton variant="text" width={30} />
</TableCell>
{/* Delivery Info */}
{Array.from({ length: col }).map((_, index) => (
<TableCell key={index}>
<Stack spacing={0.5}>
<Skeleton variant="text" width={100} />
<Skeleton variant="text" width={80} />
</Stack>
</TableCell>
))}
{/* Notes */}
<TableCell>
<Skeleton variant="text" width={150} />
</TableCell>
{/* Order Status */}
<TableCell>
<Skeleton variant="rounded" width={60} height={24} />
</TableCell>
{/* Actions */}
<TableCell align="center">
<Stack direction="row" spacing={1} justifyContent="flex-end">
<Skeleton variant="circular" width={28} height={28} />
<Skeleton variant="circular" width={28} height={28} />
</Stack>
</TableCell>
</TableRow>
))}
</>
);
};

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,8 @@ const Customers = Loadable(lazy(() => import('pages/nearle/clients/customers')))
const Locations = Loadable(lazy(() => import('pages/nearle/locations/Locations'))); const Locations = Loadable(lazy(() => import('pages/nearle/locations/Locations')));
const Orders = Loadable(lazy(() => import('pages/nearle/orders/orders'))); const Orders = Loadable(lazy(() => import('pages/nearle/orders/orders')));
const Deliveries = Loadable(lazy(() => import('pages/nearle/deliveries/deliveries')));
const OrdersPreview = Loadable(lazy(() => import('pages/nearle/orders/OrdersPreview')));
const Details = Loadable(lazy(() => import('pages/nearle/orders/details'))); const Details = Loadable(lazy(() => import('pages/nearle/orders/details')));
const Accountsettings = Loadable(lazy(() => import('pages/nearle/accountsettings'))); const Accountsettings = Loadable(lazy(() => import('pages/nearle/accountsettings')));
@@ -63,11 +65,15 @@ const MainRoutes = {
path: 'orders', path: 'orders',
children: [ children: [
{ {
path: '', // /orders path: '',
element: <Orders /> element: <Orders />
}, },
{ {
path: 'create/grouporders', // /orders/create/grouporders path: 'preview',
element: <OrdersPreview />
},
{
path: 'create/grouporders',
element: <MultipleOrders /> element: <MultipleOrders />
}, },
{ {
@@ -76,6 +82,10 @@ const MainRoutes = {
} }
] ]
}, },
{
path: 'deliveries',
element: <Deliveries />
},
{ {
path: 'customers', path: 'customers',
children: [ children: [