updates on the deliveries page and the order creation page
This commit is contained in:
@@ -13,7 +13,7 @@ import Loader from 'components/Loader';
|
||||
import { KeyboardArrowDownOutlined, KeyboardArrowUpOutlined } from '@mui/icons-material';
|
||||
|
||||
import { PiMapPinLineDuotone } from 'react-icons/pi';
|
||||
import { MdOutlineDateRange } from 'react-icons/md';
|
||||
import { MdOutlineDateRange, MdPersonOff, MdEventBusy } from 'react-icons/md';
|
||||
import { VscArchive } from 'react-icons/vsc';
|
||||
import DateFilterDialog from 'components/DateFilterDialog';
|
||||
import DebounceSearchBar from 'components/nearle_components/DebounceSearchBar';
|
||||
@@ -29,11 +29,7 @@ import DirectionsBikeOutlinedIcon from '@mui/icons-material/DirectionsBikeOutlin
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import BoltIcon from '@mui/icons-material/Bolt';
|
||||
import { ArrowRightAltOutlined } from '@mui/icons-material';
|
||||
import { DashboardFilled } from '@ant-design/icons';
|
||||
import { MdDirectionsBike } from 'react-icons/md';
|
||||
import { FaMapLocationDot } from 'react-icons/fa6';
|
||||
import { HiOutlineArrowLeft } from 'react-icons/hi';
|
||||
import { GiProfit } from 'react-icons/gi';
|
||||
|
||||
import { IoReload } from 'react-icons/io5';
|
||||
|
||||
@@ -74,7 +70,8 @@ import {
|
||||
Divider,
|
||||
AccordionDetails,
|
||||
AccordionSummary,
|
||||
Accordion
|
||||
Accordion,
|
||||
Paper
|
||||
} from '@mui/material';
|
||||
|
||||
import {
|
||||
@@ -152,6 +149,10 @@ const Orders = () => {
|
||||
{ label: 'Auto', value: 2 }
|
||||
];
|
||||
const [selectedMode, setSelectedMode] = useState(null);
|
||||
// Riders the operator has marked as absent for today — passed to the AI
|
||||
// assign API so its allocation skips them. Optional: the AI run still
|
||||
// works with an empty list.
|
||||
const [absentRiders, setAbsentRiders] = useState([]);
|
||||
|
||||
// when orders are selected to show popup to stop unwanted reload
|
||||
|
||||
@@ -549,6 +550,18 @@ const Orders = () => {
|
||||
deliverylocation: val.deliverysuburb
|
||||
}));
|
||||
console.log('deliveryData', deliveryData);
|
||||
// Normalize the operator's "Absent Riders" picks into the {userid, username}
|
||||
// shape the AI assign API expects. The API returns firstname/lastname, so
|
||||
// we join them into a single `username` string (with a fallback to the
|
||||
// raw `username` field when names are missing).
|
||||
const absentRidersPayload = (absentRiders || []).map((r) => ({
|
||||
userid: r.userid,
|
||||
username:
|
||||
`${r.firstname || ''} ${r.lastname || ''}`.trim() ||
|
||||
r.username ||
|
||||
`Rider ${r.userid}`
|
||||
}));
|
||||
|
||||
if (aiModeRef.current == 0) {
|
||||
// manual assign
|
||||
createDeliveryMutation.mutate({
|
||||
@@ -561,7 +574,8 @@ const Orders = () => {
|
||||
// mode 1 -> bike , 2 -> auto
|
||||
deliveries: selectedMode.value == 1 ? deliveryData : { body: deliveryData },
|
||||
hypertuning_params: tune || null,
|
||||
selectedMode
|
||||
selectedMode,
|
||||
absent_riders: absentRidersPayload
|
||||
// reshuffle: 'joshi'
|
||||
});
|
||||
} else {
|
||||
@@ -574,7 +588,8 @@ const Orders = () => {
|
||||
pay_type: 'hourly', // options: "hourly", "daily", or "orders"
|
||||
base_pay: 300.0,
|
||||
strategy: 'multi_trip'
|
||||
}
|
||||
},
|
||||
absent_riders: absentRidersPayload
|
||||
},
|
||||
selectedMode
|
||||
});
|
||||
@@ -634,6 +649,8 @@ const Orders = () => {
|
||||
setAssignDialog(false);
|
||||
setRider(null);
|
||||
setPayment(null);
|
||||
setSelectedMode(null);
|
||||
setAbsentRiders([]);
|
||||
};
|
||||
|
||||
const errorMessage = fetchpercentageIsError
|
||||
@@ -1716,6 +1733,235 @@ const Orders = () => {
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Absent Riders multi-select — optional. Operator picks the
|
||||
riders who are off today; the AI assign API receives their
|
||||
{userid, username} under `absent_riders` and skips them
|
||||
during allocation. Leaving it empty is valid: the AI run
|
||||
proceeds against every rider in the location. */}
|
||||
{aiModeRef.current == 1 && (
|
||||
<Autocomplete
|
||||
multiple
|
||||
fullWidth={false}
|
||||
options={ridersList || []}
|
||||
loading={ridersListLoading}
|
||||
disableCloseOnSelect
|
||||
limitTags={2}
|
||||
value={absentRiders}
|
||||
onChange={(event, newValue) => setAbsentRiders(newValue)}
|
||||
getOptionLabel={(option) => {
|
||||
const name = `${option.firstname || ''} ${option.lastname || ''}`.trim();
|
||||
return name || `Rider #${option.userid}`;
|
||||
}}
|
||||
isOptionEqualToValue={(option, value) => option.userid === value.userid}
|
||||
PaperComponent={(paperProps) => (
|
||||
<Paper
|
||||
{...paperProps}
|
||||
sx={{
|
||||
mt: 0.75,
|
||||
borderRadius: 2,
|
||||
boxShadow: '0 14px 40px rgba(15, 23, 42, 0.18)',
|
||||
border: '1px solid',
|
||||
borderColor: 'divider',
|
||||
overflow: 'hidden'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
ListboxProps={{ sx: { py: 0, maxHeight: 340 } }}
|
||||
noOptionsText={
|
||||
<Stack alignItems="center" py={2} spacing={1}>
|
||||
<MdEventBusy size={26} color="#94a3b8" />
|
||||
<Typography variant="caption" color="text.secondary">
|
||||
No riders to show for this location
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
renderTags={(value, getTagProps) =>
|
||||
value.map((option, index) => {
|
||||
const name =
|
||||
`${option.firstname || ''} ${option.lastname || ''}`.trim() ||
|
||||
`Rider #${option.userid}`;
|
||||
const initials =
|
||||
(name.match(/\b\w/g) || []).slice(0, 2).join('').toUpperCase() || '?';
|
||||
const tagProps = getTagProps({ index });
|
||||
return (
|
||||
<Chip
|
||||
{...tagProps}
|
||||
key={option.userid}
|
||||
size="small"
|
||||
avatar={
|
||||
<Avatar
|
||||
sx={{
|
||||
bgcolor: '#fed7aa',
|
||||
color: '#9a3412',
|
||||
fontSize: 10,
|
||||
fontWeight: 800
|
||||
}}
|
||||
>
|
||||
{initials}
|
||||
</Avatar>
|
||||
}
|
||||
label={name}
|
||||
sx={{
|
||||
height: 26,
|
||||
bgcolor: '#fff7ed',
|
||||
color: '#9a3412',
|
||||
border: '1px solid',
|
||||
borderColor: '#fdba74',
|
||||
fontWeight: 600,
|
||||
fontSize: 12,
|
||||
'& .MuiChip-label': { px: 0.75 },
|
||||
'& .MuiChip-deleteIcon': {
|
||||
color: '#c2410c',
|
||||
fontSize: 16,
|
||||
'&:hover': { color: '#7c2d12' }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})
|
||||
}
|
||||
renderOption={(props, option, { selected }) => {
|
||||
const name = `${option.firstname || ''} ${option.lastname || ''}`.trim();
|
||||
const initials =
|
||||
(name.match(/\b\w/g) || []).slice(0, 2).join('').toUpperCase() || '?';
|
||||
return (
|
||||
<li
|
||||
{...props}
|
||||
key={option.userid}
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 12,
|
||||
padding: '8px 12px',
|
||||
borderBottom: '1px solid #f1f5f9',
|
||||
backgroundColor: selected ? '#fff7ed' : 'transparent'
|
||||
}}
|
||||
>
|
||||
<Checkbox
|
||||
size="small"
|
||||
checked={selected}
|
||||
sx={{
|
||||
p: 0.5,
|
||||
color: '#fb923c',
|
||||
'&.Mui-checked': { color: '#ea580c' }
|
||||
}}
|
||||
/>
|
||||
<Avatar
|
||||
sx={{
|
||||
width: 34,
|
||||
height: 34,
|
||||
bgcolor: selected ? '#fb923c' : '#f1f5f9',
|
||||
color: selected ? '#fff' : '#475569',
|
||||
fontSize: 12,
|
||||
fontWeight: 800,
|
||||
transition: 'background-color 0.15s, color 0.15s'
|
||||
}}
|
||||
>
|
||||
{initials}
|
||||
</Avatar>
|
||||
<Stack direction="column" spacing={0} flex={1} minWidth={0}>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight={700}
|
||||
color="#0f172a"
|
||||
noWrap
|
||||
>
|
||||
{name || `Rider #${option.userid}`}
|
||||
</Typography>
|
||||
<Typography variant="caption" color="text.secondary" noWrap>
|
||||
ID #{option.userid}
|
||||
{option.contactno ? ` · ${option.contactno}` : ''}
|
||||
</Typography>
|
||||
</Stack>
|
||||
{selected && (
|
||||
<Chip
|
||||
size="small"
|
||||
label="Absent"
|
||||
sx={{
|
||||
height: 18,
|
||||
fontSize: 9,
|
||||
fontWeight: 800,
|
||||
letterSpacing: 0.3,
|
||||
bgcolor: '#ea580c',
|
||||
color: '#fff',
|
||||
'& .MuiChip-label': { px: 0.75 }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</li>
|
||||
);
|
||||
}}
|
||||
renderInput={(params) => (
|
||||
<TextField
|
||||
{...params}
|
||||
label="Absent Riders"
|
||||
placeholder={absentRiders.length ? '' : 'Pick riders unavailable today'}
|
||||
InputLabelProps={{ shrink: true }}
|
||||
InputProps={{
|
||||
...params.InputProps,
|
||||
startAdornment: (
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
spacing={0.75}
|
||||
sx={{ pl: 0.5, mr: 0.25 }}
|
||||
>
|
||||
<MdPersonOff size={18} color="#ea580c" />
|
||||
{absentRiders.length > 0 && (
|
||||
<Chip
|
||||
size="small"
|
||||
label={absentRiders.length}
|
||||
sx={{
|
||||
height: 18,
|
||||
minWidth: 22,
|
||||
fontSize: 10,
|
||||
fontWeight: 800,
|
||||
bgcolor: '#ea580c',
|
||||
color: '#fff',
|
||||
'& .MuiChip-label': { px: 0.5 }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{params.InputProps.startAdornment}
|
||||
</Stack>
|
||||
)
|
||||
}}
|
||||
sx={{
|
||||
minWidth: 300,
|
||||
maxWidth: 440,
|
||||
'& .MuiOutlinedInput-root': {
|
||||
borderRadius: 2,
|
||||
bgcolor: '#fffbf5',
|
||||
transition: 'border-color 0.15s, box-shadow 0.15s',
|
||||
'& fieldset': {
|
||||
borderColor: '#fdba74',
|
||||
borderWidth: 1.5
|
||||
},
|
||||
'&:hover fieldset': { borderColor: '#fb923c' },
|
||||
'&.Mui-focused': {
|
||||
boxShadow: '0 0 0 3px rgba(251, 146, 60, 0.18)'
|
||||
},
|
||||
'&.Mui-focused fieldset': {
|
||||
borderColor: '#ea580c',
|
||||
borderWidth: 2
|
||||
}
|
||||
},
|
||||
'& .MuiInputLabel-root': {
|
||||
fontWeight: 700,
|
||||
fontSize: 13,
|
||||
color: '#9a3412',
|
||||
'&.Mui-focused': { color: '#9a3412' }
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
sx={{
|
||||
'& .MuiAutocomplete-tag': { my: 0.25 },
|
||||
'& .MuiAutocomplete-endAdornment': { right: 8 }
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
<Button
|
||||
color={'primary'}
|
||||
disabled={!selectedMode && aiModeRef.current == 1}
|
||||
@@ -2108,7 +2354,7 @@ const Orders = () => {
|
||||
<CircularLoader color="inherit" />
|
||||
</Backdrop>
|
||||
}
|
||||
<DialogTitle>
|
||||
<DialogTitle sx={{ py: 1.25 }}>
|
||||
<Stack direction="row" alignItems="center" justifyContent="space-between" sx={{}}>
|
||||
<Stack direction="row" alignItems="center" spacing={1}>
|
||||
<Tooltip title="Back to orders" placement="top">
|
||||
@@ -2190,51 +2436,13 @@ const Orders = () => {
|
||||
/>
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack sx={{ my: 2 }}>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_orders}
|
||||
primary={'Orders'}
|
||||
percentage={<DashboardFilled />}
|
||||
color={theme.palette.success.main}
|
||||
sx={{ cursor: 'pointer' }}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.utilized_riders}
|
||||
primary={'Riders'}
|
||||
percentage={<MdDirectionsBike />}
|
||||
color={theme.palette.warning.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={zoneData?.length}
|
||||
primary={'Zones'}
|
||||
percentage={<FaMapLocationDot />}
|
||||
color={theme.palette.info.main}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={6} sm={6} md={3}>
|
||||
<HoverSocialCard
|
||||
secondary={metaData?.total_profit}
|
||||
primary={'Profit'}
|
||||
percentage={<GiProfit />}
|
||||
color={theme.palette.error.main}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Stack>
|
||||
</DialogTitle>
|
||||
<DialogContent sx={{ p: 0, display: 'flex', flexDirection: 'column', overflow: 'hidden', flex: 1 }}>
|
||||
{dispatchPreviewData && <Dispatch data={dispatchPreviewData} embedded />}
|
||||
</DialogContent>
|
||||
<DialogActions>
|
||||
<Stack display={'flex'} flexDirection={'row'} gap={2} alignItems={'center'} justifyContent={'end'} sx={{ p: 2 }}>
|
||||
<DialogActions sx={{ px: 2, py: 1.25 }}>
|
||||
<Stack direction="row" gap={2} alignItems="center" justifyContent="end">
|
||||
<Button
|
||||
sx={{}}
|
||||
variant="contained"
|
||||
color="secondary"
|
||||
startIcon={<ArrowBackIcon />}
|
||||
@@ -2245,7 +2453,7 @@ const Orders = () => {
|
||||
>
|
||||
Back
|
||||
</Button>
|
||||
<Button sx={{ my: 2 }} variant="contained" onClick={handleFinalCreateDelivery}>
|
||||
<Button variant="contained" onClick={handleFinalCreateDelivery}>
|
||||
Assign Orders
|
||||
</Button>
|
||||
</Stack>
|
||||
|
||||
Reference in New Issue
Block a user