updates on the orders and assign page
This commit is contained in:
BIN
src/assets/images/aiImage.png
Normal file
BIN
src/assets/images/aiImage.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 759 KiB |
34
src/components/nearle_components/LoaderWithImage.js
Normal file
34
src/components/nearle_components/LoaderWithImage.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
140
src/components/nearle_components/MobileCard.js
Normal file
140
src/components/nearle_components/MobileCard.js
Normal 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
|
||||||
|
};
|
||||||
@@ -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" />,
|
||||||
|
|||||||
@@ -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" />,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
1104
src/pages/nearle/deliveries/deliveries.js
Normal file
1104
src/pages/nearle/deliveries/deliveries.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
768
src/pages/nearle/orders/OrdersPreview.js
Normal file
768
src/pages/nearle/orders/OrdersPreview.js
Normal 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;
|
||||||
49
src/pages/nearle/orders/OrdersTableSkeleton.js
Normal file
49
src/pages/nearle/orders/OrdersTableSkeleton.js
Normal 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
@@ -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: [
|
||||||
|
|||||||
Reference in New Issue
Block a user