From 2b5e416006c692eabf3f101485cd5543d4456195 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Fri, 23 Oct 2026 11:30:01 +0800 Subject: [PATCH] ui update --- src/app/api/stockTake/actions.ts | 6 +- .../ApproverStockTakeAll.tsx | 134 ++++++++------ .../StockTakeRoundCardList.tsx | 174 ++++++++++++------ src/i18n/en/stockTake.json | 21 ++- src/i18n/zh/stockTake.json | 21 ++- 5 files changed, 232 insertions(+), 124 deletions(-) diff --git a/src/app/api/stockTake/actions.ts b/src/app/api/stockTake/actions.ts index b4863138..ab467856 100644 --- a/src/app/api/stockTake/actions.ts +++ b/src/app/api/stockTake/actions.ts @@ -384,9 +384,13 @@ export type StockTakeRoundCardsRes = { completedYearMonths: string[]; }; -export const getStockTakeRoundCards = async (yearMonth?: string | null) => { +export const getStockTakeRoundCards = async ( + yearMonth?: string | null, + view?: "open" | "history" | null, +) => { const params = new URLSearchParams(); if (yearMonth?.trim()) params.set("yearMonth", yearMonth.trim()); + if (view === "open" || view === "history") params.set("view", view); const qs = params.toString(); return serverFetchJson( `${BASE_API_URL}/stockTakeRecord/stockTakeRoundCards${qs ? `?${qs}` : ""}`, diff --git a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx index 99286147..f1bbe23d 100644 --- a/src/components/StockTakeManagement/ApproverStockTakeAll.tsx +++ b/src/components/StockTakeManagement/ApproverStockTakeAll.tsx @@ -25,12 +25,10 @@ import { InputLabel, Select, MenuItem, - FormControlLabel, Tab, Tabs, ToggleButton, ToggleButtonGroup, - Checkbox, Dialog, DialogTitle, DialogContent, @@ -1645,36 +1643,74 @@ const ApproverStockTakeAll: React.FC = ({ const batchButtonInTabBar = Boolean(batchButtonHost && batchSaveButton); const searchFieldCount = (searchKind === "section" ? 2 : 0) + 1 + (mode === "pending" ? 1 : 0) + 1; - const varianceColumn = ((searchFieldCount - 1) % 3) + 1; const rowFillers = searchFieldCount % 3 === 0 ? 0 : 3 - (searchFieldCount % 3); - const varianceFilterControl = ( - <> - { - setSearchVarianceFilterInclusive(e.target.checked); - handleSearch({ varianceFilterInclusive: e.target.checked }); - }} - /> + const varianceToleranceLabel = searchVariancePercentTolerance || "0"; + const varianceOutOfRange = !searchVarianceFilterInclusive; + const varianceRangeBar = ( + + { + if (next == null) return; + const inclusive = next === "in"; + setSearchVarianceFilterInclusive(inclusive); + handleSearch({ varianceFilterInclusive: inclusive }); + }} + > + + {t("Variance out of range")} + + + {t("Variance in range")} + + + {t("Variance filter inclusive only")}} - /> - - {searchVarianceFilterInclusive - ? t("Variance filter inclusive range hint", { - value: searchVariancePercentTolerance || "0", - op: searchVarianceFilterStrict ? "<" : "≤", - }) - : t("Variance filter exclusive range hint", { - value: searchVariancePercentTolerance || "0", - op: searchVarianceFilterStrict ? ">" : "≥", - })} + > + + + + + + ±{varianceToleranceLabel}% - + ); const searchActionButtons = ( @@ -2059,35 +2095,29 @@ const ApproverStockTakeAll: React.FC = ({ sx={{ display: { xs: "none", md: "block" } }} /> ))} - {varianceColumn === 1 ? ( - <> - - {varianceFilterControl} - - {searchActionButtons} - - ) : ( - <> - - {searchActionButtons} - - {varianceColumn === 3 ? ( - - ) : null} - - {varianceFilterControl} - - - )} + + {searchActionButtons} + - - {t("Total")}: {total} | {t("Shown")}: {sortedDetails.length} - + + + {t("Total")}: {total} | {t("Shown")}: {sortedDetails.length} + + {varianceRangeBar} + void; @@ -74,8 +78,8 @@ const StockTakeRoundCardList: React.FC = ({ onYearMonthChange, }) => { const { t, i18n } = useTranslation(["stockTake", "common"]); + const [listView, setListView] = useState("open"); const [loading, setLoading] = useState(true); - const [hasOpenRounds, setHasOpenRounds] = useState(false); const [rounds, setRounds] = useState([]); const [completedYearMonths, setCompletedYearMonths] = useState([]); const [closingId, setClosingId] = useState(null); @@ -83,17 +87,15 @@ const StockTakeRoundCardList: React.FC = ({ const closeLockRef = useRef(false); const thisMonth = dayjs().format("YYYY-MM"); - const load = useCallback(async (ym: string) => { + const load = useCallback(async (ym: string, view: RoundListView) => { setLoading(true); try { - const res = await getStockTakeRoundCards(ym); - setHasOpenRounds(Boolean(res.hasOpenRounds)); + 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); - setHasOpenRounds(false); setRounds([]); setCompletedYearMonths([]); } finally { @@ -102,8 +104,8 @@ const StockTakeRoundCardList: React.FC = ({ }, []); useEffect(() => { - void load(yearMonth); - }, [load, yearMonth, refreshNonce]); + 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(() => { @@ -125,6 +127,22 @@ const StockTakeRoundCardList: React.FC = ({ 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; @@ -132,16 +150,39 @@ const StockTakeRoundCardList: React.FC = ({ ? 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 (hasOpenRounds || monthOptions.length === 0) return; + 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; @@ -149,7 +190,7 @@ const StockTakeRoundCardList: React.FC = ({ try { await closeStockTakeRound(card.stockTakeRoundId); setPendingCloseCard(null); - await load(yearMonth); + await load(yearMonth, listView); } catch (e) { console.error("Failed to close stock take round:", e); } finally { @@ -168,21 +209,38 @@ const StockTakeRoundCardList: React.FC = ({ const s = (status || "").toLowerCase().replace(/_/g, ""); if (s === "completed") return "success"; if (s === "approving") return "info"; - if (s === "stocktaking" || s === "processing") return "primary"; // 盤點中 - if (s === "pending") return "warning"; // 待處理 + if (s === "stocktaking" || s === "processing") return "primary"; + if (s === "pending") return "warning"; return "default"; }; return ( + setListView(next)} + sx={{ mb: 1.5, minHeight: 36 }} + > + + + + - {hasOpenRounds + {listView === "open" ? t("Open rounds this month") : t("Completed rounds for month", { month: formatYearMonth(yearMonth) })} - {!hasOpenRounds && monthOptions.length > 0 ? ( + {listView === "history" && monthOptions.length > 0 ? ( <> - + + + + @@ -239,6 +309,9 @@ const StockTakeRoundCardList: React.FC = ({ const total = isItemRound ? (card.totalInventoryLotNumber || 0) : (card.sectionCount || 0); const taken = card.stockTakenCount || 0; const approved = card.approvedCount || 0; + const progressLegend = isItemRound + ? t("Round progress legend lots") + : t("Round progress legend"); return ( = ({ {t("Plan Start Date")}:{" "} {card.planStartDate ? dayjs(card.planStartDate).format(OUTPUT_DATE_FORMAT) : "-"} - - {card.stockTakeRoundMode === "item" - ? `${t("Total Items")}: ${card.totalItemNumber || 0}` - : `${t("Total Sections")}: ${card.sectionCount}`} - {t("Main floor")}: {card.mainFloor?.trim() || "-"} - - - - {isItemRound ? t("Stock taken lots") : t("Stock taken")} - - - {taken} / {total} - - - - - {isItemRound ? t("Approved lots") : t("Approved progress")} - - - {approved} / {total} - - + + + {progressLegend} + + + {taken} + / + {approved} + / + {total} + diff --git a/src/i18n/en/stockTake.json b/src/i18n/en/stockTake.json index 0910e7b8..923a455d 100644 --- a/src/i18n/en/stockTake.json +++ b/src/i18n/en/stockTake.json @@ -25,15 +25,24 @@ "Completed rounds for month": "Completed rounds ({{month}})", "Failed to close round": "Failed to close round", "In progress": "In progress", + "History rounds": "History", + "Year": "Year", + "Month": "Month", + "Year suffix": "", + "Month suffix": "", "Next month": "Next month", - "Open rounds this month": "Open rounds (this month)", + "Open rounds this month": "Open rounds", "Previous month": "Previous month", "Round closed": "Round closed", "Main floor": "Main floor", - "Stock taken": "Stock taken", + "Stock taken": "Counted", "Approved progress": "Approved", - "Stock taken lots": "Stock taken lots", + "Stock taken lots": "Counted lots", "Approved lots": "Approved lots", + "Round progress legend": "Counted / Approved / Total sections", + "Round progress legend lots": "Counted / Approved / Total lots", + "Variance out of range": "Outside", + "Variance in range": "Within", "Bad Qty": "Bad Qty", "Batch Save All": "Batch Save All", "Batch approver save completed: {{success}} success, {{errors}} errors": "Batch approver save completed: {{success}} success, {{errors}} errors", @@ -173,9 +182,9 @@ "UOM": "UOM", "Variance": "Variance", "Variance %": "Variance %", - "Variance filter exclusive range hint": "Show rows with -{{value}}% {{op}} variance % {{op}} {{value}}% (outside range, server-filtered)", - "Variance filter inclusive only": "Show only rows within variance range", - "Variance filter inclusive range hint": "Show rows with -{{value}}% {{op}} variance % {{op}} {{value}}% (within range)", + "Variance filter exclusive range hint": "Show rows with |variance %| ≥ {{value}}%", + "Variance filter inclusive only": "Within range", + "Variance filter inclusive range hint": "Show rows with |variance %| ≤ {{value}}%", "View Details": "View Details", "View ReStockTake": "View ReStockTake", "Warehouse": "Warehouse", diff --git a/src/i18n/zh/stockTake.json b/src/i18n/zh/stockTake.json index 07bf0cfe..05d1554c 100644 --- a/src/i18n/zh/stockTake.json +++ b/src/i18n/zh/stockTake.json @@ -8,15 +8,24 @@ "Completed rounds for month": "已完成輪次({{month}})", "Failed to close round": "關閉輪次失敗", "In progress": "進行中", + "History rounds": "歷史輪次", + "Year": "年", + "Month": "月", + "Year suffix": "年", + "Month suffix": "月", "Next month": "下一個月", - "Open rounds this month": "進行中的輪次(本月)", + "Open rounds this month": "進行中的輪次", "Previous month": "上一個月", "Round closed": "已關閉此輪", "Main floor": "主要樓層", - "Stock taken": "已盤點區域", + "Stock taken": "已點算區域", "Approved progress": "已審核區域", - "Stock taken lots": "已盤點批次", + "Stock taken lots": "已點算批次", "Approved lots": "已審核批次", + "Round progress legend": "已點算 / 已審核 / 總區域", + "Round progress legend lots": "已點算 / 已審核 / 總批號", + "Variance out of range": "範圍外", + "Variance in range": "範圍內", "Stock Take Section": "盤點區域類型", "Warehouse": "倉庫", "Warehouse code": "倉庫代碼", @@ -103,9 +112,9 @@ "variance Percentage": "差異百分比", "Variance": "差異", "Picker": "盤點員", - "Variance filter inclusive only": "反選", - "Variance filter inclusive range hint": "只顯示 -{{value}}% {{op}} 差異% {{op}} {{value}}% 的列(範圍內)", - "Variance filter exclusive range hint": "只顯示 -{{value}}% {{op}} 差異% {{op}} {{value}}% 的列(範圍外)", + "Variance filter inclusive only": "範圍內", + "Variance filter inclusive range hint": "只顯示 |差異%| ≤ {{value}}% 的列", + "Variance filter exclusive range hint": "只顯示 |差異%| ≥ {{value}}% 的列", "Total": "總數", "Shown": "顯示", "Confirm batch save approver": "確認批次儲存審核",