diff --git a/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx b/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx index ee7fba7..5c12aad 100644 --- a/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx +++ b/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx @@ -203,87 +203,91 @@ const TruckScheduleDashboard: React.FC = () => { return '-'; }; - // Calculate time remaining for truck departure - const calculateTimeRemaining = useCallback((item: TruckScheduleDashboardItem, dateOption: string): string => { - // If all tickets are completed, return the difference between ETD and last ticket end time - if (item.numberOfPickTickets > 0 && item.numberOfTicketsCompleted >= item.numberOfPickTickets) { - const lastTicketEndTime = item.lastTicketEndTime; - const departureTime = item.truckDepartureTime; - - if (!lastTicketEndTime || !departureTime) return '-'; - - // Parse last ticket end time - let lastEndDayjs: dayjs.Dayjs; - if (Array.isArray(lastTicketEndTime)) { - lastEndDayjs = arrayToDayjs(lastTicketEndTime, true); - } else if (typeof lastTicketEndTime === 'string') { - lastEndDayjs = dayjs(lastTicketEndTime); - if (!lastEndDayjs.isValid()) return '-'; + // Compare a timestamp against the selected date (today/tomorrow/...) and return a short label. + // Used to avoid confusion when picking crosses midnight. + const relativeDayLabel = useCallback( + (dateTimeData: string | number[] | null, dateOption: string): string | null => { + if (!dateTimeData) return null; + const dateOffset = getDateOffset(dateOption); + const baseDate = dayjs().startOf('day').add(dateOffset, 'day'); + + let dt: dayjs.Dayjs; + if (Array.isArray(dateTimeData)) { + dt = arrayToDayjs(dateTimeData, true); } else { - return '-'; + dt = dayjs(dateTimeData); } - - // Parse departure time + if (!dt.isValid()) return null; + + const diffDays = dt.startOf('day').diff(baseDate, 'day'); + if (diffDays === 0) return null; + if (diffDays === -1) return '昨日'; + if (diffDays === 1) return '明日'; + if (diffDays <= -2) return `${Math.abs(diffDays)}日前`; + return `${diffDays}日後`; + }, + [], + ); + + const getDepartureEtdDayjs = useCallback( + (departureTime: string | number[] | null, dateOption: string): dayjs.Dayjs | null => { + if (!departureTime) return null; const dateOffset = getDateOffset(dateOption); - const baseDate = dayjs().add(dateOffset, 'day'); - let departureDayjs: dayjs.Dayjs; - + const baseDate = dayjs().startOf('day').add(dateOffset, 'day'); + + let hour: number; + let minute: number; + if (Array.isArray(departureTime)) { - if (departureTime.length < 2) return '-'; - const hour = departureTime[0] || 0; - const minute = departureTime[1] || 0; - departureDayjs = baseDate.hour(hour).minute(minute).second(0); + if (departureTime.length < 2) return null; + hour = departureTime[0] || 0; + minute = departureTime[1] || 0; } else if (typeof departureTime === 'string') { const parts = departureTime.split(':'); - if (parts.length < 2) return '-'; - const hour = parseInt(parts[0], 10); - const minute = parseInt(parts[1], 10); - departureDayjs = baseDate.hour(hour).minute(minute).second(0); + if (parts.length < 2) return null; + hour = parseInt(parts[0], 10); + minute = parseInt(parts[1], 10); } else { - return '-'; + return null; } - - // Calculate difference: ETD - lastTicketEndTime - const diffMinutes = departureDayjs.diff(lastEndDayjs, 'minute'); - - if (diffMinutes < 0) { - // ETD is before last ticket end (negative difference) - const absDiff = Math.abs(diffMinutes); - const hours = Math.floor(absDiff / 60); - const minutes = absDiff % 60; - return `-${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; - } else { - // ETD is after last ticket end (positive difference) - const hours = Math.floor(diffMinutes / 60); - const minutes = diffMinutes % 60; - return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; - } - } - - const departureTime = item.truckDepartureTime; - if (!departureTime || !currentTime) return '-'; - - const now = currentTime; - let departureHour: number; - let departureMinute: number; - - if (Array.isArray(departureTime)) { - if (departureTime.length < 2) return '-'; - departureHour = departureTime[0] || 0; - departureMinute = departureTime[1] || 0; - } else if (typeof departureTime === 'string') { - const parts = departureTime.split(':'); - if (parts.length < 2) return '-'; - departureHour = parseInt(parts[0], 10); - departureMinute = parseInt(parts[1], 10); - } else { - return '-'; + + return baseDate.hour(hour).minute(minute).second(0); + }, + [], + ); + + const getLastEndDayjsOrNull = useCallback((lastTicketEndTime: string | number[] | null): dayjs.Dayjs | null => { + if (!lastTicketEndTime) return null; + if (Array.isArray(lastTicketEndTime)) { + return arrayToDayjs(lastTicketEndTime, true); } - - // Create departure datetime for the selected date (today, tomorrow, or day after tomorrow) - const dateOffset = getDateOffset(dateOption); - const departure = now.clone().add(dateOffset, 'day').hour(departureHour).minute(departureMinute).second(0); - const diffMinutes = departure.diff(now, 'minute'); + const d = dayjs(lastTicketEndTime); + return d.isValid() ? d : null; + }, []); + + const getDiffMinutesForRemaining = useCallback( + (item: TruckScheduleDashboardItem, dateOption: string): number | null => { + if (!currentTime) return null; + const etd = getDepartureEtdDayjs(item.truckDepartureTime, dateOption); + if (!etd) return null; + + const allDone = + item.numberOfTicketsReleased > 0 && item.numberOfTicketsCompleted >= item.numberOfTicketsReleased; + if (allDone) { + const lastEnd = getLastEndDayjsOrNull(item.lastTicketEndTime); + if (!lastEnd) return null; + return etd.diff(lastEnd, 'minute'); + } + + return etd.diff(currentTime, 'minute'); + }, + [currentTime, getDepartureEtdDayjs, getLastEndDayjsOrNull], + ); + + // Calculate time remaining for truck departure + const calculateTimeRemaining = useCallback((item: TruckScheduleDashboardItem, dateOption: string): string => { + const diffMinutes = getDiffMinutesForRemaining(item, dateOption); + if (diffMinutes === null) return '-'; if (diffMinutes < 0) { // Past departure time @@ -296,7 +300,7 @@ const TruckScheduleDashboard: React.FC = () => { const minutes = diffMinutes % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; } - }, [currentTime]); + }, [getDiffMinutesForRemaining]); // Generate unique key for tracking completed items const getItemKey = (item: TruckScheduleDashboardItem): string => { @@ -414,34 +418,12 @@ const TruckScheduleDashboard: React.FC = () => { }, [allData, selectedDate, selectedStore]); // Get chip color based on time remaining - const getTimeChipColor = (departureTime: string | number[] | null, dateOption: string): "success" | "warning" | "error" | "default" => { - if (!departureTime || !currentTime) return "default"; - - const now = currentTime; - let departureHour: number; - let departureMinute: number; - - if (Array.isArray(departureTime)) { - if (departureTime.length < 2) return "default"; - departureHour = departureTime[0] || 0; - departureMinute = departureTime[1] || 0; - } else if (typeof departureTime === 'string') { - const parts = departureTime.split(':'); - if (parts.length < 2) return "default"; - departureHour = parseInt(parts[0], 10); - departureMinute = parseInt(parts[1], 10); - } else { - return "default"; - } - - // Create departure datetime for the selected date (today, tomorrow, or day after tomorrow) - const dateOffset = getDateOffset(dateOption); - const departure = now.clone().add(dateOffset, 'day').hour(departureHour).minute(departureMinute).second(0); - const diffMinutes = departure.diff(now, 'minute'); - - if (diffMinutes < 0) return "error"; // Past due - if (diffMinutes <= 30) return "warning"; // Within 30 minutes - return "success"; // More than 30 minutes + const getTimeChipColor = (item: TruckScheduleDashboardItem, dateOption: string): "success" | "warning" | "error" | "default" => { + const diffMinutes = getDiffMinutesForRemaining(item, dateOption); + if (diffMinutes === null) return "default"; + if (diffMinutes < 0) return "error"; + if (diffMinutes < 30) return "warning"; + return "success"; }; return ( @@ -535,7 +517,9 @@ const TruckScheduleDashboard: React.FC = () => { ) : ( filteredData.map((row, index) => { const timeRemaining = calculateTimeRemaining(row, selectedDate); - const chipColor = getTimeChipColor(row.truckDepartureTime, selectedDate); + const chipColor = getTimeChipColor(row, selectedDate); + const startRel = relativeDayLabel(row.firstTicketStartTime, selectedDate); + const endRel = relativeDayLabel(row.lastTicketEndTime, selectedDate); return ( { - {formatDateTime(row.firstTicketStartTime)} + + {formatDateTime(row.firstTicketStartTime)} + {startRel && ( + + )} + - {formatDateTime(row.lastTicketEndTime)} + + {formatDateTime(row.lastTicketEndTime)} + {endRel && ( + + )} +