Просмотр исходного кода

Dashboard: Goods Receipt Status

MergeProblem1
B.E.N.S.O.N 1 неделю назад
Родитель
Сommit
b58947b1e5
5 измененных файлов: 207 добавлений и 72 удалений
  1. +18
    -0
      src/app/api/dashboard/actions.ts
  2. +17
    -0
      src/app/api/dashboard/client.ts
  3. +134
    -72
      src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx
  4. +19
    -0
      src/i18n/en/dashboard.json
  5. +19
    -0
      src/i18n/zh/dashboard.json

+ 18
- 0
src/app/api/dashboard/actions.ts Просмотреть файл

@@ -190,3 +190,21 @@ export const testing = cache(async (queryParams?: Record<string, any>) => {
); );
} }
}); });

export interface GoodsReceiptStatusRow {
supplierId: number | null;
supplierName: string;
expectedNoOfDelivery: number;
noOfOrdersReceivedAtDock: number;
noOfItemsInspected: number;
noOfItemsWithIqcIssue: number;
noOfItemsCompletedPutAwayAtStore: number;
}

export const fetchGoodsReceiptStatus = cache(async (date?: string) => {
const url = date
? `${BASE_API_URL}/dashboard/goods-receipt-status?date=${date}`
: `${BASE_API_URL}/dashboard/goods-receipt-status`;

return await serverFetchJson<GoodsReceiptStatusRow[]>(url, { method: "GET" });
});

+ 17
- 0
src/app/api/dashboard/client.ts Просмотреть файл

@@ -0,0 +1,17 @@
"use client";

import {
fetchGoodsReceiptStatus,
type GoodsReceiptStatusRow,
} from "./actions";

export const fetchGoodsReceiptStatusClient = async (
date?: string,
): Promise<GoodsReceiptStatusRow[]> => {
return await fetchGoodsReceiptStatus(date);
};

export type { GoodsReceiptStatusRow };

export default fetchGoodsReceiptStatusClient;


+ 134
- 72
src/components/DashboardPage/goodsReceiptStatus/GoodsReceiptStatus.tsx Просмотреть файл

@@ -1,13 +1,9 @@
"use client"; "use client";


