diff --git a/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx b/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx index 94476d9..2c0ba24 100644 --- a/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx +++ b/src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx @@ -32,33 +32,52 @@ interface CompletedTracker { refreshCount: number; } +// Data stored per date for instant switching +interface DateData { + today: TruckScheduleDashboardItem[]; + tomorrow: TruckScheduleDashboardItem[]; + dayAfterTomorrow: TruckScheduleDashboardItem[]; +} + const TruckScheduleDashboard: React.FC = () => { const { t } = useTranslation("dashboard"); const [selectedStore, setSelectedStore] = useState(""); const [selectedDate, setSelectedDate] = useState("today"); - const [data, setData] = useState([]); + // Store data for all three dates for instant switching + const [allData, setAllData] = useState({ today: [], tomorrow: [], dayAfterTomorrow: [] }); const [loading, setLoading] = useState(true); // Initialize as null to avoid SSR/client hydration mismatch const [currentTime, setCurrentTime] = useState(null); const [isClient, setIsClient] = useState(false); - const completedTrackerRef = useRef>(new Map()); - const refreshCountRef = useRef(0); + // Track completed items per date + const completedTrackerRef = useRef>>(new Map([ + ['today', new Map()], + ['tomorrow', new Map()], + ['dayAfterTomorrow', new Map()] + ])); + const refreshCountRef = useRef>(new Map([ + ['today', 0], + ['tomorrow', 0], + ['dayAfterTomorrow', 0] + ])); // Get date label for display (e.g., "2026-01-17") const getDateLabel = (offset: number): string => { return dayjs().add(offset, 'day').format('YYYY-MM-DD'); }; + // Get day offset based on date option + const getDateOffset = (dateOption: string): number => { + if (dateOption === "today") return 0; + if (dateOption === "tomorrow") return 1; + if (dateOption === "dayAfterTomorrow") return 2; + return 0; + }; + // Convert date option to YYYY-MM-DD format for API const getDateParam = (dateOption: string): string => { - if (dateOption === "today") { - return dayjs().format('YYYY-MM-DD'); - } else if (dateOption === "tomorrow") { - return dayjs().add(1, 'day').format('YYYY-MM-DD'); - } else if (dateOption === "dayAfterTomorrow") { - return dayjs().add(2, 'day').format('YYYY-MM-DD'); - } - return dayjs().add(1, 'day').format('YYYY-MM-DD'); + const offset = getDateOffset(dateOption); + return dayjs().add(offset, 'day').format('YYYY-MM-DD'); }; // Set client flag and time on mount @@ -109,7 +128,7 @@ const TruckScheduleDashboard: React.FC = () => { }; // Calculate time remaining for truck departure - const calculateTimeRemaining = useCallback((departureTime: string | number[] | null): string => { + const calculateTimeRemaining = useCallback((departureTime: string | number[] | null, dateOption: string): string => { if (!departureTime || !currentTime) return '-'; const now = currentTime; @@ -129,8 +148,9 @@ const TruckScheduleDashboard: React.FC = () => { return '-'; } - // Create departure datetime for today - const departure = now.clone().hour(departureHour).minute(departureMinute).second(0); + // 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) { @@ -151,58 +171,81 @@ const TruckScheduleDashboard: React.FC = () => { return `${item.storeId}-${item.truckLanceCode}-${item.truckDepartureTime}`; }; - // Load data from API - const loadData = useCallback(async () => { + // Process data for a specific date option with completed tracker logic + const processDataForDate = (result: TruckScheduleDashboardItem[], dateOption: string): TruckScheduleDashboardItem[] => { + const tracker = completedTrackerRef.current.get(dateOption) || new Map(); + const currentRefresh = (refreshCountRef.current.get(dateOption) || 0) + 1; + refreshCountRef.current.set(dateOption, currentRefresh); + + result.forEach(item => { + const key = getItemKey(item); + // If all tickets are completed, track it + if (item.numberOfPickTickets > 0 && item.numberOfTicketsCompleted >= item.numberOfPickTickets) { + const existing = tracker.get(key); + if (!existing) { + tracker.set(key, { key, refreshCount: currentRefresh }); + } + } else { + // Remove from tracker if no longer completed + tracker.delete(key); + } + }); + + completedTrackerRef.current.set(dateOption, tracker); + + // Filter out items that have been completed for 2+ refresh cycles + return result.filter(item => { + const key = getItemKey(item); + const itemTracker = tracker.get(key); + if (itemTracker) { + // Hide if completed for 2 or more refresh cycles + if (currentRefresh - itemTracker.refreshCount >= 2) { + return false; + } + } + return true; + }); + }; + + // Load data for all three dates in parallel for instant switching + const loadData = useCallback(async (isInitialLoad: boolean = false) => { + // Only show loading spinner on initial load, not during refresh + if (isInitialLoad) { + setLoading(true); + } try { - const dateParam = getDateParam(selectedDate); - const result = await fetchTruckScheduleDashboardClient(dateParam); + const dateOptions = ['today', 'tomorrow', 'dayAfterTomorrow'] as const; + const dateParams = dateOptions.map(opt => getDateParam(opt)); - // Update completed tracker - refreshCountRef.current += 1; - const currentRefresh = refreshCountRef.current; + // Fetch all three dates in parallel + const [todayResult, tomorrowResult, dayAfterResult] = await Promise.all([ + fetchTruckScheduleDashboardClient(dateParams[0]), + fetchTruckScheduleDashboardClient(dateParams[1]), + fetchTruckScheduleDashboardClient(dateParams[2]) + ]); - result.forEach(item => { - const key = getItemKey(item); - // If all tickets are completed, track it - if (item.numberOfPickTickets > 0 && item.numberOfTicketsCompleted >= item.numberOfPickTickets) { - const existing = completedTrackerRef.current.get(key); - if (!existing) { - completedTrackerRef.current.set(key, { key, refreshCount: currentRefresh }); - } - } else { - // Remove from tracker if no longer completed - completedTrackerRef.current.delete(key); - } + // Process each date's data with completed tracker logic + setAllData({ + today: processDataForDate(todayResult, 'today'), + tomorrow: processDataForDate(tomorrowResult, 'tomorrow'), + dayAfterTomorrow: processDataForDate(dayAfterResult, 'dayAfterTomorrow') }); - - // Filter out items that have been completed for 2+ refresh cycles - const filteredResult = result.filter(item => { - const key = getItemKey(item); - const tracker = completedTrackerRef.current.get(key); - if (tracker) { - // Hide if completed for 2 or more refresh cycles - if (currentRefresh - tracker.refreshCount >= 2) { - return false; - } - } - return true; - }); - - setData(filteredResult); } catch (error) { console.error('Error fetching truck schedule dashboard:', error); } finally { - setLoading(false); + if (isInitialLoad) { + setLoading(false); + } } - }, [selectedDate]); + }, []); // Initial load and auto-refresh every 5 minutes useEffect(() => { - loadData(); + loadData(true); // Initial load - show spinner const refreshInterval = setInterval(() => { - loadData(); - }, 0.1 * 60 * 1000); // 5 minutes + loadData(false); // Refresh - don't show spinner, keep existing data visible + }, 5 * 60 * 1000); // 5 minutes return () => clearInterval(refreshInterval); }, [loadData]); @@ -218,14 +261,17 @@ const TruckScheduleDashboard: React.FC = () => { return () => clearInterval(timeInterval); }, [isClient]); - // Filter data by selected store + // Get data for selected date, then filter by store - both filters are instant const filteredData = useMemo(() => { - if (!selectedStore) return data; - return data.filter(item => item.storeId === selectedStore); - }, [data, selectedStore]); + // First get the data for the selected date + const dateData = allData[selectedDate as keyof DateData] || []; + // Then filter by store if selected + if (!selectedStore) return dateData; + return dateData.filter(item => item.storeId === selectedStore); + }, [allData, selectedDate, selectedStore]); // Get chip color based on time remaining - const getTimeChipColor = (departureTime: string | number[] | null): "success" | "warning" | "error" | "default" => { + const getTimeChipColor = (departureTime: string | number[] | null, dateOption: string): "success" | "warning" | "error" | "default" => { if (!departureTime || !currentTime) return "default"; const now = currentTime; @@ -245,7 +291,9 @@ const TruckScheduleDashboard: React.FC = () => { return "default"; } - const departure = now.clone().hour(departureHour).minute(departureMinute).second(0); + // 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 @@ -332,8 +380,8 @@ const TruckScheduleDashboard: React.FC = () => { ) : ( filteredData.map((row, index) => { - const timeRemaining = calculateTimeRemaining(row.truckDepartureTime); - const chipColor = getTimeChipColor(row.truckDepartureTime); + const timeRemaining = calculateTimeRemaining(row.truckDepartureTime, selectedDate); + const chipColor = getTimeChipColor(row.truckDepartureTime, selectedDate); return (