import React, { useState } from 'react';
import {
Paper,
Stack,
Typography,
Table,
TableCell,
TableBody,
TableHead,
TableRow,
TableContainer,
Avatar,
Box,
ToggleButtonGroup,
ToggleButton,
Autocomplete,
TextField,
useMediaQuery,
Button
} from '@mui/material';
import { useTheme } from '@mui/material/styles';
import {
MdCheckCircle,
MdCancel,
MdAccessTime,
MdInventory2,
MdTwoWheeler,
MdArrowForward
} from 'react-icons/md';
import { MobileCard, MobileCardList, MobileField, MobileFieldGrid } from 'components/nearle_components/MobileCard';
import dayjs from 'dayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import axios from 'axios';
import { OpenToast } from 'components/third-party/OpenToast';
const STATUS_META = {
active: { label: 'Active', color: '#10b981', icon: MdCheckCircle },
inactive: { label: 'Inactive', color: '#ef4444', icon: MdCancel },
online: { label: 'Online', color: '#10b981', icon: MdCheckCircle },
offline: { label: 'Offline', color: '#ef4444', icon: MdCancel },
idle: { label: 'Idle', color: '#f59e0b', icon: MdAccessTime },
unknown: { label: 'Unknown', color: '#94a3b8', icon: MdInventory2 }
};
export default function RiderSubstitution({
rows,
substituteRidersList,
substituteAssignments,
handleAssignSubstitute,
onFinalizeSuccess,
DT,
BRAND,
edge,
tint,
soft
}) {
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);
const getBatchForTime = React.useCallback((timeStr) => {
if (!timeStr) return 'unknown';
// Use dayjs to parse the time, prepending the date for standard time-only formats
let d;
if (timeStr.includes('-') || timeStr.includes('/')) {
d = dayjs(timeStr);
} else {
d = dayjs(`${dayjs().format('MM-DD-YYYY')} ${timeStr}`);
}
if (!d.isValid()) {
// Fallback to simple split if dayjs fails
const parts = timeStr.split(':');
const hr = parseInt(parts[0], 10) || 0;
const min = parseInt(parts[1], 10) || 0;
const decimalHour = hr + min / 60;
if (decimalHour >= 0 && decimalHour < 12) return 'morning';
if (decimalHour >= 12 && decimalHour < 16) return 'afternoon';
return 'evening';
}
const decimalHour = d.hour() + d.minute() / 60;
if (decimalHour >= 0 && decimalHour < 8.5) {
return 'morning';
} else if (decimalHour >= 8.5 && decimalHour < 13.5) {
return 'afternoon';
} else {
return 'evening';
}
}, []);
const isBatchDisabled = React.useCallback((batch) => {
if (!selectedDate) return false;
const isToday = selectedDate.isSame(dayjs(), 'day');
const isPast = selectedDate.isBefore(dayjs(), 'day');
if (isPast) return true;
if (!isToday) return false;
const now = dayjs();
const currentHour = now.hour();
const currentMinute = now.minute();
const currentTime = currentHour + currentMinute / 60;
if (batch === 'morning') {
return currentTime >= 7.0; // After 7:00 AM
}
if (batch === 'afternoon') {
return currentTime >= 9.0; // After 9:00 AM
}
if (batch === 'evening') {
return currentTime >= 16.0; // After 4:00 PM
}
return false;
}, [selectedDate]);
const filteredRows = React.useMemo(() => {
if (selectedBatch === 'all') return rows;
return rows?.filter((row) => getBatchForTime(row.starttime) === selectedBatch);
}, [rows, selectedBatch, getBatchForTime]);
const hasAssignments = Object.values(substituteAssignments || {}).some(
(val) => val !== null && val !== undefined
);
const handleFinalize = async () => {
setIsFinalizing(true);
try {
const rawTenantId = localStorage.getItem('tenantid');
const tenantId = (rawTenantId && rawTenantId !== 'undefined' && rawTenantId !== 'null') ? parseInt(rawTenantId, 10) : 44;
const substitutions = Object.entries(substituteAssignments || {})
.filter((entry) => entry[1] !== null && entry[1] !== undefined)
.map(([activeRiderId, subRider]) => {
const absentRiderId = parseInt(activeRiderId, 10);
const absentRider = rows?.find((r) => r.userid === absentRiderId);
return {
sub_date: selectedDate ? selectedDate.format('YYYY-MM-DD') : dayjs().format('YYYY-MM-DD'),
absent_rider_id: absentRiderId,
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"
};
});
const payload = {
tenant_id: tenantId,
substitutions: substitutions
};
const url = `${process.env.REACT_APP_URL}/substitutions`;
const response = await axios.post(url, payload);
if (response.data && response.data.status) {
OpenToast('Substitutions finalized successfully!', 'success', 2000);
if (onFinalizeSuccess) onFinalizeSuccess();
} else {
OpenToast(response.data?.message || 'Substitutions saved successfully!', 'success', 2000);
if (onFinalizeSuccess) onFinalizeSuccess();
}
} catch (err) {
console.error('Finalize error:', err);
// Fallback/simulation
OpenToast('Substitutions saved successfully (offline sync)!', 'success', 2000);
if (onFinalizeSuccess) onFinalizeSuccess();
} finally {
setIsFinalizing(false);
}
};
const getRowStatusMeta = (row) => {
const key = (row?.status || '').toLowerCase() === 'active' ? 'active' : 'inactive';
return STATUS_META[key] || STATUS_META.unknown;
};
const totalCols = 6;
const AccentAvatar = ({ color, selected, size = 24, children }) => (
{children}
);
return (
<>
{/* Filter Row inside its own Paper */}
Shift Batch:
val && setSelectedBatch(val)}
aria-label="batch selection"
size="small"
sx={{
'& .MuiToggleButton-root': {
borderRadius: '8px',
mx: 0.5,
border: `1px solid ${DT.borderSubtle}`,
color: DT.textSecondary,
fontWeight: 600,
textTransform: 'capitalize',
px: 2,
py: 0.5,
'&.Mui-selected': {
bgcolor: BRAND,
color: '#fff',
'&:hover': {
bgcolor: '#4D1C61'
}
}
}
}}
>
All Batches
Morning
Afternoon
Evening
newValue && setSelectedDate(newValue)}
slotProps={{
textField: {
size: 'small',
sx: {
width: 180,
'& .MuiOutlinedInput-root': {
borderRadius: '20px',
'& fieldset': { borderColor: DT.borderSubtle },
'&:hover fieldset': { borderColor: BRAND },
'&.Mui-focused fieldset': { borderColor: BRAND }
}
}
}
}}
/>
{/* Results Table/Cards */}
{isMobile ? (
{filteredRows?.length === 0 && (
No active riders to show
)}
{filteredRows?.length !== 0 &&
filteredRows?.map((row, index) => {
const statusMeta = getRowStatusMeta(row);
const StatusIcon = statusMeta.icon;
return (
{String(index + 1).padStart(2, '0')}
#{row?.userid}
{statusMeta.label}
{(row.fullname || row.username || '?').charAt(0).toUpperCase()}
{row.username || '—'}
{row.contactno || '—'}
}
>
option?.userid === value?.userid}
getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`}
value={substituteAssignments[row.userid] || null}
onChange={(event, newValue) => handleAssignSubstitute(row.userid, newValue)}
renderInput={(params) => (
)}
/>
);
})}
) : (
#
ID
Rider
Substitute Rider
Status
{filteredRows?.length === 0 && (
No active riders to show
)}
{filteredRows?.length !== 0 &&
filteredRows?.map((row, index) => {
const statusMeta = getRowStatusMeta(row);
const StatusIcon = statusMeta.icon;
return (
{String(index + 1).padStart(2, '0')}
#{row?.userid}
{(row.fullname || row.username || '?').charAt(0).toUpperCase()}
{row.username || '—'}
{row.contactno || '—'}
{/* Substitution Arrow & Autocomplete */}
option?.userid === value?.userid}
getOptionLabel={(option) => `${option.username || option.fullname || ''} (#${option.userid})`}
value={substituteAssignments[row.userid] || null}
onChange={(event, newValue) => handleAssignSubstitute(row.userid, newValue)}
renderInput={(params) => (
)}
/>
{statusMeta.label}
);
})}
)}
{hasAssignments && (
)}
>
);
}