| @@ -18,6 +18,7 @@ import CollapsibleCard from "../CollapsibleCard"; | |||||
| import { EscalationResult } from "@/app/api/escalation"; | import { EscalationResult } from "@/app/api/escalation"; | ||||
| import EscalationLogTable from "./escalation/EscalationLogTable"; | import EscalationLogTable from "./escalation/EscalationLogTable"; | ||||
| import { TruckScheduleDashboard } from "./truckSchedule"; | import { TruckScheduleDashboard } from "./truckSchedule"; | ||||
| import { GoodsReceiptStatus } from "./goodsReceiptStatus"; | |||||
| type Props = { | type Props = { | ||||
| // iqc: IQCItems[] | undefined | // iqc: IQCItems[] | undefined | ||||
| escalationLogs: EscalationResult[] | escalationLogs: EscalationResult[] | ||||
| @@ -50,20 +51,28 @@ const DashboardPage: React.FC<Props> = ({ | |||||
| </CardContent> | </CardContent> | ||||
| </CollapsibleCard> | </CollapsibleCard> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | |||||
| <CollapsibleCard title={t("Goods Receipt Status")} defaultOpen={true}> | |||||
| <CardContent> | |||||
| <GoodsReceiptStatus /> | |||||
| </CardContent> | |||||
| </CollapsibleCard> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <CollapsibleCard | <CollapsibleCard | ||||
| title={`${t("Responsible Escalation List")} (${t("pending")} : ${ | title={`${t("Responsible Escalation List")} (${t("pending")} : ${ | ||||
| getPendingLog().length > 0 ? getPendingLog().length : t("No")})`} | getPendingLog().length > 0 ? getPendingLog().length : t("No")})`} | ||||
| showFilter={true} | showFilter={true} | ||||
| filterText={t("show completed logs")} | filterText={t("show completed logs")} | ||||
| // defaultOpen={getPendingLog().length > 0} // TODO Fix default not opening | |||||
| > | > | ||||
| <CardContent> | <CardContent> | ||||
| <EscalationLogTable items={escLog}/> | <EscalationLogTable items={escLog}/> | ||||
| </CardContent> | </CardContent> | ||||
| </CollapsibleCard> | </CollapsibleCard> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | |||||
| {/* Hidden: Progress chart - not in use currently */} | |||||
| {/* <Grid item xs={12}> | |||||
| <CollapsibleCard title={t("Progress chart")}> | <CollapsibleCard title={t("Progress chart")}> | ||||
| <CardContent> | <CardContent> | ||||
| <Grid container spacing={3}> | <Grid container spacing={3}> | ||||
| @@ -79,9 +88,10 @@ const DashboardPage: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| </CardContent> | </CardContent> | ||||
| </CollapsibleCard> | </CollapsibleCard> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| <Grid item xs={12}> | |||||
| {/* Hidden: Warehouse status - not in use currently */} | |||||
| {/* <Grid item xs={12}> | |||||
| <CollapsibleCard title={t("Warehouse status")}> | <CollapsibleCard title={t("Warehouse status")}> | ||||
| <CardContent> | <CardContent> | ||||
| <Grid container spacing={2}> | <Grid container spacing={2}> | ||||
| @@ -95,31 +105,10 @@ const DashboardPage: React.FC<Props> = ({ | |||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| {/*<Grid item xs={12} md={6}> | |||||
| <Grid container spacing={2}> | |||||
| <Grid item xs={12} sm={6}> | |||||
| <DashboardBox | |||||
| title={t("Temperature status")} | |||||
| value="--" | |||||
| unit="°C" | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12} sm={6}> | |||||
| <DashboardBox | |||||
| title={t("Humidity status")} | |||||
| value="--" | |||||
| unit="%" | |||||
| /> | |||||
| </Grid> | |||||
| <Grid item xs={12}> | |||||
| <DashboardLineChart /> | |||||
| </Grid> | |||||
| </Grid> | |||||
| </Grid>*/} | |||||
| </Grid> | </Grid> | ||||
| </CardContent> | </CardContent> | ||||
| </CollapsibleCard> | </CollapsibleCard> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| </Grid> | </Grid> | ||||
| </ThemeProvider> | </ThemeProvider> | ||||
| ); | ); | ||||
| @@ -0,0 +1,178 @@ | |||||
| "use client"; | |||||
| import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react'; | |||||
| import { | |||||
| Box, | |||||
| Typography, | |||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| Card, | |||||
| CardContent, | |||||
| Stack, | |||||
| Table, | |||||
| TableBody, | |||||
| TableCell, | |||||
| TableContainer, | |||||
| TableHead, | |||||
| TableRow, | |||||
| Paper, | |||||
| CircularProgress, | |||||
| Chip | |||||
| } from '@mui/material'; | |||||
| import { useTranslation } from 'react-i18next'; | |||||
| import dayjs from 'dayjs'; | |||||
| // Placeholder interface - to be defined when implementing details | |||||
| interface GoodsReceiptStatusItem { | |||||
| id: string; | |||||
| // Add fields as needed when implementing | |||||
| } | |||||
| const GoodsReceiptStatus: React.FC = () => { | |||||
| const { t } = useTranslation("dashboard"); | |||||
| const [selectedFilter, setSelectedFilter] = useState<string>(""); | |||||
| const [data, setData] = useState<GoodsReceiptStatusItem[]>([]); | |||||
| const [loading, setLoading] = useState<boolean>(false); | |||||
| const [currentTime, setCurrentTime] = useState<dayjs.Dayjs | null>(null); | |||||
| const [isClient, setIsClient] = useState<boolean>(false); | |||||
| // Set client flag and time on mount | |||||
| useEffect(() => { | |||||
| setIsClient(true); | |||||
| setCurrentTime(dayjs()); | |||||
| }, []); | |||||
| // Load data from API - placeholder for now | |||||
| const loadData = useCallback(async () => { | |||||
| try { | |||||
| // TODO: Implement API call when ready | |||||
| // const result = await fetchGoodsReceiptStatusClient(); | |||||
| // setData(result); | |||||
| setData([]); | |||||
| } catch (error) { | |||||
| console.error('Error fetching goods receipt status:', error); | |||||
| } finally { | |||||
| setLoading(false); | |||||
| } | |||||
| }, []); | |||||
| // Initial load and auto-refresh every 5 minutes | |||||
| useEffect(() => { | |||||
| loadData(); | |||||
| const refreshInterval = setInterval(() => { | |||||
| loadData(); | |||||
| }, 5 * 60 * 1000); // 5 minutes | |||||
| return () => clearInterval(refreshInterval); | |||||
| }, [loadData]); | |||||
| // Update current time every 1 minute | |||||
| useEffect(() => { | |||||
| if (!isClient) return; | |||||
| const timeInterval = setInterval(() => { | |||||
| setCurrentTime(dayjs()); | |||||
| }, 60 * 1000); // 1 minute | |||||
| return () => clearInterval(timeInterval); | |||||
| }, [isClient]); | |||||
| // Filter data by selected filter | |||||
| const filteredData = useMemo(() => { | |||||
| if (!selectedFilter) return data; | |||||
| return data.filter(item => true); // TODO: Implement filter logic | |||||
| }, [data, selectedFilter]); | |||||
| return ( | |||||
| <Card sx={{ mb: 2 }}> | |||||
| <CardContent> | |||||
| {/* Title */} | |||||
| <Typography variant="h5" sx={{ mb: 3, fontWeight: 600 }}> | |||||
| {t("Goods Receipt Status")} | |||||
| </Typography> | |||||
| {/* Filter */} | |||||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||||
| <FormControl sx={{ minWidth: 150 }} size="small"> | |||||
| <InputLabel id="filter-select-label" shrink={true}> | |||||
| {t("Filter")} | |||||
| </InputLabel> | |||||
| <Select | |||||
| labelId="filter-select-label" | |||||
| id="filter-select" | |||||
| value={selectedFilter} | |||||
| label={t("Filter")} | |||||
| onChange={(e) => setSelectedFilter(e.target.value)} | |||||
| displayEmpty | |||||
| > | |||||
| <MenuItem value="">{t("All")}</MenuItem> | |||||
| {/* TODO: Add filter options when implementing */} | |||||
| </Select> | |||||
| </FormControl> | |||||
| <Typography variant="body2" sx={{ alignSelf: 'center', color: 'text.secondary' }}> | |||||
| {t("Auto-refresh every 5 minutes")} | {t("Last updated")}: {isClient && currentTime ? currentTime.format('HH:mm:ss') : '--:--:--'} | |||||
| </Typography> | |||||
| </Stack> | |||||
| {/* Table */} | |||||
| <Box sx={{ mt: 2 }}> | |||||
| {loading ? ( | |||||
| <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}> | |||||
| <CircularProgress /> | |||||
| </Box> | |||||
| ) : ( | |||||
| <TableContainer component={Paper}> | |||||
| <Table size="small" sx={{ minWidth: 1200 }}> | |||||
| <TableHead> | |||||
| <TableRow sx={{ backgroundColor: 'grey.100' }}> | |||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 1")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 2")}</TableCell> | |||||
| <TableCell sx={{ fontWeight: 600 }}>{t("Column 3")}</TableCell> | |||||
| {/* TODO: Add table columns when implementing */} | |||||
| </TableRow> | |||||
| </TableHead> | |||||
| <TableBody> | |||||
| {filteredData.length === 0 ? ( | |||||
| <TableRow> | |||||
| <TableCell colSpan={3} align="center"> | |||||
| <Typography variant="body2" color="text.secondary"> | |||||
| {t("No data available")} | |||||
| </Typography> | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| ) : ( | |||||
| filteredData.map((row, index) => ( | |||||
| <TableRow | |||||
| key={row.id || index} | |||||
| sx={{ | |||||
| '&:hover': { backgroundColor: 'grey.50' } | |||||
| }} | |||||
| > | |||||
| <TableCell> | |||||
| {/* TODO: Add table cell content when implementing */} | |||||
| - | |||||
| </TableCell> | |||||
| <TableCell> | |||||
| - | |||||
| </TableCell> | |||||
| <TableCell> | |||||
| - | |||||
| </TableCell> | |||||
| </TableRow> | |||||
| )) | |||||
| )} | |||||
| </TableBody> | |||||
| </Table> | |||||
| </TableContainer> | |||||
| )} | |||||
| </Box> | |||||
| </CardContent> | |||||
| </Card> | |||||
| ); | |||||
| }; | |||||
| export default GoodsReceiptStatus; | |||||
| @@ -0,0 +1 @@ | |||||
| export { default as GoodsReceiptStatus } from './GoodsReceiptStatus'; | |||||
| @@ -72,5 +72,12 @@ | |||||
| "Tickets Completed": "Tickets Completed", | "Tickets Completed": "Tickets Completed", | ||||
| "Last Ticket End": "Last Ticket End", | "Last Ticket End": "Last Ticket End", | ||||
| "Pick Time (min)": "Pick Time (min)", | "Pick Time (min)": "Pick Time (min)", | ||||
| "No truck schedules available for today": "No truck schedules available for today" | |||||
| "No truck schedules available for today": "No truck schedules available for today", | |||||
| "Goods Receipt Status": "Goods Receipt Status", | |||||
| "Filter": "Filter", | |||||
| "All": "All", | |||||
| "Column 1": "Column 1", | |||||
| "Column 2": "Column 2", | |||||
| "Column 3": "Column 3", | |||||
| "No data available": "No data available" | |||||
| } | } | ||||
| @@ -72,5 +72,12 @@ | |||||
| "Tickets Completed": "已完成成品出倉單", | "Tickets Completed": "已完成成品出倉單", | ||||
| "Last Ticket End": "末單結束時間", | "Last Ticket End": "末單結束時間", | ||||
| "Pick Time (min)": "揀貨時間(分鐘)", | "Pick Time (min)": "揀貨時間(分鐘)", | ||||
| "No truck schedules available for today": "今日無車輛調度計劃" | |||||
| "No truck schedules available for today": "今日無車輛調度計劃", | |||||
| "Goods Receipt Status": "貨物接收狀態", | |||||
| "Filter": "篩選", | |||||
| "All": "全部", | |||||
| "Column 1": "欄位1", | |||||
| "Column 2": "欄位2", | |||||
| "Column 3": "欄位3", | |||||
| "No data available": "暫無資料" | |||||
| } | } | ||||