ソースを参照

dashboards formatting (keep same)

reset-do-picking-order
kelvin.yau 2週間前
コミット
19b4ed534c
11個のファイルの変更225行の追加101行の削除
  1. +16
    -6
      src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatusNew.tsx
  2. +27
    -16
      src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx
  3. +38
    -23
      src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx
  4. +28
    -11
      src/components/Jodetail/MaterialPickStatusTable.tsx
  5. +44
    -25
      src/components/ProductionProcess/EquipmentStatusDashboard.tsx
  6. +23
    -9
      src/components/ProductionProcess/JobProcessStatus.tsx
  7. +23
    -9
      src/components/ProductionProcess/OperatorKpiDashboard.tsx
  8. +7
    -1
      src/i18n/zh/common.json
  9. +7
    -1
      src/i18n/zh/dashboard.json
  10. +6
    -0
      src/i18n/zh/jo.json
  11. +6
    -0
      src/i18n/zh/ticketReleaseTable.json

+ 16
- 6
src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatusNew.tsx ファイルの表示

@@ -62,6 +62,12 @@ const GoodsReceiptStatusNew: React.FC = () => {
return () => clearInterval(refreshInterval);
}, [loadData, screenCleared]);

const [now, setNow] = useState(dayjs());

useEffect(() => {
const timer = setInterval(() => setNow(dayjs()), 60 * 1000);
return () => clearInterval(timer);
}, []);

const selectedDateLabel = useMemo(() => {
return selectedDate.format('YYYY-MM-DD');
@@ -187,10 +193,14 @@ const GoodsReceiptStatusNew: React.FC = () => {
</Stack>

<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)}>
{t("Exit Screen")}
@@ -204,7 +214,7 @@ const GoodsReceiptStatusNew: React.FC = () => {
<CircularProgress />
</Box>
) : (
<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}>
<Table
size="small"
sx={{
@@ -212,7 +222,7 @@ const GoodsReceiptStatusNew: React.FC = () => {
tableLayout: 'fixed',
}}
>
<TableHead>
<TableHead sx={{ backgroundColor: 'grey.100', position: 'sticky', top: 0, zIndex: 1 }}>
<TableRow sx={{ backgroundColor: 'grey.100' }}>
<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>


+ 27
- 16
src/components/DashboardPage/truckSchedule/TruckScheduleDashboard.tsx ファイルの表示

@@ -74,7 +74,7 @@ const TruckScheduleDashboard: React.FC = () => {
['tomorrow', 0],
['dayAfterTomorrow', 0]
]));
const [now, setNow] = useState(dayjs());
// Save completed tracker state to sessionStorage
const saveCompletedTrackerToStorage = useCallback(() => {
if (typeof window === 'undefined') return;
@@ -377,6 +377,11 @@ const TruckScheduleDashboard: React.FC = () => {
}
}, [processDataForDate]);

useEffect(() => {
const timer = setInterval(() => setNow(dayjs()), 60 * 1000);
return () => clearInterval(timer);
}, []);

