updates on the rider and the rider substitution page regarding the crud operations and more change outthere

This commit is contained in:
2026-06-22 12:17:07 +05:30
parent 4823df08a3
commit 94ad8ab262
2 changed files with 287 additions and 52 deletions

View File

@@ -45,11 +45,15 @@ const STATUS_META = {
};
export default function RiderSubstitution({
appId,
rows,
allRidersList,
substituteRidersList,
substituteAssignments,
handleAssignSubstitute,
onFinalizeSuccess,
selectedDate,
setSelectedDate,
DT,
BRAND,
edge,
@@ -58,7 +62,6 @@ export default function RiderSubstitution({
}) {
const theme = useTheme();
const isMobile = useMediaQuery(theme.breakpoints.down('md'));
const [selectedDate, setSelectedDate] = useState(dayjs());
const [selectedBatch, setSelectedBatch] = useState('all');
const [isFinalizing, setIsFinalizing] = useState(false);
@@ -118,9 +121,8 @@ export default function RiderSubstitution({
}, [selectedDate]);
const filteredRows = React.useMemo(() => {
if (selectedBatch === 'all') return rows;
return rows?.filter((row) => getBatchForTime(row.starttime) === selectedBatch);
}, [rows, selectedBatch, getBatchForTime]);
return rows;
}, [rows]);
const hasAssignments = Object.values(substituteAssignments || {}).some(
(val) => val !== null && val !== undefined
@@ -129,8 +131,37 @@ export default function RiderSubstitution({
const handleFinalize = async () => {
setIsFinalizing(true);
try {
const rawTenantId = localStorage.getItem('tenantid');
const tenantId = (rawTenantId && rawTenantId !== 'undefined' && rawTenantId !== 'null') ? parseInt(rawTenantId, 10) : 44;
let partnerId = '';
try {
const rawLocs = localStorage.getItem('applocations');
if (rawLocs) {
const locs = JSON.parse(rawLocs);
const currentLoc = locs.find((l) => l.applocationid === appId);
if (currentLoc && currentLoc.partnerid) {
partnerId = currentLoc.partnerid;
} else {
const firstValidLoc = locs.find((l) => l.partnerid);
if (firstValidLoc && firstValidLoc.partnerid) {
partnerId = firstValidLoc.partnerid;
}
}
}
} catch (e) {
console.error('Error parsing applocations', e);
}
if (!partnerId) {
const savedPartnerId = localStorage.getItem('partnerid');
if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') {
partnerId = savedPartnerId;
}
}
if (!partnerId || partnerId === '0' || partnerId === 0) {
partnerId = 44;
}
const tenantId = parseInt(partnerId, 10);
const substitutions = Object.entries(substituteAssignments || {})
.filter((entry) => entry[1] !== null && entry[1] !== undefined)
@@ -143,7 +174,8 @@ export default function RiderSubstitution({
absent_rider_name: absentRider?.username || absentRider?.fullname || `Rider #${absentRiderId}`,
sub_rider_id: parseInt(subRider.userid, 10),
sub_rider_name: subRider.username || subRider.fullname || `Rider #${subRider.userid}`,
reason: "Scheduled"
reason: "Scheduled",
batch: selectedBatch
};
});
@@ -315,6 +347,8 @@ export default function RiderSubstitution({
filteredRows?.map((row, index) => {
const statusMeta = getRowStatusMeta(row);
const StatusIcon = statusMeta.icon;
const riderProfile = allRidersList?.find((r) => r.userid === row.userid);
const starttime = riderProfile?.starttime || row.starttime;
return (
<MobileCard
key={row.userid ?? index}
@@ -398,7 +432,7 @@ export default function RiderSubstitution({
<MobileField label="Substitute" full>
<Autocomplete
size="small"
disabled={isBatchDisabled(getBatchForTime(row.starttime))}
disabled={isBatchDisabled(getBatchForTime(starttime))}
options={substituteRidersList || []}
isOptionEqualToValue={(option, value) => option?.userid === value?.userid}
getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`}
@@ -471,6 +505,8 @@ export default function RiderSubstitution({
filteredRows?.map((row, index) => {
const statusMeta = getRowStatusMeta(row);
const StatusIcon = statusMeta.icon;
const riderProfile = allRidersList?.find((r) => r.userid === row.userid);
const starttime = riderProfile?.starttime || row.starttime;
return (
<TableRow
key={row.userid ?? index}
@@ -545,7 +581,7 @@ export default function RiderSubstitution({
<TableCell sx={{ minWidth: 220 }}>
<Autocomplete
size="small"
disabled={isBatchDisabled(getBatchForTime(row.starttime))}
disabled={isBatchDisabled(getBatchForTime(starttime))}
options={substituteRidersList || []}
isOptionEqualToValue={(option, value) => option?.userid === value?.userid}
getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`}

View File

@@ -23,7 +23,14 @@ import {
Skeleton,
useMediaQuery,
ToggleButtonGroup,
ToggleButton
ToggleButton,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Autocomplete,
TextField
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
var utc = require('dayjs/plugin/utc');
@@ -51,7 +58,8 @@ import {
MdAccessTime,
MdInventory2,
MdTwoWheeler,
MdArrowForward
MdArrowForward,
MdDelete
} from 'react-icons/md';
import LocationAutocomplete from 'components/nearle_components/LocationAutocomplete';
import PageHeader from 'components/nearle_components/PageHeader';
@@ -164,9 +172,101 @@ const Riders = () => {
const totalCols = roleid == 1 ? 11 : 10;
const [logsRow, setLogsRow] = useState(null);
const [riderLogsdata, setRiderLogsdata] = useState(null);
const [historyFromDate, setHistoryFromDate] = useState(dayjs().startOf('month'));
const [historyToDate, setHistoryToDate] = useState(dayjs().endOf('month'));
const [historyDate, setHistoryDate] = useState(dayjs());
const [historyStatus, setHistoryStatus] = useState('all');
const [selectedDate, setSelectedDate] = useState(dayjs());
const [editingSub, setEditingSub] = useState(null);
const [newSubRider, setNewSubRider] = useState(null);
const getResolvedPartnerId = () => {
let partnerId = '';
try {
const rawLocs = localStorage.getItem('applocations');
if (rawLocs) {
const locs = JSON.parse(rawLocs);
const currentLoc = locs.find((l) => l.applocationid === appId);
if (currentLoc && currentLoc.partnerid) {
partnerId = currentLoc.partnerid;
} else {
const firstValidLoc = locs.find((l) => l.partnerid);
if (firstValidLoc && firstValidLoc.partnerid) {
partnerId = firstValidLoc.partnerid;
}
}
}
} catch (e) {
console.error('Error parsing applocations', e);
}
if (!partnerId) {
const savedPartnerId = localStorage.getItem('partnerid');
if (savedPartnerId && savedPartnerId !== 'undefined' && savedPartnerId !== 'null') {
partnerId = savedPartnerId;
}
}
if (!partnerId || partnerId === '0' || partnerId === 0) {
partnerId = 44;
}
return partnerId;
};
const getHistoryStatusColor = (status) => {
const s = (status || '').toLowerCase();
if (s === 'cancelled' || s === 'deleted' || s === 'inactive') return '#ef4444'; // Red
if (s === 'scheduled') return '#0ea5e9'; // Blue
if (s === 'active' || s === 'completed') return '#10b981'; // Green
return '#64748b'; // Gray
};
const handleDeleteSubstitution = async (row) => {
if (window.confirm(`Are you sure you want to delete the substitution for ${row.absent_rider_name}?`)) {
try {
const resolvedTenantId = row.tenant_id || row.tenantid || getResolvedPartnerId();
const url = `${process.env.REACT_APP_URL}/substitutions/${row.id}?tenant_id=${resolvedTenantId}`;
const res = await axios.delete(url);
if (res.data && res.data.status) {
OpenToast('Substitution deleted successfully!', 'success', 2000);
} else {
OpenToast(res.data?.message || 'Failed to delete substitution.', 'error', 2000);
}
} catch (err) {
console.error(err);
OpenToast(err.response?.data?.message || err.message || 'Failed to delete substitution.', 'error', 2000);
} finally {
queryClient.invalidateQueries({ queryKey: ['substitutionsHistory'] });
}
}
};
const handleUpdateSubstitutionSubmit = async () => {
if (!newSubRider) {
OpenToast('Please select a substitute rider', 'error', 2000);
return;
}
try {
const resolvedTenantId = editingSub.tenant_id || editingSub.tenantid || getResolvedPartnerId();
const url = `${process.env.REACT_APP_URL}/substitutions/${editingSub.id}`;
const payload = {
tenant_id: parseInt(resolvedTenantId, 10),
sub_rider_id: parseInt(newSubRider.userid, 10),
sub_rider_name: newSubRider.username || newSubRider.fullname || `Rider #${newSubRider.userid}`
};
const res = await axios.put(url, payload);
if (res.data && res.data.status) {
OpenToast('Substitution updated successfully!', 'success', 2000);
} else {
OpenToast(res.data?.message || 'Failed to update substitution.', 'error', 2000);
}
} catch (err) {
console.error(err);
OpenToast(err.response?.data?.message || err.message || 'Failed to update substitution.', 'error', 2000);
} finally {
setEditingSub(null);
setNewSubRider(null);
queryClient.invalidateQueries({ queryKey: ['substitutionsHistory'] });
}
};
const [substituteAssignments, setSubstituteAssignments] = useState(() => {
try {
@@ -229,15 +329,18 @@ const Riders = () => {
}
}
if (!partnerId) {
if (!partnerId || partnerId === '0' || partnerId === 0) {
partnerId = 44; // final fallback
}
const url = `${process.env.REACT_APP_URL}/partners/getallriders/?applocationid=${appId}&partnerid=${partnerId}`;
const url = `${process.env.REACT_APP_URL}/partners/getallriders/?applocationid=0&partnerid=${partnerId}&pagesize=1000&pageno=1&status=`;
const res = await axios.get(url);
const allRiders = res.data.details || [];
console.log('Substitute query - all riders returned from API:', allRiders.map(r => ({ userid: r.userid, username: r.username, partnerid: r.partnerid })));
const allowedIds = [1121, 772, 1116];
return allRiders.filter((rider) => allowedIds.includes(parseInt(rider.userid)));
const filtered = allRiders.filter((rider) => allowedIds.includes(parseInt(rider.userid)));
console.log('Substitute query - filtered allowed riders:', filtered);
return filtered;
} catch (err) {
console.error(err);
return [];
@@ -246,10 +349,10 @@ const Riders = () => {
});
const { data: subRidersLogsData, isLoading: subRidersLogsLoading } = useQuery({
queryKey: ['subRidersLogs', appId, debouncedSearch],
queryKey: ['subRidersLogs', appId, debouncedSearch, selectedDate.format('YYYY-MM-DD')],
queryFn: async () => {
try {
const todayStr = dayjs().format('YYYY-MM-DD');
const dateStr = selectedDate.format('YYYY-MM-DD');
// Resolve partner ID dynamically
let partnerId = '';
@@ -278,11 +381,11 @@ const Riders = () => {
}
}
if (!partnerId) {
if (!partnerId || partnerId === '0' || partnerId === 0) {
partnerId = 44;
}
const url = `${process.env.REACT_APP_URL2}/partners/getriderlogs/?applocationid=${appId}&partnerid=${partnerId}&fromdate=${todayStr}&todate=${todayStr}&keyword=${debouncedSearch}`;
const url = `${process.env.REACT_APP_URL2}/partners/getriderlogs/?applocationid=${appId}&partnerid=${partnerId}&fromdate=${dateStr}&todate=${dateStr}&keyword=${debouncedSearch}`;
const res = await axios.get(url);
return res.data.details || [];
} catch (err) {
@@ -293,7 +396,7 @@ const Riders = () => {
});
const { data: subHistoryData, isLoading: subHistoryLoading } = useQuery({
queryKey: ['substitutionsHistory', appId, historyFromDate, historyToDate, historyStatus],
queryKey: ['substitutionsHistory', appId, historyDate, historyStatus],
queryFn: async () => {
try {
let partnerId = '';
@@ -322,11 +425,12 @@ const Riders = () => {
}
}
if (!partnerId) {
if (!partnerId || partnerId === '0' || partnerId === 0) {
partnerId = 44;
}
let url = `${process.env.REACT_APP_URL}/substitutions?tenant_id=${partnerId}&from_date=${historyFromDate.format('YYYY-MM-DD')}&to_date=${historyToDate.format('YYYY-MM-DD')}`;
const dateStr = historyDate.format('YYYY-MM-DD');
let url = `${process.env.REACT_APP_URL}/substitutions?tenant_id=${partnerId}&from_date=${dateStr}&to_date=${dateStr}`;
if (historyStatus !== 'all') {
url += `&status=${historyStatus}`;
}
@@ -672,33 +776,14 @@ const Riders = () => {
<Stack direction="row" spacing={1.5} alignItems="center">
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
label="From Date"
value={historyFromDate}
onChange={(newValue) => newValue && setHistoryFromDate(newValue)}
label="Select Date"
value={historyDate}
onChange={(newValue) => newValue && setHistoryDate(newValue)}
slotProps={{
textField: {
size: 'small',
sx: {
width: 150,
'& .MuiOutlinedInput-root': {
borderRadius: '20px',
'& fieldset': { borderColor: DT.borderSubtle },
'&:hover fieldset': { borderColor: BRAND },
'&.Mui-focused fieldset': { borderColor: BRAND }
}
}
}
}}
/>
<DatePicker
label="To Date"
value={historyToDate}
onChange={(newValue) => newValue && setHistoryToDate(newValue)}
slotProps={{
textField: {
size: 'small',
sx: {
width: 150,
width: 180,
'& .MuiOutlinedInput-root': {
borderRadius: '20px',
'& fieldset': { borderColor: DT.borderSubtle },
@@ -757,9 +842,9 @@ const Riders = () => {
px: 1,
py: 0.25,
borderRadius: 999,
bgcolor: tint('#10b981'),
border: `1px solid ${edge('#10b981')}`,
color: '#10b981',
bgcolor: tint(getHistoryStatusColor(row.status)),
border: `1px solid ${edge(getHistoryStatusColor(row.status))}`,
color: getHistoryStatusColor(row.status),
fontWeight: 800,
fontSize: 10
}}
@@ -841,6 +926,7 @@ const Riders = () => {
<TableCell>Substitute Rider</TableCell>
<TableCell>Reason</TableCell>
<TableCell align="center">Status</TableCell>
<TableCell align="right">Action</TableCell>
</TableRow>
</TableHead>
<TableBody>
@@ -908,9 +994,9 @@ const Riders = () => {
px: 1.5,
py: 0.5,
borderRadius: 999,
bgcolor: tint('#10b981'),
border: `1px solid ${edge('#10b981')}`,
color: '#10b981',
bgcolor: tint(getHistoryStatusColor(row.status)),
border: `1px solid ${edge(getHistoryStatusColor(row.status))}`,
color: getHistoryStatusColor(row.status),
fontWeight: 800,
fontSize: 11,
textTransform: 'capitalize'
@@ -919,6 +1005,43 @@ const Riders = () => {
{row.status || 'scheduled'}
</Box>
</TableCell>
<TableCell align="right">
<Stack direction="row" spacing={0.75} justifyContent="flex-end">
<Tooltip title="Update substitution" placement="top">
<IconButton
size="small"
sx={{
bgcolor: soft(BRAND),
color: BRAND,
border: `1px solid ${edge(BRAND)}`,
'&:hover': { bgcolor: BRAND, color: '#fff' }
}}
onClick={() => {
setEditingSub(row);
setNewSubRider(null);
}}
>
<MdEdit size={14} />
</IconButton>
</Tooltip>
<Tooltip title="Delete substitution" placement="top">
<IconButton
size="small"
sx={{
bgcolor: soft('#ef4444'),
color: '#ef4444',
border: `1px solid ${edge('#ef4444')}`,
'&:hover': { bgcolor: '#ef4444', color: '#fff' }
}}
onClick={() => {
handleDeleteSubstitution(row);
}}
>
<MdDelete size={14} />
</IconButton>
</Tooltip>
</Stack>
</TableCell>
</TableRow>
))}
</TableBody>
@@ -930,10 +1053,13 @@ const Riders = () => {
<RiderSubstitution
appId={appId}
rows={subRidersLogsData || []}
allRidersList={rows}
substituteRidersList={substituteRidersList}
substituteAssignments={substituteAssignments}
handleAssignSubstitute={handleAssignSubstitute}
onFinalizeSuccess={handleFinalizeSuccess}
selectedDate={selectedDate}
setSelectedDate={setSelectedDate}
DT={DT}
BRAND={BRAND}
edge={edge}
@@ -1670,6 +1796,79 @@ const Riders = () => {
</TableContainer>
</Paper>
)}
<Dialog
open={Boolean(editingSub)}
onClose={() => setEditingSub(null)}
maxWidth="xs"
fullWidth
PaperProps={{
sx: {
borderRadius: `${DT.radiusCard / 8}px`,
p: 1
}
}}
>
<DialogTitle sx={{ fontWeight: 700, pb: 1 }}>Update Substitution</DialogTitle>
<DialogContent>
<Stack spacing={2} sx={{ mt: 1 }}>
<Typography variant="body2" sx={{ color: DT.textSecondary }}>
Choose a new substitute rider for <strong>{editingSub?.absent_rider_name}</strong>.
</Typography>
<Autocomplete
size="small"
options={substituteRidersList || []}
isOptionEqualToValue={(option, value) => option?.userid === value?.userid}
getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`}
value={newSubRider}
onChange={(event, newValue) => setNewSubRider(newValue)}
renderInput={(params) => (
<TextField
{...params}
placeholder="Select Substitute Rider"
sx={{
'& .MuiOutlinedInput-root': {
borderRadius: '8px',
bgcolor: '#f8fafc',
'& fieldset': { borderColor: DT.borderSubtle }
}
}}
/>
)}
/>
</Stack>
</DialogContent>
<DialogActions sx={{ px: 3, pb: 2 }}>
<Button
variant="outlined"
onClick={() => setEditingSub(null)}
sx={{
borderRadius: '8px',
textTransform: 'none',
fontWeight: 600,
borderColor: DT.borderSubtle,
color: DT.textSecondary
}}
>
Cancel
</Button>
<Button
variant="contained"
onClick={handleUpdateSubstitutionSubmit}
disabled={!newSubRider}
sx={{
borderRadius: '8px',
textTransform: 'none',
fontWeight: 600,
bgcolor: BRAND,
color: '#fff',
'&:hover': { bgcolor: '#4D1C61' }
}}
>
Save Changes
</Button>
</DialogActions>
</Dialog>
</>
);
};