import React, { useState, useEffect, useCallback, useMemo, useRef } from 'react';
import React, { useState, useEffect, useCallback, useMemo } from 'react';
import { import {
Box, Box,
Typography, Typography,
FormControl,
InputLabel,
Select,
MenuItem,
Card, Card,
CardContent, CardContent,
Stack, Stack,
@@ -19,88 +15,112 @@ import {
TableRow, TableRow,
Paper, Paper,
CircularProgress, CircularProgress,
Chip
Button
} from '@mui/material'; } from '@mui/material';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { LocalizationProvider } from '@mui/x-date-pickers/LocalizationProvider';
import { AdapterDayjs } from '@mui/x-date-pickers/AdapterDayjs';
import { DatePicker } from '@mui/x-date-pickers/DatePicker';
import { fetchGoodsReceiptStatusClient, type GoodsReceiptStatusRow } from '@/app/api/dashboard/client';


interface GoodsReceiptStatusItem {
id: string;
}
const REFRESH_MS = 15 * 60 * 1000;


const GoodsReceiptStatus: React.FC = () => { const GoodsReceiptStatus: React.FC = () => {
const { t } = useTranslation("dashboard"); 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);
useEffect(() => {
setIsClient(true);
setCurrentTime(dayjs());
}, []);
const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs>(dayjs());
const [data, setData] = useState<GoodsReceiptStatusRow[]>([]);
const [loading, setLoading] = useState<boolean>(true);
const [lastUpdated, setLastUpdated] = useState<dayjs.Dayjs | null>(null);
const [screenCleared, setScreenCleared] = useState<boolean>(false);


const loadData = useCallback(async () => { const loadData = useCallback(async () => {
if (screenCleared) return;
try { try {
setData([]);
setLoading(true);
const dateParam = selectedDate.format('YYYY-MM-DD');
const result = await fetchGoodsReceiptStatusClient(dateParam);
setData(result ?? []);
setLastUpdated(dayjs());
} catch (error) { } catch (error) {
console.error('Error fetching goods receipt status:', error); console.error('Error fetching goods receipt status:', error);
setData([]);
} finally { } finally {
setLoading(false); setLoading(false);
} }
}, []);
}, [selectedDate, screenCleared]);


useEffect(() => { useEffect(() => {
if (screenCleared) return;
loadData(); loadData();
const refreshInterval = setInterval(() => { const refreshInterval = setInterval(() => {
loadData(); loadData();
}, 5 * 60 * 1000);
}, REFRESH_MS);
return () => clearInterval(refreshInterval); return () => clearInterval(refreshInterval);
}, [loadData]);
}, [loadData, screenCleared]);


useEffect(() => {
if (!isClient) return;
const timeInterval = setInterval(() => {
setCurrentTime(dayjs());
}, 60 * 1000);
return () => clearInterval(timeInterval);
}, [isClient]);


const filteredData = useMemo(() => {
if (!selectedFilter) return data;
return data.filter(item => true);
}, [data, selectedFilter]);
const selectedDateLabel = useMemo(() => {
return selectedDate.format('YYYY-MM-DD');
}, [selectedDate]);

if (screenCleared) {
return (
<Card sx={{ mb: 2 }}>
<CardContent>
<Stack direction="row" spacing={2} justifyContent="space-between" alignItems="center">
<Typography variant="body2" color="text.secondary">
{t("Screen cleared")}
</Typography>
<Button variant="contained" onClick={() => setScreenCleared(false)}>
{t("Restore Screen")}
</Button>
</Stack>
</CardContent>
</Card>
);
}


return ( return (
<Card sx={{ mb: 2 }}> <Card sx={{ mb: 2 }}>
<CardContent> <CardContent>
{/* 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') : '--:--:--'}
{/* Header */}
<Stack direction="row" spacing={2} sx={{ mb: 2 }} alignItems="center" flexWrap="wrap">
<Stack direction="row" spacing={1} alignItems="center">
<Typography variant="body2" sx={{ fontWeight: 600 }}>
{t("Date")}:
</Typography>
<LocalizationProvider dateAdapter={AdapterDayjs}>
<DatePicker
value={selectedDate}
onChange={(value) => {
if (!value) return;
setSelectedDate(value);
}}
slotProps={{
textField: {
size: "small",
sx: { minWidth: 160 }
}
}}
/>
</LocalizationProvider>
<Typography variant="caption" color="text.secondary">
{t("Allow to select Date to view history.")}
</Typography>
</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> </Typography>

<Button variant="outlined" color="inherit" onClick={() => setScreenCleared(true)}>
{t("Exit Screen")}
</Button>
</Stack> </Stack>


{/* Table */} {/* Table */}
@@ -114,38 +134,80 @@ const GoodsReceiptStatus: React.FC = () => {
<Table size="small" sx={{ minWidth: 1200 }}> <Table size="small" sx={{ minWidth: 1200 }}>
<TableHead> <TableHead>
<TableRow sx={{ backgroundColor: 'grey.100' }}> <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 */}
<TableCell sx={{ fontWeight: 600 }}>{t("Supplier")}</TableCell>
<TableCell sx={{ fontWeight: 600 }} align="center">{t("Expected No. of Delivery")}</TableCell>
<TableCell sx={{ fontWeight: 600 }} align="center">{t("No. of Orders Received at Dock")}</TableCell>
<TableCell sx={{ fontWeight: 600 }} align="center">{t("No. of Items Inspected")}</TableCell>
<TableCell sx={{ fontWeight: 600 }} align="center">{t("No. of Items with IQC Issue")}</TableCell>
<TableCell sx={{ fontWeight: 600 }} align="center">{t("No. of Items Completed Put Away at Store")}</TableCell>
</TableRow>
<TableRow sx={{ backgroundColor: 'grey.50' }}>
<TableCell>
<Typography variant="caption" color="text.secondary">
{t("Show Supplier Name")}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="caption" color="text.secondary">
{t("Based on Expected Delivery Date")}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="caption" color="text.secondary">
{t("Upon entry of DN and Lot No. for all items of the order")}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="caption" color="text.secondary">
{t("Upon any IQC decision received")}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="caption" color="text.secondary">
{t("Count any item with IQC defect in any IQC criteria")}
</Typography>
</TableCell>
<TableCell align="center">
<Typography variant="caption" color="text.secondary">
{t("Upon completion of put away for an material in order. Count no. of items being put away")}
</Typography>
</TableCell>
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
{filteredData.length === 0 ? (
{data.length === 0 ? (
<TableRow> <TableRow>
<TableCell colSpan={3} align="center">
<TableCell colSpan={6} align="center">
<Typography variant="body2" color="text.secondary"> <Typography variant="body2" color="text.secondary">
{t("No data available")}
{t("No data available")} ({selectedDateLabel})
</Typography> </Typography>
</TableCell> </TableCell>
</TableRow> </TableRow>
) : ( ) : (
filteredData.map((row, index) => (
data.map((row, index) => (
<TableRow <TableRow
key={row.id || index}
key={`${row.supplierId ?? 'na'}-${index}`}
sx={{ sx={{
'&:hover': { backgroundColor: 'grey.50' } '&:hover': { backgroundColor: 'grey.50' }
}} }}
> >
<TableCell> <TableCell>
{/* TODO: Add table cell content when implementing */}
-
{row.supplierName || '-'}
</TableCell> </TableCell>
<TableCell>
-
<TableCell align="center">
{row.expectedNoOfDelivery ?? 0}
</TableCell> </TableCell>
<TableCell>
-
<TableCell align="center">
{row.noOfOrdersReceivedAtDock ?? 0}
</TableCell>
<TableCell align="center">
{row.noOfItemsInspected ?? 0}
</TableCell>
<TableCell align="center">
{row.noOfItemsWithIqcIssue ?? 0}
</TableCell>
<TableCell align="center">
{row.noOfItemsCompletedPutAwayAtStore ?? 0}
</TableCell> </TableCell>
</TableRow> </TableRow>
)) ))


+ 19
- 0
src/i18n/en/dashboard.json Просмотреть файл

@@ -79,6 +79,25 @@
"Tomorrow": "Tomorrow", "Tomorrow": "Tomorrow",
"Day After Tomorrow": "Day After Tomorrow", "Day After Tomorrow": "Day After Tomorrow",
"Goods Receipt Status": "Goods Receipt Status", "Goods Receipt Status": "Goods Receipt Status",
"Date": "Date",
"Time": "Time",
"Allow to select Date to view history.": "Allow to select Date to view history.",
"Auto-refresh every 15 minutes": "Auto-refresh every 15 minutes",
"Exit Screen": "Exit Screen",
"Restore Screen": "Restore Screen",
"Screen cleared": "Screen cleared",
"Supplier": "Supplier",
"Expected No. of Delivery": "Expected No. of Delivery",
"No. of Orders Received at Dock": "No. of Orders Received at Dock",
"No. of Items Inspected": "No. of Items Inspected",
"No. of Items with IQC Issue": "No. of Items with IQC Issue",
"No. of Items Completed Put Away at Store": "No. of Items Completed Put Away at Store",
"Show Supplier Name": "Show Supplier Name",
"Based on Expected Delivery Date": "Based on Expected Delivery Date",
"Upon entry of DN and Lot No. for all items of the order": "Upon entry of DN and Lot No. for all items of the order",
"Upon any IQC decision received": "Upon any IQC decision received",
"Count any item with IQC defect in any IQC criteria": "Count any item with IQC defect in any IQC criteria",
"Upon completion of put away for an material in order. Count no. of items being put away": "Upon completion of put away for an material in order. Count no. of items being put away",
"Filter": "Filter", "Filter": "Filter",
"All": "All", "All": "All",
"Column 1": "Column 1", "Column 1": "Column 1",


+ 19
- 0
src/i18n/zh/dashboard.json Просмотреть файл

@@ -79,6 +79,25 @@
"Tomorrow": "翌日", "Tomorrow": "翌日",
"Day After Tomorrow": "後日", "Day After Tomorrow": "後日",
"Goods Receipt Status": "貨物接收狀態", "Goods Receipt Status": "貨物接收狀態",
"Date": "日期",
"Time": "時間",
"Allow to select Date to view history.": "可選擇日期查看歷史記錄。",
"Auto-refresh every 15 minutes": "每15分鐘自動刷新",
"Exit Screen": "退出畫面",
"Restore Screen": "恢復畫面",
"Screen cleared": "畫面已清除",
"Supplier": "供應商",
"Expected No. of Delivery": "預計送貨數",
"No. of Orders Received at Dock": "碼頭已收訂單數",
"No. of Items Inspected": "已檢驗貨品數",
"No. of Items with IQC Issue": "IQC異常貨品數",
"No. of Items Completed Put Away at Store": "已完成上架貨品數",
"Show Supplier Name": "顯示供應商名稱",
"Based on Expected Delivery Date": "按預計送貨日期統計",
"Upon entry of DN and Lot No. for all items of the order": "當訂單所有貨品已輸入DN及批號時",
"Upon any IQC decision received": "當收到任何IQC判定",
"Count any item with IQC defect in any IQC criteria": "統計任何IQC準則不合格的貨品",
"Upon completion of put away for an material in order. Count no. of items being put away": "當訂單物料完成上架。統計正在上架的貨品數",
"Filter": "篩選", "Filter": "篩選",
"All": "全部", "All": "全部",
"Column 1": "欄位1", "Column 1": "欄位1",


Загрузка…
Отмена
Сохранить