| @@ -62,6 +62,12 @@ const GoodsReceiptStatusNew: React.FC = () => { | |||||
| return () => clearInterval(refreshInterval); | return () => clearInterval(refreshInterval); | ||||
| }, [loadData, screenCleared]); | }, [loadData, screenCleared]); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| useEffect(() => { | |||||
| const timer = setInterval(() => setNow(dayjs()), 60 * 1000); | |||||
| return () => clearInterval(timer); | |||||
| }, []); | |||||
| const selectedDateLabel = useMemo(() => { | const selectedDateLabel = useMemo(() => { | ||||
| return selectedDate.format('YYYY-MM-DD'); | return selectedDate.format('YYYY-MM-DD'); | ||||
| @@ -187,10 +193,14 @@ const GoodsReceiptStatusNew: React.FC = () => { | |||||
| </Stack> | </Stack> | ||||
| <Box sx={{ flexGrow: 1 }} /> | <Box sx={{ flexGrow: 1 }} /> | ||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 15 minutes")} | {t("Last updated")}: {lastUpdated ? lastUpdated.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 15 minutes")} | {t("Last updated")}: {lastUpdated ? lastUpdated.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| <Button variant="outlined" color="inherit" onClick={() => setScreenCleared(true)}> | <Button variant="outlined" color="inherit" onClick={() => setScreenCleared(true)}> | ||||
| {t("Exit Screen")} | {t("Exit Screen")} | ||||
| @@ -204,7 +214,7 @@ const GoodsReceiptStatusNew: React.FC = () => { | |||||
| <CircularProgress /> | <CircularProgress /> | ||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <TableContainer component={Paper}> | |||||
| <TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}> | |||||
| <Table | <Table | ||||
| size="small" | size="small" | ||||
| sx={{ | sx={{ | ||||
| @@ -212,7 +222,7 @@ const GoodsReceiptStatusNew: React.FC = () => { | |||||
| tableLayout: 'fixed', | tableLayout: 'fixed', | ||||
| }} | }} | ||||
| > | > | ||||
| <TableHead> | |||||
| <TableHead sx={{ backgroundColor: 'grey.100', position: 'sticky', top: 0, zIndex: 1 }}> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | <TableRow sx={{ backgroundColor: 'grey.100' }}> | ||||
| <TableCell sx={{ fontWeight: 600, width: '32%', padding: '4px 6px' }}>{t("Supplier")}</TableCell> | <TableCell sx={{ fontWeight: 600, width: '32%', padding: '4px 6px' }}>{t("Supplier")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600, width: '30%', padding: '4px 6px' }}>{t("Purchase Order Code")}</TableCell> | <TableCell sx={{ fontWeight: 600, width: '30%', padding: '4px 6px' }}>{t("Purchase Order Code")}</TableCell> | ||||
| @@ -74,7 +74,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| ['tomorrow', 0], | ['tomorrow', 0], | ||||
| ['dayAfterTomorrow', 0] | ['dayAfterTomorrow', 0] | ||||
| ])); | ])); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| // Save completed tracker state to sessionStorage | // Save completed tracker state to sessionStorage | ||||
| const saveCompletedTrackerToStorage = useCallback(() => { | const saveCompletedTrackerToStorage = useCallback(() => { | ||||
| if (typeof window === 'undefined') return; | if (typeof window === 'undefined') return; | ||||
| @@ -377,6 +377,11 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| } | } | ||||
| }, [processDataForDate]); | }, [processDataForDate]); | ||||
| useEffect(() => { | |||||
| const timer = setInterval(() => setNow(dayjs()), 60 * 1000); | |||||
| return () => clearInterval(timer); | |||||
| }, []); | |||||
| // Initial load and auto-refresh every 5 minutes | // Initial load and auto-refresh every 5 minutes | ||||
| useEffect(() => { | useEffect(() => { | ||||
| loadData(true); // Initial load - show spinner | loadData(true); // Initial load - show spinner | ||||
| @@ -483,11 +488,17 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</MenuItem> | <MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Typography variant="body2" sx={{ alignSelf: 'center', color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | </Typography> | ||||
| </Stack> | </Stack> | ||||
| </Stack> | |||||
| {/* Table */} | {/* Table */} | ||||
| <Box sx={{ mt: 2 }}> | <Box sx={{ mt: 2 }}> | ||||
| @@ -496,20 +507,20 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <CircularProgress /> | <CircularProgress /> | ||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <TableContainer component={Paper}> | |||||
| <TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}> | |||||
| <Table size="small" sx={{ minWidth: 1200 }}> | <Table size="small" sx={{ minWidth: 1200 }}> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100', position: 'sticky', top: 0, zIndex: 1 }}> | |||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Store ID")}</TableCell> | <TableCell sx={{ fontWeight: 600 }}>{t("Store ID")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Truck Schedule")}</TableCell> | <TableCell sx={{ fontWeight: 600 }}>{t("Truck Schedule")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Time Remaining")}</TableCell> | <TableCell sx={{ fontWeight: 600 }}>{t("Time Remaining")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600 }} align="center">{t("No. of Shops")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="center">{t("Tickets Released")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="center">{t("Tickets Completed")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="center">{t("Total Items")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="right">{t("No. of Shops")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="right">{t("Tickets Released")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="right">{t("Tickets Completed")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="right">{t("Total Items")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }}>{t("First Ticket Start")}</TableCell> | <TableCell sx={{ fontWeight: 600 }}>{t("First Ticket Start")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Last Ticket End")}</TableCell> | <TableCell sx={{ fontWeight: 600 }}>{t("Last Ticket End")}</TableCell> | ||||
| <TableCell sx={{ fontWeight: 600 }} align="center">{t("Pick Time (min)")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }} align="right">{t("Pick Time (min)")}</TableCell> | |||||
| </TableRow> | </TableRow> | ||||
| </TableHead> | </TableHead> | ||||
| <TableBody> | <TableBody> | ||||
| @@ -561,26 +572,26 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| sx={{ fontWeight: 600 }} | sx={{ fontWeight: 600 }} | ||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="right"> | |||||
| <Typography variant="body2"> | <Typography variant="body2"> | ||||
| {row.numberOfShopsToServe} [{row.numberOfPickTickets}] | {row.numberOfShopsToServe} [{row.numberOfPickTickets}] | ||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="right"> | |||||
| <Chip | <Chip | ||||
| label={row.numberOfTicketsReleased} | label={row.numberOfTicketsReleased} | ||||
| size="small" | size="small" | ||||
| color={row.numberOfTicketsReleased > 0 ? 'info' : 'default'} | color={row.numberOfTicketsReleased > 0 ? 'info' : 'default'} | ||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="right"> | |||||
| <Chip | <Chip | ||||
| label={row.numberOfTicketsCompleted} | label={row.numberOfTicketsCompleted} | ||||
| size="small" | size="small" | ||||
| color={row.numberOfTicketsCompleted > 0 ? 'success' : 'default'} | color={row.numberOfTicketsCompleted > 0 ? 'success' : 'default'} | ||||
| /> | /> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="right"> | |||||
| <Typography variant="body2" sx={{ fontWeight: 500 }}> | <Typography variant="body2" sx={{ fontWeight: 500 }}> | ||||
| {row.totalItemsToPick} | {row.totalItemsToPick} | ||||
| </Typography> | </Typography> | ||||
| @@ -591,7 +602,7 @@ const TruckScheduleDashboard: React.FC = () => { | |||||
| <TableCell> | <TableCell> | ||||
| {formatDateTime(row.lastTicketEndTime)} | {formatDateTime(row.lastTicketEndTime)} | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell align="center"> | |||||
| <TableCell align="right"> | |||||
| <Typography | <Typography | ||||
| variant="body2" | variant="body2" | ||||
| sx={{ | sx={{ | ||||
| @@ -40,6 +40,8 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| pageSize: 5, | pageSize: 5, | ||||
| }); | }); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null); | |||||
| const formatTime = (timeData: any): string => { | const formatTime = (timeData: any): string => { | ||||
| if (!timeData) return ''; | if (!timeData) return ''; | ||||
| @@ -74,21 +76,25 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| return { startDate: today, endDate: dayAfterTomorrow }; | return { startDate: today, endDate: dayAfterTomorrow }; | ||||
| }; | }; | ||||
| useEffect(() => { | |||||
| const loadData = async () => { | |||||
| setLoading(true); | |||||
| try { | |||||
| const { startDate, endDate } = getDateRange(); | |||||
| const result = await fetchTicketReleaseTable(startDate, endDate); | |||||
| setData(result); | |||||
| } catch (error) { | |||||
| console.error('Error fetching ticket release table:', error); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | |||||
| }; | |||||
| loadData(); | |||||
| }, []); | |||||
| const loadData = useCallback(async () => { | |||||
| setLoading(true); | |||||
| try { | |||||
| const { startDate, endDate } = getDateRange(); | |||||
| const result = await fetchTicketReleaseTable(startDate, endDate); | |||||
| setData(result); | |||||
| setLastDataRefreshTime(dayjs()); | |||||
| } catch (error) { | |||||
| console.error('Error fetching ticket release table:', error); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | |||||
| }, []); | |||||
| useEffect(() => { | |||||
| loadData(); | |||||
| const id = setInterval(loadData, 5 * 60 * 1000); | |||||
| return () => clearInterval(id); | |||||
| }, [loadData]); | |||||
| const filteredData = data.filter((item) => { | const filteredData = data.filter((item) => { | ||||
| // Filter by floor if selected | // Filter by floor if selected | ||||
| @@ -214,6 +220,16 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| <MenuItem value="completed">{t("completed")}</MenuItem> | <MenuItem value="completed">{t("completed")}</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Stack> | </Stack> | ||||
| <Box sx={{ mt: 2 }}> | <Box sx={{ mt: 2 }}> | ||||
| @@ -223,10 +239,10 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <> | <> | ||||
| <TableContainer component={Paper}> | |||||
| <TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}> | |||||
| <Table size="small" sx={{ minWidth: 650 }}> | <Table size="small" sx={{ minWidth: 650 }}> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow> | |||||
| <TableRow sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'grey.100' }}> | |||||
| <TableCell>{t("Store ID")}</TableCell> | <TableCell>{t("Store ID")}</TableCell> | ||||
| <TableCell>{t("Required Delivery Date")}</TableCell> | <TableCell>{t("Required Delivery Date")}</TableCell> | ||||
| <TableCell> | <TableCell> | ||||
| @@ -242,7 +258,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| {/*<TableCell>{t("Truck Departure Time")}</TableCell> | {/*<TableCell>{t("Truck Departure Time")}</TableCell> | ||||
| <TableCell>{t("Truck Lane Code")}</TableCell>*/} | <TableCell>{t("Truck Lane Code")}</TableCell>*/} | ||||
| <TableCell sx={{ minWidth: 200, width: '20%' }}>{t("Shop Name")}</TableCell> | <TableCell sx={{ minWidth: 200, width: '20%' }}>{t("Shop Name")}</TableCell> | ||||
| <TableCell>{t("Loading Sequence")}</TableCell> | |||||
| <TableCell align="right">{t("Loading Sequence")}</TableCell> | |||||
| {/*<TableCell>{t("Delivery Order Code(s)")}</TableCell> | {/*<TableCell>{t("Delivery Order Code(s)")}</TableCell> | ||||
| <TableCell>{t("Pick Order Code(s)")}</TableCell> | <TableCell>{t("Pick Order Code(s)")}</TableCell> | ||||
| <TableCell>{t("Ticket Number")}</TableCell> | <TableCell>{t("Ticket Number")}</TableCell> | ||||
| @@ -263,7 +279,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{t("Handler Name")}</TableCell> | <TableCell>{t("Handler Name")}</TableCell> | ||||
| <TableCell sx={{ minWidth: 100, width: '8%', whiteSpace: 'nowrap' }}>{t("Number of FG Items (Order Item(s) Count)")}</TableCell> | |||||
| <TableCell align="right" sx={{ minWidth: 100, width: '8%', whiteSpace: 'nowrap' }}>{t("Number of FG Items (Order Item(s) Count)")}</TableCell> | |||||
| </TableRow> | </TableRow> | ||||
| </TableHead> | </TableHead> | ||||
| <TableBody> | <TableBody> | ||||
| @@ -309,9 +325,9 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| </TableCell> | </TableCell> | ||||
| <TableCell sx={{ minWidth: 200, width: '20%' }}>{row.shopName || '-'}</TableCell> | <TableCell sx={{ minWidth: 200, width: '20%' }}>{row.shopName || '-'}</TableCell> | ||||
| <TableCell>{row.loadingSequence || '-'}</TableCell> | |||||
| <TableCell align="right">{row.loadingSequence || '-'}</TableCell> | |||||
| {/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell> | {/*<TableCell>{row.deliveryOrderCode || '-'}</TableCell> | ||||
| <TableCell>{row.pickOrderCode || '-'}</TableCell> | |||||
| <TableCell align="right">{row.pickOrderCode || '-'}</TableCell> | |||||
| <TableCell>{row.ticketNo || '-'}</TableCell> | <TableCell>{row.ticketNo || '-'}</TableCell> | ||||
| <TableCell> | <TableCell> | ||||
| {row.ticketReleaseTime | {row.ticketReleaseTime | ||||
| @@ -360,8 +376,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{row.handlerName || '-'}</TableCell> | |||||
| <TableCell sx={{ minWidth: 100, width: '8%' }}>{row.numberOfFGItems ?? 0}</TableCell> | |||||
| <TableCell align="right" sx={{ minWidth: 100, width: '8%' }}>{row.numberOfFGItems ?? 0}</TableCell> | |||||
| </TableRow> | </TableRow> | ||||
| ); | ); | ||||
| }) | }) | ||||
| @@ -37,7 +37,8 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| pageNum: 0, | pageNum: 0, | ||||
| pageSize: 10, | pageSize: 10, | ||||
| }); | }); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null); | |||||
| const loadData = useCallback(async () => { | const loadData = useCallback(async () => { | ||||
| setLoading(true); | setLoading(true); | ||||
| @@ -49,6 +50,7 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| setData(result || []); | setData(result || []); | ||||
| } | } | ||||
| refreshCountRef.current += 1; | refreshCountRef.current += 1; | ||||
| setLastDataRefreshTime(dayjs()); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error fetching material pick status:', error); | console.error('Error fetching material pick status:', error); | ||||
| setData([]); | setData([]); | ||||
| @@ -65,6 +67,11 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| return () => clearInterval(interval); | return () => clearInterval(interval); | ||||
| }, [loadData]); | }, [loadData]); | ||||
| useEffect(() => { | |||||
| const timer = setInterval(() => setNow(dayjs()), 60 * 1000); | |||||
| return () => clearInterval(timer); | |||||
| }, []); | |||||
| const formatTime = (timeData: any): string => { | const formatTime = (timeData: any): string => { | ||||
| if (!timeData) return ''; | if (!timeData) return ''; | ||||
| @@ -245,6 +252,16 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem> | <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 10 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Stack> | </Stack> | ||||
| <Box sx={{ mt: 2 }}> | <Box sx={{ mt: 2 }}> | ||||
| @@ -254,9 +271,9 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <> | <> | ||||
| <TableContainer component={Paper}> | |||||
| <TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}> | |||||
| <Table size="small" sx={{ minWidth: 650 }}> | <Table size="small" sx={{ minWidth: 650 }}> | ||||
| <TableHead> | |||||
| <TableHead sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'grey.100' }}> | |||||
| <TableRow> | <TableRow> | ||||
| <TableCell> | <TableCell> | ||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | ||||
| @@ -267,7 +284,7 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | |||||
| <TableCell align="right"> | |||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("Job Order Qty")} | {t("Job Order Qty")} | ||||
| @@ -276,7 +293,7 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | |||||
| <TableCell align="right"> | |||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("No. of Items to be Picked")} | {t("No. of Items to be Picked")} | ||||
| @@ -284,7 +301,7 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | |||||
| <TableCell align="right"> | |||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("No. of Items with Issue During Pick")} | {t("No. of Items with Issue During Pick")} | ||||
| @@ -308,7 +325,7 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </Box> | </Box> | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell sx={{ | |||||
| <TableCell align="right" sx={{ | |||||
| }}> | }}> | ||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}> | ||||
| @@ -343,16 +360,16 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| </TableCell> | </TableCell> | ||||
| <TableCell> | |||||
| <TableCell align="right"> | |||||
| {row.jobOrderQty !== null && row.jobOrderQty !== undefined | {row.jobOrderQty !== null && row.jobOrderQty !== undefined | ||||
| ? `${row.jobOrderQty} ${row.uom || ''}` | ? `${row.jobOrderQty} ${row.uom || ''}` | ||||
| : '-'} | : '-'} | ||||
| </TableCell> | </TableCell> | ||||
| <TableCell>{row.numberOfItemsToPick ?? 0}</TableCell> | |||||
| <TableCell>{row.numberOfItemsWithIssue ?? 0}</TableCell> | |||||
| <TableCell align="right">{row.numberOfItemsToPick ?? 0}</TableCell> | |||||
| <TableCell align="right">{row.numberOfItemsWithIssue ?? 0}</TableCell> | |||||
| <TableCell>{formatTime(row.pickStartTime) || '-'}</TableCell> | <TableCell>{formatTime(row.pickStartTime) || '-'}</TableCell> | ||||
| <TableCell>{formatTime(row.pickEndTime) || '-'}</TableCell> | <TableCell>{formatTime(row.pickEndTime) || '-'}</TableCell> | ||||
| <TableCell sx={{ | |||||
| <TableCell align="right" sx={{ | |||||
| backgroundColor: 'rgba(76, 175, 80, 0.1)', | backgroundColor: 'rgba(76, 175, 80, 0.1)', | ||||
| fontWeight: 600 | fontWeight: 600 | ||||
| }}> | }}> | ||||
| @@ -17,6 +17,7 @@ import { | |||||
| Tabs, | Tabs, | ||||
| Tab, | Tab, | ||||
| Chip, | Chip, | ||||
| Stack | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| @@ -34,7 +35,7 @@ const STATUS_COLORS: Record<string, "success" | "default" | "warning" | "error"> | |||||
| Idle: "default", | Idle: "default", | ||||
| Repair: "warning", | Repair: "warning", | ||||
| }; | }; | ||||
| const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null); | |||||
| const formatDateTime = (value: any): string => { | const formatDateTime = (value: any): string => { | ||||
| if (!value) return "-"; | if (!value) return "-"; | ||||
| @@ -132,12 +133,14 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| const [data, setData] = useState<EquipmentStatusByTypeResponse[]>([]); | const [data, setData] = useState<EquipmentStatusByTypeResponse[]>([]); | ||||
| const [loading, setLoading] = useState<boolean>(true); | const [loading, setLoading] = useState<boolean>(true); | ||||
| const [tabIndex, setTabIndex] = useState<number>(0); | const [tabIndex, setTabIndex] = useState<number>(0); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| const loadData = useCallback(async () => { | const loadData = useCallback(async () => { | ||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| const result = await fetchEquipmentStatus(); | const result = await fetchEquipmentStatus(); | ||||
| setData(result || []); | setData(result || []); | ||||
| setLastDataRefreshTime(dayjs()); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error fetching equipment status:", error); | console.error("Error fetching equipment status:", error); | ||||
| setData([]); | setData([]); | ||||
| @@ -163,6 +166,11 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| return () => clearInterval(timer); | return () => clearInterval(timer); | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| const timer = setInterval(() => setNow(dayjs()), 60 * 1000); | |||||
| return () => clearInterval(timer); | |||||
| }, []); | |||||
| const handleTabChange = (_: React.SyntheticEvent, newValue: number) => { | const handleTabChange = (_: React.SyntheticEvent, newValue: number) => { | ||||
| setTabIndex(newValue); | setTabIndex(newValue); | ||||
| }; | }; | ||||
| @@ -180,21 +188,32 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </Box> | </Box> | ||||
| <Tabs | |||||
| value={tabIndex} | |||||
| onChange={handleTabChange} | |||||
| sx={{ mb: 2 }} | |||||
| variant="scrollable" | |||||
| scrollButtons="auto" | |||||
| > | |||||
| <Tab label={t("All")} /> | |||||
| {data.map((type, index) => ( | |||||
| <Tab | |||||
| key={type.equipmentTypeId} | |||||
| label={type.equipmentTypeName || `${t("Equipment Type")} ${index + 1}`} | |||||
| /> | |||||
| ))} | |||||
| </Tabs> | |||||
| <Box sx={{ display: 'flex', alignItems: 'center', width: '100%', mb: 2 }}> | |||||
| <Tabs | |||||
| value={tabIndex} | |||||
| onChange={handleTabChange} | |||||
| sx={{ flexShrink: 0 }} | |||||
| variant="scrollable" | |||||
| scrollButtons="auto" | |||||
| > | |||||
| <Tab label={t("All")} /> | |||||
| {data.map((type, index) => ( | |||||
| <Tab | |||||
| key={type.equipmentTypeId} | |||||
| label={type.equipmentTypeName || `${t("Equipment Type")} ${index + 1}`} | |||||
| /> | |||||
| ))} | |||||
| </Tabs> | |||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 1 minute")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Box> | |||||
| {loading ? ( | {loading ? ( | ||||
| <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}> | <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}> | ||||
| @@ -223,17 +242,17 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| {type.equipmentTypeName || "-"} | {type.equipmentTypeName || "-"} | ||||
| </Typography> | </Typography> | ||||
| <TableContainer component={Paper}> | |||||
| <TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}> | |||||
| <Table size="small"> | <Table size="small"> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow> | |||||
| <TableRow sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'background.paper' }}> | |||||
| <TableCell> | <TableCell> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("Equipment Name and Code")} | {t("Equipment Name and Code")} | ||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| {details.map((d) => ( | {details.map((d) => ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| <Box sx={{ display: "flex", flexDirection: "column" }}> | <Box sx={{ display: "flex", flexDirection: "column" }}> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {d.equipmentDetailName || "-"} | {d.equipmentDetailName || "-"} | ||||
| @@ -255,7 +274,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| {details.map((d) => ( | {details.map((d) => ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| {d.status === "Processing" ? d.currentProcess?.processName || "-" : "-"} | {d.status === "Processing" ? d.currentProcess?.processName || "-" : "-"} | ||||
| </TableCell> | </TableCell> | ||||
| ))} | ))} | ||||
| @@ -275,7 +294,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| // Processing 时只显示 job order code,不显示 Chip | // Processing 时只显示 job order code,不显示 Chip | ||||
| if (d.status === "Processing" && cp?.jobOrderCode) { | if (d.status === "Processing" && cp?.jobOrderCode) { | ||||
| return ( | return ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| <Typography variant="body2" sx={{ fontWeight: 500 }}> | <Typography variant="body2" sx={{ fontWeight: 500 }}> | ||||
| {cp.jobOrderCode} | {cp.jobOrderCode} | ||||
| </Typography> | </Typography> | ||||
| @@ -285,7 +304,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| // 其他状态显示 Chip | // 其他状态显示 Chip | ||||
| return ( | return ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| <Chip label={t(`${d.status}`)} color={chipColor} size="small" /> | <Chip label={t(`${d.status}`)} color={chipColor} size="small" /> | ||||
| </TableCell> | </TableCell> | ||||
| ); | ); | ||||
| @@ -302,7 +321,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| {details.map((d) => ( | {details.map((d) => ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| {d.status === "Processing" | {d.status === "Processing" | ||||
| ? formatDateTime(d.currentProcess?.startTime) | ? formatDateTime(d.currentProcess?.startTime) | ||||
| : "-"} | : "-"} | ||||
| @@ -318,7 +337,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| {details.map((d) => ( | {details.map((d) => ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell key={d.equipmentDetailId}> | |||||
| {d.status === "Processing" | {d.status === "Processing" | ||||
| ? calculateEstimatedCompletionTime( | ? calculateEstimatedCompletionTime( | ||||
| d.currentProcess?.startTime, | d.currentProcess?.startTime, | ||||
| @@ -337,7 +356,7 @@ const EquipmentStatusDashboard: React.FC = () => { | |||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| {details.map((d) => ( | {details.map((d) => ( | ||||
| <TableCell key={d.equipmentDetailId} align="center"> | |||||
| <TableCell align="right" key={d.equipmentDetailId}> | |||||
| {d.status === "Processing" | {d.status === "Processing" | ||||
| ? calculateRemainingTime( | ? calculateRemainingTime( | ||||
| d.currentProcess?.startTime, | d.currentProcess?.startTime, | ||||
| @@ -31,6 +31,7 @@ const JobProcessStatus: React.FC = () => { | |||||
| const refreshCountRef = useRef<number>(0); | const refreshCountRef = useRef<number>(0); | ||||
| const [currentTime, setCurrentTime] = useState(dayjs()); | const [currentTime, setCurrentTime] = useState(dayjs()); | ||||
| const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); | const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); | ||||
| const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null); | |||||
| // Update current time every second for countdown | // Update current time every second for countdown | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -46,6 +47,7 @@ const JobProcessStatus: React.FC = () => { | |||||
| const result = await fetchJobProcessStatus(selectedDate); | const result = await fetchJobProcessStatus(selectedDate); | ||||
| setData(result); | setData(result); | ||||
| refreshCountRef.current += 1; | refreshCountRef.current += 1; | ||||
| setLastDataRefreshTime(dayjs()); | |||||
| } catch (error) { | } catch (error) { | ||||
| console.error('Error fetching job process status:', error); | console.error('Error fetching job process status:', error); | ||||
| setData([]); | setData([]); | ||||
| @@ -189,6 +191,16 @@ const JobProcessStatus: React.FC = () => { | |||||
| <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>前天</MenuItem> | <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>前天</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {currentTime.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 10 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Stack> | </Stack> | ||||
| <Box sx={{ mt: 2 }}> | <Box sx={{ mt: 2 }}> | ||||
| {loading ? ( | {loading ? ( | ||||
| @@ -200,12 +212,14 @@ const JobProcessStatus: React.FC = () => { | |||||
| component={Paper} | component={Paper} | ||||
| sx={{ | sx={{ | ||||
| border: '3px solid #135fed', | border: '3px solid #135fed', | ||||
| overflowX: 'auto', // 关键:允许横向滚动 | |||||
| overflowX: 'auto', | |||||
| maxHeight: 440, | |||||
| overflow: 'auto' | |||||
| }} | }} | ||||
| > | > | ||||
| <Table size="small" sx={{ minWidth: 1800 }}> | <Table size="small" sx={{ minWidth: 1800 }}> | ||||
| <TableHead> | |||||
| <TableRow> | |||||
| <TableHead sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'grey.100' }}> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||||
| <TableCell rowSpan={3} sx={{ padding: '16px 20px' }}> | <TableCell rowSpan={3} sx={{ padding: '16px 20px' }}> | ||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("Job Order No.")} | {t("Job Order No.")} | ||||
| @@ -223,18 +237,18 @@ const JobProcessStatus: React.FC = () => { | |||||
| </TableCell> | </TableCell> | ||||
| </TableRow> | </TableRow> | ||||
| <TableRow> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||||
| {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( | {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( | ||||
| <TableCell key={num} align="center" sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <TableCell key={num} sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | <Typography variant="subtitle2" sx={{ fontWeight: 600 }}> | ||||
| {t("Process")} {num} | {t("Process")} {num} | ||||
| </Typography> | </Typography> | ||||
| </TableCell> | </TableCell> | ||||
| ))} | ))} | ||||
| </TableRow> | </TableRow> | ||||
| <TableRow> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||||
| {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( | {Array.from({ length: 16 }, (_, i) => i + 1).map((num) => ( | ||||
| <TableCell key={num} align="center" sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <TableCell key={num} sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> | ||||
| <Typography variant="caption" sx={{ fontWeight: 600 }}> | <Typography variant="caption" sx={{ fontWeight: 600 }}> | ||||
| {t("Start")} | {t("Start")} | ||||
| @@ -284,7 +298,7 @@ const JobProcessStatus: React.FC = () => { | |||||
| // 如果工序不是必需的,只显示一个 N/A | // 如果工序不是必需的,只显示一个 N/A | ||||
| if (!process.isRequired) { | if (!process.isRequired) { | ||||
| return ( | return ( | ||||
| <TableCell key={index} align="center" sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <TableCell key={index} sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <Typography variant="body2"> | <Typography variant="body2"> | ||||
| N/A | N/A | ||||
| </Typography> | </Typography> | ||||
| @@ -298,7 +312,7 @@ const JobProcessStatus: React.FC = () => { | |||||
| ].filter(Boolean).join(" "); | ].filter(Boolean).join(" "); | ||||
| // 如果工序是必需的,显示三行(Start、Finish、Wait Time) | // 如果工序是必需的,显示三行(Start、Finish、Wait Time) | ||||
| return ( | return ( | ||||
| <TableCell key={index} align="center" sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <TableCell key={index} sx={{ padding: '16px 20px', minWidth: 150 }}> | |||||
| <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> | <Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}> | ||||
| <Typography variant="body2" sx={{ mb: 0.5 }}>{label || "-"}</Typography> | <Typography variant="body2" sx={{ mb: 0.5 }}>{label || "-"}</Typography> | ||||
| <Typography variant="body2" sx={{ py: 0.5 }}> | <Typography variant="body2" sx={{ py: 0.5 }}> | ||||
| @@ -32,6 +32,8 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| const [loading, setLoading] = useState<boolean>(true); | const [loading, setLoading] = useState<boolean>(true); | ||||
| const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); | const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD")); | ||||
| const refreshCountRef = useRef<number>(0); | const refreshCountRef = useRef<number>(0); | ||||
| const [now, setNow] = useState(dayjs()); | |||||
| const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null); | |||||
| const formatTime = (timeData: any): string => { | const formatTime = (timeData: any): string => { | ||||
| if (!timeData) return "-"; | if (!timeData) return "-"; | ||||
| @@ -69,6 +71,7 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| try { | try { | ||||
| const result = await fetchOperatorKpi(selectedDate); | const result = await fetchOperatorKpi(selectedDate); | ||||
| setData(result); | setData(result); | ||||
| setLastDataRefreshTime(dayjs()); | |||||
| refreshCountRef.current += 1; | refreshCountRef.current += 1; | ||||
| } catch (error) { | } catch (error) { | ||||
| console.error("Error fetching operator KPI:", error); | console.error("Error fetching operator KPI:", error); | ||||
| @@ -86,6 +89,11 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| return () => clearInterval(interval); | return () => clearInterval(interval); | ||||
| }, [loadData]); | }, [loadData]); | ||||
| useEffect(() => { | |||||
| const timer = setInterval(() => setNow(dayjs()), 60 * 1000); | |||||
| return () => clearInterval(timer); | |||||
| }, []); | |||||
| const renderCurrentProcesses = (processes: OperatorKpiProcessInfo[]) => { | const renderCurrentProcesses = (processes: OperatorKpiProcessInfo[]) => { | ||||
| if (!processes || processes.length === 0) { | if (!processes || processes.length === 0) { | ||||
| return ( | return ( | ||||
| @@ -167,6 +175,16 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem> | <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem> | ||||
| </Select> | </Select> | ||||
| </FormControl> | </FormControl> | ||||
| <Box sx={{ flexGrow: 1 }} /> | |||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Now")}: {now.format('HH:mm')} | |||||
| </Typography> | |||||
| <Typography variant="body2" sx={{ color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 10 minutes")} | {t("Last updated")}: {lastDataRefreshTime ? lastDataRefreshTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| </Stack> | </Stack> | ||||
| {loading ? ( | {loading ? ( | ||||
| @@ -174,25 +192,22 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| <CircularProgress /> | <CircularProgress /> | ||||
| </Box> | </Box> | ||||
| ) : ( | ) : ( | ||||
| <TableContainer | |||||
| <TableContainer sx={{ border: "3px solid #135fed", overflowX: "auto", maxHeight: 440, overflow: 'auto' }} | |||||
| component={Paper} | component={Paper} | ||||
| sx={{ | |||||
| border: "3px solid #135fed", | |||||
| overflowX: "auto", | |||||
| }} | |||||
| > | > | ||||
| <Table size="small" sx={{ minWidth: 800 }}> | <Table size="small" sx={{ minWidth: 800 }}> | ||||
| <TableHead> | <TableHead> | ||||
| <TableRow | |||||
| <TableRow | |||||
| sx={{ | sx={{ | ||||
| bgcolor: "#424242", | bgcolor: "#424242", | ||||
| "& th": { | "& th": { | ||||
| borderBottom: "none", | borderBottom: "none", | ||||
| py: 1.5, | py: 1.5, | ||||
| position: 'sticky', top: 0, zIndex: 1 | |||||
| }, | }, | ||||
| }} | }} | ||||
| > | > | ||||
| <TableCell sx={{ width: 80 }}> | |||||
| <TableCell align="right" sx={{ width: 80 }}> | |||||
| <Typography | <Typography | ||||
| variant="subtitle2" | variant="subtitle2" | ||||
| sx={{ | sx={{ | ||||
| @@ -252,10 +267,9 @@ const OperatorKpiDashboard: React.FC = () => { | |||||
| }, | }, | ||||
| }} | }} | ||||
| > | > | ||||
| <TableCell | |||||
| <TableCell align="right" | |||||
| sx={{ | sx={{ | ||||
| width: 80, | width: 80, | ||||
| textAlign: "center", | |||||
| fontWeight: 500, | fontWeight: 500, | ||||
| verticalAlign: "top", | verticalAlign: "top", | ||||
| }} | }} | ||||
| @@ -497,5 +497,11 @@ | |||||
| "Handled By": "處理者", | "Handled By": "處理者", | ||||
| "submit": "提交", | "submit": "提交", | ||||
| "Received Qty": "接收數量", | "Received Qty": "接收數量", | ||||
| "bomWeighting": "物料清單" | |||||
| "bomWeighting": "物料清單", | |||||
| "Now": "現時", | |||||
| "Last updated": "最後更新", | |||||
| "Auto-refresh every 5 minutes": "每5分鐘自動刷新", | |||||
| "Auto-refresh every 10 minutes": "每10分鐘自動刷新", | |||||
| "Auto-refresh every 15 minutes": "每15分鐘自動刷新", | |||||
| "Auto-refresh every 1 minute": "每1分鐘自動刷新" | |||||
| } | } | ||||
| @@ -112,5 +112,11 @@ | |||||
| "Show Purchase Order Codes": "顯示採購訂單編號", | "Show Purchase Order Codes": "顯示採購訂單編號", | ||||
| "x/y orders received": "x/y張單已處理", | "x/y orders received": "x/y張單已處理", | ||||
| "Goods Receipt Status New": "採購單接收狀態", | "Goods Receipt Status New": "採購單接收狀態", | ||||
| "Status": "狀態" | |||||
| "Status": "狀態", | |||||
| "Now": "現時", | |||||
| "Last updated": "最後更新", | |||||
| "Auto-refresh every 5 minutes": "每5分鐘自動刷新", | |||||
| "Auto-refresh every 10 minutes": "每10分鐘自動刷新", | |||||
| "Auto-refresh every 15 minutes": "每15分鐘自動刷新", | |||||
| "Auto-refresh every 1 minute": "每1分鐘自動刷新" | |||||
| } | } | ||||
| @@ -151,6 +151,12 @@ | |||||
| "View Details": "查看詳情", | "View Details": "查看詳情", | ||||
| "Skip": "跳過", | "Skip": "跳過", | ||||
| "Handler": "提料員", | "Handler": "提料員", | ||||
| "Now": "現時", | |||||
| "Last updated": "最後更新", | |||||
| "Auto-refresh every 5 minutes": "每5分鐘自動刷新", | |||||
| "Auto-refresh every 10 minutes": "每10分鐘自動刷新", | |||||
| "Auto-refresh every 15 minutes": "每15分鐘自動刷新", | |||||
| "Auto-refresh every 1 minute": "每1分鐘自動刷新", | |||||
| "completed Job Order pick orders with Matching": "工單已完成提料和對料", | "completed Job Order pick orders with Matching": "工單已完成提料和對料", | ||||
| "No completed Job Order pick orders with matching found": "沒有相關記錄", | "No completed Job Order pick orders with matching found": "沒有相關記錄", | ||||
| @@ -4,6 +4,12 @@ | |||||
| "Today": "是日", | "Today": "是日", | ||||
| "Tomorrow": "翌日", | "Tomorrow": "翌日", | ||||
| "Day After Tomorrow": "後日", | "Day After Tomorrow": "後日", | ||||
| "Now": "現時", | |||||
| "Last updated": "最後更新", | |||||
| "Auto-refresh every 5 minutes": "每5分鐘自動刷新", | |||||
| "Auto-refresh every 10 minutes": "每10分鐘自動刷新", | |||||
| "Auto-refresh every 15 minutes": "每15分鐘自動刷新", | |||||
| "Auto-refresh every 1 minute": "每1分鐘自動刷新", | |||||
| "Floor": "樓層", | "Floor": "樓層", | ||||
| "All Floors": "所有樓層", | "All Floors": "所有樓層", | ||||
| "Store ID": "樓層", | "Store ID": "樓層", | ||||