|
- "use client";
-
- import {
- Box,
- Button,
- Card,
- CardContent,
- CardActions,
- Stack,
- Typography,
- Chip,
- CircularProgress,
- TablePagination,
- Grid,
- LinearProgress,
- } from "@mui/material";
- import { useState, useCallback, useEffect } from "react";
- import { useTranslation } from "react-i18next";
- import {
- getApproverStockTakeRecords,
- AllPickedStockTakeListReponse,
-
- } from "@/app/api/stockTake/actions";
- import dayjs from "dayjs";
- import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
-
- const PER_PAGE = 6;
-
- interface ApproverCardListProps {
- onCardClick: (session: AllPickedStockTakeListReponse) => void;
- }
-
- const ApproverCardList: React.FC<ApproverCardListProps> = ({ onCardClick }) => {
- const { t } = useTranslation(["inventory", "common"]);
-
- const [loading, setLoading] = useState(false);
- const [stockTakeSessions, setStockTakeSessions] = useState<AllPickedStockTakeListReponse[]>([]);
- const [page, setPage] = useState(0);
- const [creating, setCreating] = useState(false);
-
- const fetchStockTakeSessions = useCallback(async () => {
- setLoading(true);
- try {
- const data = await getApproverStockTakeRecords();
- setStockTakeSessions(Array.isArray(data) ? data : []);
- setPage(0);
- } catch (e) {
- console.error(e);
- setStockTakeSessions([]);
- } finally {
- setLoading(false);
- }
- }, []);
-
- useEffect(() => {
- fetchStockTakeSessions();
- }, [fetchStockTakeSessions]);
-
- const startIdx = page * PER_PAGE;
- const paged = stockTakeSessions.slice(startIdx, startIdx + PER_PAGE);
- const TimeDisplay: React.FC<{ startTime: string | null; endTime: string | null }> = ({ startTime, endTime }) => {
- const [currentTime, setCurrentTime] = useState(dayjs());
-
- useEffect(() => {
- if (!endTime && startTime) {
- const interval = setInterval(() => {
- setCurrentTime(dayjs());
- }, 1000); // 每秒更新一次
-
- return () => clearInterval(interval);
- }
- }, [startTime, endTime]);
-
- if (endTime && startTime) {
- // 当有结束时间时,计算从开始到结束的持续时间
- const start = dayjs(startTime);
- const end = dayjs(endTime);
- const duration = dayjs.duration(end.diff(start));
- const hours = Math.floor(duration.asHours());
- const minutes = duration.minutes();
- const seconds = duration.seconds();
-
- return (
- <>
- {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}
- </>
- );
- } else if (startTime) {
- // 当没有结束时间时,显示实时计时器
- const start = dayjs(startTime);
- const duration = dayjs.duration(currentTime.diff(start));
- const hours = Math.floor(duration.asHours());
- const minutes = duration.minutes();
- const seconds = duration.seconds();
-
- return (
- <>
- {hours.toString().padStart(2, '0')}:{minutes.toString().padStart(2, '0')}:{seconds.toString().padStart(2, '0')}
- </>
- );
- } else {
- return <>-</>;
- }
- };
- const startTimeDisplay = (startTime: string | null) => {
- if (startTime) {
- const start = dayjs(startTime);
- return start.format("HH:mm");
- } else {
- return "-";
- }
- };
- const endTimeDisplay = (endTime: string | null) => {
- if (endTime) {
- const end = dayjs(endTime);
- return end.format("HH:mm");
- } else {
- return "-";
- }
- };
-
- const getStatusColor = (status: string | null) => {
- if (!status) return "default";
- const statusLower = status.toLowerCase();
- if (statusLower === "completed") return "success";
- if (statusLower === "in_progress" || statusLower === "processing") return "primary";
- if (statusLower === "no_cycle") return "default";
- if (statusLower === "approving") return "info";
- return "warning";
- };
-
- const getCompletionRate = (session: AllPickedStockTakeListReponse): number => {
- if (session.totalInventoryLotNumber === 0) return 0;
- return Math.round((session.currentStockTakeItemNumber / session.totalInventoryLotNumber) * 100);
- };
-
- if (loading) {
- return (
- <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
- <CircularProgress />
- </Box>
- );
- }
-
- return (
- <Box>
- <Box sx={{ display: "flex", justifyContent: "space-between", alignItems: "center", mb: 2 }}>
- <Typography variant="body2" color="text.secondary">
- {t("Total Sections")}: {stockTakeSessions.length}
- </Typography>
- </Box>
-
- <Grid container spacing={2}>
- {paged.map((session) => {
- const statusColor = getStatusColor(session.status);
- const lastStockTakeDate = session.lastStockTakeDate
- ? dayjs(session.lastStockTakeDate).format(OUTPUT_DATE_FORMAT)
- : "-";
- const completionRate = getCompletionRate(session);
- const isDisabled = session.status === null;
-
- return (
- <Grid key={session.id} item xs={12} sm={6} md={4}>
- <Card
- sx={{
- minHeight: 200,
- display: "flex",
- flexDirection: "column",
- border: "1px solid",
- borderColor: statusColor === "success" ? "success.main" : "primary.main",
- cursor: isDisabled ? "not-allowed" : "pointer",
- opacity: isDisabled ? 0.6 : 1,
- "&:hover": {
- boxShadow: isDisabled ? 0 : 4,
- },
- }}
- onClick={() => {
- if (!isDisabled && session.status !== null) {
- onCardClick(session);
- }
- }}
- >
- <CardContent sx={{ pb: 1, flexGrow: 1 }}>
- <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}>
- <Typography variant="subtitle1" fontWeight={600}>
- {t("Section")}: {session.stockTakeSession}
- </Typography>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
- {t("Last Stock Take Date")}: {lastStockTakeDate || "-"}
- </Typography>
- </Stack>
-
- <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>{t("Stock Taker")}: {session.stockTakerName || "-"}</Typography>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>{t("Approver")}: {session.approverName || "-"}</Typography>
- </Stack>
- <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 1 }}>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>{t("start time")}: {startTimeDisplay(session.startTime) || "-"}</Typography>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>{t("end time")}: {endTimeDisplay(session.endTime) || "-"}</Typography>
- </Stack>
- <Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
- {t("Control Time")}: <TimeDisplay startTime={session.startTime} endTime={session.endTime} />
- </Typography>
- {session.totalInventoryLotNumber > 0 && (
- <Box sx={{ mt: 2 }}>
- <Stack direction="row" justifyContent="space-between" alignItems="center" sx={{ mb: 0.5 }}>
- <Typography variant="body2" fontWeight={600}>
- {t("Progress")}
- </Typography>
- <Typography variant="body2" fontWeight={600}>
- {completionRate}%
- </Typography>
- </Stack>
- <LinearProgress
- variant="determinate"
- value={completionRate}
- sx={{ height: 8, borderRadius: 1 }}
- />
- </Box>
- )}
- </CardContent>
-
- <CardActions sx={{ pt: 0.5 ,justifyContent: "space-between"}}>
- <Button
- variant="contained"
- size="small"
- disabled={isDisabled}
- onClick={(e) => {
- e.stopPropagation();
- if (!isDisabled) {
- onCardClick(session);
- }
- }}
- >
- {t("View Details")}
-
- </Button>
- {session.status ? (
- <Chip size="small" label={t(session.status)} color={statusColor as any} />
- ) : (
- <Chip size="small" label={t(" ")} color="default" />
- )}
- </CardActions>
- </Card>
- </Grid>
- );
- })}
- </Grid>
-
- {stockTakeSessions.length > 0 && (
- <TablePagination
- component="div"
- count={stockTakeSessions.length}
- page={page}
- rowsPerPage={PER_PAGE}
- onPageChange={(e, p) => setPage(p)}
- rowsPerPageOptions={[PER_PAGE]}
- />
- )}
- </Box>
- );
- };
-
- export default ApproverCardList;
|