"use client"; import React, { useState, useEffect, useCallback, useRef } from 'react'; import { Box, Typography, Card, CardContent, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, CircularProgress, Stack } from '@mui/material'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; import { fetchJobProcessStatus, JobProcessStatusResponse } from '@/app/api/jo/actions'; import { arrayToDayjs } from '@/app/utils/formatUtil'; import { FormControl, Select, MenuItem } from "@mui/material"; const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes const JobProcessStatus: React.FC = () => { const { t } = useTranslation(["common", "jo"]); const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const refreshCountRef = useRef(0); const [currentTime, setCurrentTime] = useState(dayjs()); const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); // Update current time every second for countdown useEffect(() => { const timer = setInterval(() => { setCurrentTime(dayjs()); }, 1000); return () => clearInterval(timer); }, []); const loadData = useCallback(async () => { setLoading(true); try { const result = await fetchJobProcessStatus(selectedDate); setData(result); refreshCountRef.current += 1; } catch (error) { console.error('Error fetching job process status:', error); setData([]); } finally { setLoading(false); } }, [selectedDate]); useEffect(() => { loadData(); const interval = setInterval(() => { loadData(); }, REFRESH_INTERVAL); return () => clearInterval(interval); }, [loadData]); const formatTime = (timeData: any): string => { if (!timeData) return '-'; // 改为返回 '-' 而不是 'N/A' // Handle array format [year, month, day, hour, minute, second] if (Array.isArray(timeData)) { try { const parsed = arrayToDayjs(timeData, true); if (parsed.isValid()) { return parsed.format('HH:mm'); } } catch (error) { console.error('Error parsing array time:', error); } } // Handle LocalDateTime ISO string format (e.g., "2026-01-09T18:01:54") if (typeof timeData === 'string') { const parsed = dayjs(timeData); if (parsed.isValid()) { return parsed.format('HH:mm'); } } return '-'; }; const calculateRemainingTime = (planEndTime: any, processingTime: number | null, setupTime: number | null, changeoverTime: number | null): string => { if (!planEndTime) return '-'; let endTime: dayjs.Dayjs; // Handle array format [year, month, day, hour, minute, second] // Use arrayToDayjs for consistency with other parts of the codebase if (Array.isArray(planEndTime)) { try { endTime = arrayToDayjs(planEndTime, true); console.log('Parsed planEndTime array:', { array: planEndTime, parsed: endTime.format('YYYY-MM-DD HH:mm:ss'), isValid: endTime.isValid() }); } catch (error) { console.error('Error parsing array planEndTime:', error); return '-'; } } else if (typeof planEndTime === 'string') { endTime = dayjs(planEndTime); console.log('Parsed planEndTime string:', { string: planEndTime, parsed: endTime.format('YYYY-MM-DD HH:mm:ss'), isValid: endTime.isValid() }); } else { return '-'; } if (!endTime.isValid()) { console.error('Invalid endTime:', planEndTime); return '-'; } const diff = endTime.diff(currentTime, 'minute'); console.log('Remaining time calculation:', { endTime: endTime.format('YYYY-MM-DD HH:mm:ss'), currentTime: currentTime.format('YYYY-MM-DD HH:mm:ss'), diffMinutes: diff }); // If the planned end time is in the past, show 0 (or you could show negative time) if (diff < 0) return '0'; const hours = Math.floor(diff / 60); const minutes = diff % 60; return `${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}`; }; const calculateWaitTime = ( currentProcessEndTime: any, nextProcessStartTime: any, isLastProcess: boolean ): string => { if (isLastProcess) return '-'; if (!currentProcessEndTime) return '-'; if (nextProcessStartTime) return '0'; // Next process has started, stop counting let endTime: dayjs.Dayjs; // Handle array format if (Array.isArray(currentProcessEndTime)) { try { endTime = arrayToDayjs(currentProcessEndTime, true); } catch (error) { console.error('Error parsing array endTime:', error); return '-'; } } else if (typeof currentProcessEndTime === 'string') { endTime = dayjs(currentProcessEndTime); } else { return '-'; } if (!endTime.isValid()) return '-'; const diff = currentTime.diff(endTime, 'minute'); return diff > 0 ? diff.toString() : '0'; }; return ( {/* Title */} {t("Job Process Status Dashboard")} {/* Filters */} {loading ? ( ) : ( {t("Job Order No.")} {t("FG / WIP Item")} {t("Production Time Remaining")} {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( {t("Process")} {num} ))} {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( {t("Start")} {t("Finish")} {t("Wait Time [minutes]")} ))} {data.length === 0 ? ( {t("No data available")} ) : ( data.map((row) => ( {row.jobOrderCode || '-'} {row.itemCode || '-'} {row.itemName || '-'} {row.status === 'pending' ? '-' : calculateRemainingTime(row.planEndTime, row.processingTime, row.setupTime, row.changeoverTime)} {row.processes.map((process, index) => { const isLastProcess = index === row.processes.length - 1 || !row.processes.slice(index + 1).some(p => p.isRequired); const nextProcess = index < row.processes.length - 1 ? row.processes[index + 1] : null; const waitTime = calculateWaitTime( process.endTime, nextProcess?.startTime, isLastProcess ); // 如果工序不是必需的,只显示一个 N/A if (!process.isRequired) { return ( N/A ); } const label = [ process.processName, process.equipmentName, process.equipmentDetailName ? `-${process.equipmentDetailName}` : "", ].filter(Boolean).join(" "); // 如果工序是必需的,显示三行(Start、Finish、Wait Time) return ( {label || "-"} {formatTime(process.startTime)} {formatTime(process.endTime)} 0 ? 'warning.main' : 'text.primary', py: 0.5 }}> {waitTime} ); })} )) )}
)}
); }; export default JobProcessStatus;