Phase 1: env config + login auth (Doormile JWT) Phase 2: full API layer rewrite in api.js - All endpoints now point at api.doormile.com/api/v1 - Removed all jupiter.nearle.app references - Removed all routes.workolik.com references (Phase 3) - Miler CRUD: GET/POST/PATCH /admin/milers - Clients: GET/POST/PATCH /crm/clients - Bookings: GET /admin/bookings - Auto-assign: POST /hub/bookings/:id/auto-assign Pages rewritten: - riders.js - Doormile miler fields, status mapping - createrider.js - fixed broken form, real miler creation - editRider.js - stripped bank/vehicle/insurance, hub mapping - Tenants.js - stripped pricing dialog, CRM client fields Menu: renamed Orders/Deliveries/Tenants/Riders/Dispatch to Bookings/Consignments/Clients/Milers/Live Operations Pending Phase 3: dispatch pages, reports, consignments
214 lines
7.6 KiB
JavaScript
214 lines
7.6 KiB
JavaScript
import { useEffect, useState } from 'react';
|
|
|
|
// material-ui
|
|
import { useLocation, useNavigate } from 'react-router-dom';
|
|
|
|
import { Box, Button, Grid, InputLabel, MenuItem, Select, Stack, TextField, Typography, useMediaQuery, useTheme } from '@mui/material';
|
|
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
|
|
|
|
// project import
|
|
import MainCard from 'components/MainCard';
|
|
import axios from 'axios';
|
|
import Loader from 'components/Loader';
|
|
import { enqueueSnackbar } from 'notistack';
|
|
import CircularLoader from 'components/CircularLoader';
|
|
import { fetchAppLocations } from 'pages/api/api';
|
|
|
|
const AVAILABILITY_OPTIONS = ['Available', 'Assigned', 'On_Break', 'Offline'];
|
|
|
|
const EditRider = () => {
|
|
const theme = useTheme();
|
|
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
|
|
const location = useLocation();
|
|
const [riderdata, setRiderdata] = useState(null);
|
|
const navigate = useNavigate();
|
|
const [hubs, setHubs] = useState([]);
|
|
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
const fetchRiderData = async (id) => {
|
|
try {
|
|
// Doormile /admin/milers/:id returns { userid, displayname, phone,
|
|
// availabilitystatus, rating, hubid, currentlat, currentlon,
|
|
// completedorders, cancelledorders } — this form only edits the
|
|
// fields Doormile's PATCH endpoint actually accepts.
|
|
const riderdataresponse = await axios.get(`${process.env.REACT_APP_URL}/admin/milers/${id}`);
|
|
setRiderdata(riderdataresponse.data.data);
|
|
} catch (error) {
|
|
console.log('fetchRiderData', error);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchRiderData(location.state.riderdata.userid);
|
|
fetchAppLocations().then((locations) => {
|
|
setHubs((locations || []).filter((l) => l.applocationid !== 0));
|
|
});
|
|
}, []);
|
|
|
|
const updateRider = async () => {
|
|
setLoading(true);
|
|
try {
|
|
const response = await axios.patch(`${process.env.REACT_APP_URL}/admin/milers/${riderdata.userid}`, {
|
|
displayname: riderdata.displayname,
|
|
phone: riderdata.phone,
|
|
availabilitystatus: riderdata.availabilitystatus,
|
|
hubid: riderdata.hubid
|
|
});
|
|
if (response.status == 200) {
|
|
enqueueSnackbar('Updated Successfully', {
|
|
variant: 'success',
|
|
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
});
|
|
navigate('/nearle/riders');
|
|
} else {
|
|
enqueueSnackbar('Update Failed', {
|
|
variant: 'error',
|
|
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
});
|
|
}
|
|
} catch (err) {
|
|
enqueueSnackbar(err.response?.data?.message || err.message || 'Update Failed', {
|
|
variant: 'error',
|
|
anchorOrigin: { vertical: 'top', horizontal: 'right' },
|
|
autoHideDuration: 2000
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<>
|
|
{loading && (
|
|
<>
|
|
<Loader />
|
|
<CircularLoader />
|
|
</>
|
|
)}
|
|
<MainCard
|
|
title={
|
|
<Stack
|
|
direction={{ xs: 'column', sm: 'row' }}
|
|
justifyContent="space-between"
|
|
alignItems={{ xs: 'stretch', sm: 'center' }}
|
|
spacing={{ xs: 1.5, sm: 0 }}
|
|
sx={{ backgroundColor: 'secondary.lighter', width: '100%', height: '100%', p: 2 }}
|
|
>
|
|
<Typography variant="h3">Edit Miler</Typography>
|
|
<Button startIcon={<ArrowBackIcon />} variant="outlined" fullWidth={isMobile} onClick={() => navigate('/nearle/riders')}>
|
|
Back to Milers
|
|
</Button>
|
|
</Stack>
|
|
}
|
|
>
|
|
<Box sx={{ p: { xs: 1.5, sm: 3 } }}>
|
|
<Grid container spacing={{ xs: 2, sm: 3 }}>
|
|
<Grid item xs={12} sm={6}>
|
|
<Stack spacing={1.25}>
|
|
<InputLabel htmlFor="miler-name">Name</InputLabel>
|
|
<TextField
|
|
fullWidth
|
|
id="miler-name"
|
|
value={riderdata?.displayname || ''}
|
|
onChange={(e) => setRiderdata({ ...riderdata, displayname: e.target.value })}
|
|
placeholder="Name"
|
|
autoComplete="off"
|
|
/>
|
|
</Stack>
|
|
</Grid>
|
|
<Grid item xs={12} sm={6}>
|
|
<Stack spacing={1.25}>
|
|
<InputLabel htmlFor="miler-phone">Phone Number</InputLabel>
|
|
<Stack direction="row" justifyContent="space-between" alignItems="center" spacing={2}>
|
|
<Select defaultValue="+91" disabled sx={{ cursor: 'not-allowed' }}>
|
|
<MenuItem value="+91">+91</MenuItem>
|
|
</Select>
|
|
<TextField
|
|
type="tel"
|
|
id="miler-phone"
|
|
fullWidth
|
|
placeholder="Phone Number"
|
|
onChange={(e) => {
|
|
if (e.target.value.toString().length <= 10) {
|
|
setRiderdata({ ...riderdata, phone: e.target.value });
|
|
}
|
|
}}
|
|
value={riderdata?.phone || ''}
|
|
autoComplete="off"
|
|
/>
|
|
</Stack>
|
|
</Stack>
|
|
</Grid>
|
|
<Grid item xs={12} sm={6}>
|
|
<Stack spacing={1.25}>
|
|
<InputLabel htmlFor="miler-hub">Hub</InputLabel>
|
|
<Select
|
|
id="miler-hub"
|
|
fullWidth
|
|
displayEmpty
|
|
value={riderdata?.hubid ?? ''}
|
|
onChange={(e) => setRiderdata({ ...riderdata, hubid: e.target.value })}
|
|
renderValue={(selected) => {
|
|
if (!selected) return <em>Select hub</em>;
|
|
const hub = hubs.find((h) => h.applocationid === selected);
|
|
return hub?.locationname || selected;
|
|
}}
|
|
>
|
|
{hubs.map((h) => (
|
|
<MenuItem key={h.applocationid} value={h.applocationid}>
|
|
{h.locationname}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</Stack>
|
|
</Grid>
|
|
<Grid item xs={12} sm={6}>
|
|
<Stack spacing={1.25}>
|
|
<InputLabel htmlFor="miler-status">Availability Status</InputLabel>
|
|
<Select
|
|
id="miler-status"
|
|
fullWidth
|
|
value={riderdata?.availabilitystatus || ''}
|
|
onChange={(e) => setRiderdata({ ...riderdata, availabilitystatus: e.target.value })}
|
|
>
|
|
{AVAILABILITY_OPTIONS.map((status) => (
|
|
<MenuItem key={status} value={status}>
|
|
{status.replace('_', ' ')}
|
|
</MenuItem>
|
|
))}
|
|
</Select>
|
|
</Stack>
|
|
</Grid>
|
|
</Grid>
|
|
</Box>
|
|
</MainCard>
|
|
<Box
|
|
sx={{
|
|
position: 'sticky',
|
|
bottom: 0,
|
|
backgroundColor: 'secondary.lighter',
|
|
p: 2,
|
|
zIndex: 10,
|
|
border: '1px solid',
|
|
borderColor: '#E6EBF1',
|
|
borderTop: 'none'
|
|
}}
|
|
>
|
|
<Stack direction={{ xs: 'column-reverse', sm: 'row' }} justifyContent="flex-end" spacing={2}>
|
|
<Button startIcon={<ArrowBackIcon />} variant="outlined" fullWidth={isMobile} onClick={() => navigate('/nearle/riders')}>
|
|
Back to Milers
|
|
</Button>
|
|
<Button variant="contained" fullWidth={isMobile} onClick={() => updateRider()}>
|
|
Update
|
|
</Button>
|
|
</Stack>
|
|
</Box>
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default EditRider;
|