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({ appId, rows, allRidersList, substituteRidersList, substituteAssignments, handleAssignSubstitute, onFinalizeSuccess, selectedDate, setSelectedDate, DT, BRAND, edge, tint, soft }) { const theme = useTheme(); const isMobile = useMediaQuery(theme.breakpoints.down('md')); 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(() => { return rows; }, [rows]); const hasAssignments = Object.values(substituteAssignments || {}).some( (val) => val !== null && val !== undefined ); const handleFinalize = async () => { setIsFinalizing(true); try { 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) .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", batch: selectedBatch }; }); 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; const riderProfile = allRidersList?.find((r) => r.userid === row.userid); const starttime = riderProfile?.starttime || row.starttime; 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; const riderProfile = allRidersList?.find((r) => r.userid === row.userid); const starttime = riderProfile?.starttime || row.starttime; 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 && ( )}
); }