// Initial load and auto-refresh every 5 minutes
useEffect(() => {
loadData(true); // Initial load - show spinner
@@ -483,11 +488,17 @@ const TruckScheduleDashboard: React.FC = () => {
<MenuItem value="dayAfterTomorrow">{t("Day After Tomorrow")} ({getDateLabel(2)})</MenuItem>
</Select>
</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>
</Stack>
</Stack>

{/* Table */}
<Box sx={{ mt: 2 }}>
@@ -496,20 +507,20 @@ const TruckScheduleDashboard: React.FC = () => {
<CircularProgress />
</Box>
) : (
<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}>
<Table size="small" sx={{ minWidth: 1200 }}>
<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("Truck Schedule")}</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("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>
</TableHead>
<TableBody>
@@ -561,26 +572,26 @@ const TruckScheduleDashboard: React.FC = () => {
sx={{ fontWeight: 600 }}
/>
</TableCell>
<TableCell align="center">
<TableCell align="right">
<Typography variant="body2">
{row.numberOfShopsToServe} [{row.numberOfPickTickets}]
</Typography>
</TableCell>
<TableCell align="center">
<TableCell align="right">
<Chip
label={row.numberOfTicketsReleased}
size="small"
color={row.numberOfTicketsReleased > 0 ? 'info' : 'default'}
/>
</TableCell>
<TableCell align="center">
<TableCell align="right">
<Chip
label={row.numberOfTicketsCompleted}
size="small"
color={row.numberOfTicketsCompleted > 0 ? 'success' : 'default'}
/>
</TableCell>
<TableCell align="center">
<TableCell align="right">
<Typography variant="body2" sx={{ fontWeight: 500 }}>
{row.totalItemsToPick}
</Typography>
@@ -591,7 +602,7 @@ const TruckScheduleDashboard: React.FC = () => {
<TableCell>
{formatDateTime(row.lastTicketEndTime)}
</TableCell>
<TableCell align="center">
<TableCell align="right">
<Typography
variant="body2"
sx={{


+ 38
- 23
src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx ファイルの表示

@@ -40,6 +40,8 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
pageSize: 5,
});

const [now, setNow] = useState(dayjs());
const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null);
const formatTime = (timeData: any): string => {
if (!timeData) return '';
@@ -74,21 +76,25 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
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) => {
// Filter by floor if selected
@@ -214,6 +220,16 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
<MenuItem value="completed">{t("completed")}</MenuItem>
</Select>
</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>

<Box sx={{ mt: 2 }}>
@@ -223,10 +239,10 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</Box>
) : (
<>
<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}>
<Table size="small" sx={{ minWidth: 650 }}>
<TableHead>
<TableRow>
<TableRow sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'grey.100' }}>
<TableCell>{t("Store ID")}</TableCell>
<TableCell>{t("Required Delivery Date")}</TableCell>
<TableCell>
@@ -242,7 +258,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
{/*<TableCell>{t("Truck Departure Time")}</TableCell>
<TableCell>{t("Truck Lane Code")}</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("Pick Order Code(s)")}</TableCell>
<TableCell>{t("Ticket Number")}</TableCell>
@@ -263,7 +279,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</Box>
</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>
</TableHead>
<TableBody>
@@ -309,9 +325,9 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</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.pickOrderCode || '-'}</TableCell>
<TableCell align="right">{row.pickOrderCode || '-'}</TableCell>
<TableCell>{row.ticketNo || '-'}</TableCell>
<TableCell>
{row.ticketReleaseTime
@@ -360,8 +376,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => {
</Typography>
</Box>
</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>
);
})


+ 28
- 11
src/components/Jodetail/MaterialPickStatusTable.tsx ファイルの表示

@@ -37,7 +37,8 @@ const MaterialPickStatusTable: React.FC = () => {
pageNum: 0,
pageSize: 10,
});

const [now, setNow] = useState(dayjs());
const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null);
const loadData = useCallback(async () => {
setLoading(true);
@@ -49,6 +50,7 @@ const MaterialPickStatusTable: React.FC = () => {
setData(result || []);
}
refreshCountRef.current += 1;
setLastDataRefreshTime(dayjs());
} catch (error) {
console.error('Error fetching material pick status:', error);
setData([]);
@@ -65,6 +67,11 @@ const MaterialPickStatusTable: React.FC = () => {
return () => clearInterval(interval);
}, [loadData]);

useEffect(() => {
const timer = setInterval(() => setNow(dayjs()), 60 * 1000);
return () => clearInterval(timer);
}, []);

const formatTime = (timeData: any): string => {
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>
</Select>
</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>

<Box sx={{ mt: 2 }}>
@@ -254,9 +271,9 @@ const MaterialPickStatusTable: React.FC = () => {
</Box>
) : (
<>
<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}>
<Table size="small" sx={{ minWidth: 650 }}>
<TableHead>
<TableHead sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'grey.100' }}>
<TableRow>
<TableCell>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
@@ -267,7 +284,7 @@ const MaterialPickStatusTable: React.FC = () => {
</Box>
</TableCell>
<TableCell>
<TableCell align="right">
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("Job Order Qty")}
@@ -276,7 +293,7 @@ const MaterialPickStatusTable: React.FC = () => {
</Box>
</TableCell>
<TableCell>
<TableCell align="right">
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("No. of Items to be Picked")}
@@ -284,7 +301,7 @@ const MaterialPickStatusTable: React.FC = () => {
</Box>
</TableCell>
<TableCell>
<TableCell align="right">
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("No. of Items with Issue During Pick")}
@@ -308,7 +325,7 @@ const MaterialPickStatusTable: React.FC = () => {
</Box>
</TableCell>
<TableCell sx={{
<TableCell align="right" sx={{
}}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
@@ -343,16 +360,16 @@ const MaterialPickStatusTable: React.FC = () => {
</TableCell>
<TableCell>
<TableCell align="right">
{row.jobOrderQty !== null && row.jobOrderQty !== undefined
? `${row.jobOrderQty} ${row.uom || ''}`
: '-'}
</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.pickEndTime) || '-'}</TableCell>
<TableCell sx={{
<TableCell align="right" sx={{
backgroundColor: 'rgba(76, 175, 80, 0.1)',
fontWeight: 600
}}>


+ 44
- 25
src/components/ProductionProcess/EquipmentStatusDashboard.tsx ファイルの表示

@@ -17,6 +17,7 @@ import {
Tabs,
Tab,
Chip,
Stack
} from "@mui/material";
import { useTranslation } from "react-i18next";
import dayjs from "dayjs";
@@ -34,7 +35,7 @@ const STATUS_COLORS: Record<string, "success" | "default" | "warning" | "error">
Idle: "default",
Repair: "warning",
};
const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null);
const formatDateTime = (value: any): string => {
if (!value) return "-";

@@ -132,12 +133,14 @@ const EquipmentStatusDashboard: React.FC = () => {
const [data, setData] = useState<EquipmentStatusByTypeResponse[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [tabIndex, setTabIndex] = useState<number>(0);
const [now, setNow] = useState(dayjs());

const loadData = useCallback(async () => {
setLoading(true);
try {
const result = await fetchEquipmentStatus();
setData(result || []);
setLastDataRefreshTime(dayjs());
} catch (error) {
console.error("Error fetching equipment status:", error);
setData([]);
@@ -163,6 +166,11 @@ const EquipmentStatusDashboard: React.FC = () => {
return () => clearInterval(timer);
}, []);

useEffect(() => {
const timer = setInterval(() => setNow(dayjs()), 60 * 1000);
return () => clearInterval(timer);
}, []);

const handleTabChange = (_: React.SyntheticEvent, newValue: number) => {
setTabIndex(newValue);
};
@@ -180,21 +188,32 @@ const EquipmentStatusDashboard: React.FC = () => {
</Typography>
</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 ? (
<Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
@@ -223,17 +242,17 @@ const EquipmentStatusDashboard: React.FC = () => {
{type.equipmentTypeName || "-"}
</Typography>

<TableContainer component={Paper}>
<TableContainer component={Paper} sx={{ maxHeight: 440, overflow: 'auto' }}>
<Table size="small">
<TableHead>
<TableRow>
<TableRow sx={{ position: 'sticky', top: 0, zIndex: 1, backgroundColor: 'background.paper' }}>
<TableCell>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("Equipment Name and Code")}
</Typography>
</TableCell>
{details.map((d) => (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
<Box sx={{ display: "flex", flexDirection: "column" }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{d.equipmentDetailName || "-"}
@@ -255,7 +274,7 @@ const EquipmentStatusDashboard: React.FC = () => {
</Typography>
</TableCell>
{details.map((d) => (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
{d.status === "Processing" ? d.currentProcess?.processName || "-" : "-"}
</TableCell>
))}
@@ -275,7 +294,7 @@ const EquipmentStatusDashboard: React.FC = () => {
// Processing 时只显示 job order code,不显示 Chip
if (d.status === "Processing" && cp?.jobOrderCode) {
return (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
<Typography variant="body2" sx={{ fontWeight: 500 }}>
{cp.jobOrderCode}
</Typography>
@@ -285,7 +304,7 @@ const EquipmentStatusDashboard: React.FC = () => {
// 其他状态显示 Chip
return (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
<Chip label={t(`${d.status}`)} color={chipColor} size="small" />
</TableCell>
);
@@ -302,7 +321,7 @@ const EquipmentStatusDashboard: React.FC = () => {
</Typography>
</TableCell>
{details.map((d) => (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
{d.status === "Processing"
? formatDateTime(d.currentProcess?.startTime)
: "-"}
@@ -318,7 +337,7 @@ const EquipmentStatusDashboard: React.FC = () => {
</Typography>
</TableCell>
{details.map((d) => (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell key={d.equipmentDetailId}>
{d.status === "Processing"
? calculateEstimatedCompletionTime(
d.currentProcess?.startTime,
@@ -337,7 +356,7 @@ const EquipmentStatusDashboard: React.FC = () => {
</Typography>
</TableCell>
{details.map((d) => (
<TableCell key={d.equipmentDetailId} align="center">
<TableCell align="right" key={d.equipmentDetailId}>
{d.status === "Processing"
? calculateRemainingTime(
d.currentProcess?.startTime,


+ 23
- 9
src/components/ProductionProcess/JobProcessStatus.tsx ファイルの表示

@@ -31,6 +31,7 @@ const JobProcessStatus: React.FC = () => {
const refreshCountRef = useRef<number>(0);
const [currentTime, setCurrentTime] = useState(dayjs());
const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD"));
const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null);

// Update current time every second for countdown
useEffect(() => {
@@ -46,6 +47,7 @@ const JobProcessStatus: React.FC = () => {
const result = await fetchJobProcessStatus(selectedDate);
setData(result);
refreshCountRef.current += 1;
setLastDataRefreshTime(dayjs());
} catch (error) {
console.error('Error fetching job process status:', error);
setData([]);
@@ -189,6 +191,16 @@ const JobProcessStatus: React.FC = () => {
<MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>前天</MenuItem>
</Select>
</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>
<Box sx={{ mt: 2 }}>
{loading ? (
@@ -200,12 +212,14 @@ const JobProcessStatus: React.FC = () => {
component={Paper}
sx={{
border: '3px solid #135fed',
overflowX: 'auto', // 关键:允许横向滚动
overflowX: 'auto',
maxHeight: 440,
overflow: 'auto'
}}
>
<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' }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
{t("Job Order No.")}
@@ -223,18 +237,18 @@ const JobProcessStatus: React.FC = () => {
</TableCell>
</TableRow>
<TableRow>
<TableRow sx={{ backgroundColor: 'grey.100' }}>
{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 }}>
{t("Process")} {num}
</Typography>
</TableCell>
))}
</TableRow>
<TableRow>
<TableRow sx={{ backgroundColor: 'grey.100' }}>
{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 }}>
<Typography variant="caption" sx={{ fontWeight: 600 }}>
{t("Start")}
@@ -284,7 +298,7 @@ const JobProcessStatus: React.FC = () => {
// 如果工序不是必需的,只显示一个 N/A
if (!process.isRequired) {
return (
<TableCell key={index} align="center" sx={{ padding: '16px 20px', minWidth: 150 }}>
<TableCell key={index} sx={{ padding: '16px 20px', minWidth: 150 }}>
<Typography variant="body2">
N/A
</Typography>
@@ -298,7 +312,7 @@ const JobProcessStatus: React.FC = () => {
].filter(Boolean).join(" ");
// 如果工序是必需的,显示三行(Start、Finish、Wait Time)
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 }}>
<Typography variant="body2" sx={{ mb: 0.5 }}>{label || "-"}</Typography>
<Typography variant="body2" sx={{ py: 0.5 }}>


+ 23
- 9
src/components/ProductionProcess/OperatorKpiDashboard.tsx ファイルの表示

@@ -32,6 +32,8 @@ const OperatorKpiDashboard: React.FC = () => {
const [loading, setLoading] = useState<boolean>(true);
const [selectedDate, setSelectedDate] = useState(dayjs().format("YYYY-MM-DD"));
const refreshCountRef = useRef<number>(0);
const [now, setNow] = useState(dayjs());
const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | null>(null);

const formatTime = (timeData: any): string => {
if (!timeData) return "-";
@@ -69,6 +71,7 @@ const OperatorKpiDashboard: React.FC = () => {
try {
const result = await fetchOperatorKpi(selectedDate);
setData(result);
setLastDataRefreshTime(dayjs());
refreshCountRef.current += 1;
} catch (error) {
console.error("Error fetching operator KPI:", error);
@@ -86,6 +89,11 @@ const OperatorKpiDashboard: React.FC = () => {
return () => clearInterval(interval);
}, [loadData]);

useEffect(() => {
const timer = setInterval(() => setNow(dayjs()), 60 * 1000);
return () => clearInterval(timer);
}, []);

const renderCurrentProcesses = (processes: OperatorKpiProcessInfo[]) => {
if (!processes || processes.length === 0) {
return (
@@ -167,6 +175,16 @@ const OperatorKpiDashboard: React.FC = () => {
<MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem>
</Select>
</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>

{loading ? (
@@ -174,25 +192,22 @@ const OperatorKpiDashboard: React.FC = () => {
<CircularProgress />
</Box>
) : (
<TableContainer
<TableContainer sx={{ border: "3px solid #135fed", overflowX: "auto", maxHeight: 440, overflow: 'auto' }}
component={Paper}
sx={{
border: "3px solid #135fed",
overflowX: "auto",
}}
>
<Table size="small" sx={{ minWidth: 800 }}>
<TableHead>
<TableRow
<TableRow
sx={{
bgcolor: "#424242",
"& th": {
borderBottom: "none",
py: 1.5,
position: 'sticky', top: 0, zIndex: 1
},
}}
>
<TableCell sx={{ width: 80 }}>
<TableCell align="right" sx={{ width: 80 }}>
<Typography
variant="subtitle2"
sx={{
@@ -252,10 +267,9 @@ const OperatorKpiDashboard: React.FC = () => {
},
}}
>
<TableCell
<TableCell align="right"
sx={{
width: 80,
textAlign: "center",
fontWeight: 500,
verticalAlign: "top",
}}


+ 7
- 1
src/i18n/zh/common.json ファイルの表示

@@ -497,5 +497,11 @@
"Handled By": "處理者",
"submit": "提交",
"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分鐘自動刷新"
}

+ 7
- 1
src/i18n/zh/dashboard.json ファイルの表示

@@ -112,5 +112,11 @@
"Show Purchase Order Codes": "顯示採購訂單編號",
"x/y orders received": "x/y張單已處理",
"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分鐘自動刷新"
}

+ 6
- 0
src/i18n/zh/jo.json ファイルの表示

@@ -151,6 +151,12 @@
"View Details": "查看詳情",
"Skip": "跳過",
"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": "工單已完成提料和對料",
"No completed Job Order pick orders with matching found": "沒有相關記錄",


+ 6
- 0
src/i18n/zh/ticketReleaseTable.json ファイルの表示

@@ -4,6 +4,12 @@
"Today": "是日",
"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": "樓層",
"All Floors": "所有樓層",
"Store ID": "樓層",


読み込み中…
キャンセル
保存