"use client"; import { Box, Button, Card, FormControl, MenuItem, Select, CardActions, CardContent, Chip, CircularProgress, LinearProgress, Dialog, DialogActions, DialogContent, DialogTitle, Grid, Stack, Tab, Tabs, Typography, } from "@mui/material"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { closeStockTakeRound, getStockTakeRoundCards, type AllPickedStockTakeListReponse, type StockTakeRoundCard, } from "@/app/api/stockTake/actions"; export function roundCardToSession(card: StockTakeRoundCard): AllPickedStockTakeListReponse { return { id: card.stockTakeRoundId, stockTakeSession: card.stockTakeRoundName || "", lastStockTakeDate: null, status: card.status, approverName: null, currentStockTakeItemNumber: 0, totalInventoryLotNumber: card.totalInventoryLotNumber || 0, stockTakeId: card.stockTakeId, stockTakeRoundId: card.stockTakeRoundId, stockTakerName: null, totalItemNumber: card.totalItemNumber || 0, startTime: null, endTime: null, planStartDate: card.planStartDate, stockTakeSectionDescription: null, warehouseArea: null, storeId: card.mainFloor, reStockTakeTrueFalse: card.reStockTakeTrueFalse, listMode: card.stockTakeRoundMode === "item" ? "item" : "section", }; } type RoundListView = "open" | "history"; interface StockTakeRoundCardListProps { isAdmin: boolean; onEnterRound: (card: StockTakeRoundCard, rounds: StockTakeRoundCard[]) => void; onCreateClick: () => void; creating?: boolean; refreshNonce?: number; yearMonth: string; onYearMonthChange: (yearMonth: string) => void; } const StockTakeRoundCardList: React.FC = ({ isAdmin, onEnterRound, onCreateClick, creating = false, refreshNonce = 0, yearMonth, onYearMonthChange, }) => { const { t, i18n } = useTranslation(["stockTake", "common"]); const [listView, setListView] = useState("open"); const [loading, setLoading] = useState(true); const [rounds, setRounds] = useState([]); const [completedYearMonths, setCompletedYearMonths] = useState([]); const [closingId, setClosingId] = useState(null); const [pendingCloseCard, setPendingCloseCard] = useState(null); const closeLockRef = useRef(false); const thisMonth = dayjs().format("YYYY-MM"); const load = useCallback(async (ym: string, view: RoundListView) => { setLoading(true); try { const res = await getStockTakeRoundCards(view === "history" ? ym : undefined, view); setRounds(Array.isArray(res.rounds) ? res.rounds : []); const months = Array.isArray(res.completedYearMonths) ? res.completedYearMonths : []; setCompletedYearMonths(months); } catch (e) { console.error("Failed to load stock take rounds:", e); setRounds([]); setCompletedYearMonths([]); } finally { setLoading(false); } }, []); useEffect(() => { void load(yearMonth, listView); }, [load, yearMonth, listView, refreshNonce]); // Every month from the earliest round through this month, including months with no round. const monthOptions = useMemo(() => { const known = completedYearMonths.filter((m) => m && m <= thisMonth); const earliest = known.reduce((a, b) => (a < b ? a : b), thisMonth); const months: string[] = []; let cursor = dayjs(`${earliest}-01`); const end = dayjs(`${thisMonth}-01`); if (!cursor.isValid() || !end.isValid()) return [thisMonth]; while (cursor.isBefore(end) || cursor.isSame(end, "month")) { months.push(cursor.format("YYYY-MM")); cursor = cursor.add(1, "month"); } return months.sort((a, b) => b.localeCompare(a)); }, [completedYearMonths, thisMonth]); const monthIndex = useMemo(() => { const i = monthOptions.indexOf(yearMonth); return i >= 0 ? i : 0; }, [monthOptions, yearMonth]); const selectedYm = monthOptions.includes(yearMonth) ? yearMonth : (monthOptions[0] || thisMonth); const selectedYear = selectedYm.slice(0, 4); const selectedMonth = selectedYm.slice(5, 7); const yearOptions = useMemo(() => { const years = Array.from(new Set(monthOptions.map((ym) => ym.slice(0, 4)))); return years.sort((a, b) => b.localeCompare(a)); }, [monthOptions]); const monthOptionsForYear = useMemo(() => { return monthOptions .filter((ym) => ym.startsWith(`${selectedYear}-`)) .map((ym) => ym.slice(5, 7)) .sort((a, b) => Number(b) - Number(a)); }, [monthOptions, selectedYear]); const formatYearMonth = (ym: string) => { const parsed = dayjs(`${ym}-01`); if (!parsed.isValid()) return ym; return i18n.language?.toLowerCase().startsWith("zh") ? parsed.format("YYYY年M月") : parsed.format("MMM YYYY"); }; const formatYearLabel = (year: string) => i18n.language?.toLowerCase().startsWith("zh") ? `${year}${t("Year suffix")}` : year; const formatMonthLabel = (month: string) => { const n = Number(month); if (!Number.isFinite(n)) return month; return i18n.language?.toLowerCase().startsWith("zh") ? `${n}${t("Month suffix")}` : dayjs().month(n - 1).format("MMM"); }; const olderMonth = monthOptions[monthIndex + 1]; const newerMonth = monthIndex > 0 ? monthOptions[monthIndex - 1] : undefined; const goMonth = (delta: number) => { if (listView !== "history" || monthOptions.length === 0) return; const next = monthIndex + delta; if (next < 0 || next >= monthOptions.length) return; onYearMonthChange(monthOptions[next]); }; const onYearChange = (year: string) => { const monthsInYear = monthOptions .filter((ym) => ym.startsWith(`${year}-`)) .map((ym) => ym.slice(5, 7)); const keepMonth = monthsInYear.includes(selectedMonth) ? selectedMonth : monthsInYear[0]; if (!keepMonth) return; onYearMonthChange(`${year}-${keepMonth}`); }; const onMonthChange = (month: string) => { onYearMonthChange(`${selectedYear}-${month}`); }; const handleCloseRound = async (card: StockTakeRoundCard) => { if (!isAdmin || closeLockRef.current) return; closeLockRef.current = true; setClosingId(card.stockTakeRoundId); try { await closeStockTakeRound(card.stockTakeRoundId); setPendingCloseCard(null); await load(yearMonth, listView); } catch (e) { console.error("Failed to close stock take round:", e); } finally { setClosingId(null); closeLockRef.current = false; } }; const roundLabel = (card: StockTakeRoundCard) => { const name = card.stockTakeRoundName?.trim(); if (name) return name; return `${t("Stock Take Round")} #${card.stockTakeRoundId}`; }; const progressRow = (label: string, done: number, total: number, barColor: "primary.main" | "success.main") => { const pct = total > 0 ? Math.min(100, (done / total) * 100) : 0; return ( {label} {t("Count of total", { done, total })} ); }; return ( setListView(next)} sx={{ mb: 1.5, minHeight: 36 }} > {listView === "open" ? t("Open rounds this month") : t("Completed rounds for month", { month: formatYearMonth(yearMonth) })} {listView === "history" && monthOptions.length > 0 ? ( <> ) : null} {loading ? ( ) : rounds.length === 0 ? ( {t("No data")} ) : ( {rounds.map((card) => { const isItemRound = card.stockTakeRoundMode === "item"; const total = isItemRound ? (card.totalInventoryLotNumber || 0) : (card.sectionCount || 0); const taken = card.stockTakenCount || 0; const approved = card.approvedCount || 0; const floor = card.mainFloor?.trim(); const modeLabel = isItemRound ? t("Create by item") : t("Create by section"); return ( {roundLabel(card)} {floor ? ( {floor} ) : null} {t("Plan Start Date")}:{" "} {card.planStartDate ? dayjs(card.planStartDate).format(OUTPUT_DATE_FORMAT) : "-"} {isItemRound ? t("Round unit lots") : t("Round unit sections")} {progressRow(t("progressCounted"), taken, total, "primary.main")} {progressRow(t("progressApproved"), approved, total, "success.main")} {isAdmin && card.open ? ( ) : null} ); })} )} { if (closingId == null) setPendingCloseCard(null); }} maxWidth="sm" fullWidth > {t("Confirm close round")} {t("Close round confirm message")} ); }; type StatusChipColor = "default" | "primary" | "secondary" | "error" | "info" | "success" | "warning"; export function statusChipColor(status: string): StatusChipColor { const s = (status || "").toLowerCase().replace(/_/g, ""); if (s === "completed") return "success"; if (s === "pending") return "warning"; if (s === "approving") return "info"; if (s === "stocktaking" || s === "processing" || s === "inprogress") return "primary"; if (s === "notmatch") return "warning"; return "default"; } export default StockTakeRoundCardList;