diff --git a/src/app/(main)/report/page.tsx b/src/app/(main)/report/page.tsx index 1c69d010..f15b728c 100644 --- a/src/app/(main)/report/page.tsx +++ b/src/app/(main)/report/page.tsx @@ -131,7 +131,9 @@ export default function ReportPage() { }; const [loading, setLoading] = useState(false); const excelInFlightRef = useRef(false); - const [dynamicOptions, setDynamicOptions] = useState>({}); + const [dynamicOptions, setDynamicOptions] = useState< + Record + >({}); const [showConfirmDialog, setShowConfirmDialog] = useState(false); const [showNoDataDialog, setShowNoDataDialog] = useState(false); const [fieldErrors, setFieldErrors] = useState>({}); @@ -259,6 +261,9 @@ export default function ReportPage() { : data.map((item: any) => ({ label: item.label || item.name || item.code || String(item), value: item.value || item.code || String(item), + ...(item.mode != null && String(item.mode).trim() + ? { mode: String(item.mode).trim().toLowerCase() } + : {}), })) : []; @@ -637,7 +642,7 @@ export default function ReportPage() { const rawOptions = field.dynamicOptions ? (dynamicOptions[field.name] || field.options || []) : (field.options || []); - const options = rawOptions.map((opt) => ({ + const mappedOptions = rawOptions.map((opt) => ({ ...opt, label: optionLabel(currentReport.id, field.name, opt), })); @@ -645,6 +650,26 @@ export default function ReportPage() { const valueForSelect = field.multiple ? (currentValue ? currentValue.split(',').map(v => v.trim()).filter(v => v) : []) : currentValue; + // rep-012:已選依貨品輪 → 只顯示依貨品;已選依區域 → 只顯示依區域 + const options = + currentReport.id === "rep-012" && + field.name === "stockTakeRoundId" && + Array.isArray(valueForSelect) && + valueForSelect.length > 0 + ? (() => { + const selectedModes = new Set( + valueForSelect + .map( + (id) => + mappedOptions.find((o) => o.value === id)?.mode, + ) + .filter((m): m is string => Boolean(m)), + ); + if (selectedModes.size !== 1) return mappedOptions; + const mode = [...selectedModes][0]; + return mappedOptions.filter((o) => o.mode === mode); + })() + : mappedOptions; // Use larger grid size for 成品/半成品生產分析報告 const gridSize = field.multiline @@ -921,6 +946,17 @@ export default function ReportPage() { select={field.type === 'select'} SelectProps={field.multiple ? { multiple: true, + // 盤點輪次等長列表:可見約 5 項後捲動(預設會到 ~10+ 才捲) + MenuProps: { + PaperProps: { + sx: { + maxHeight: + field.name === "stockTakeRoundId" + ? 5 * 48 + : undefined, + }, + }, + }, renderValue: (selected: any) => { if (Array.isArray(selected)) { return selected diff --git a/src/components/StockTakeManagement/PickerCardList.tsx b/src/components/StockTakeManagement/PickerCardList.tsx index 84d3ee7d..37358a5a 100644 --- a/src/components/StockTakeManagement/PickerCardList.tsx +++ b/src/components/StockTakeManagement/PickerCardList.tsx @@ -351,6 +351,8 @@ const PickerCardList: React.FC = ({ /** Open item round → hide 「依區域」; open section round → hide 「依貨品」 (symmetric). */ const [blockSectionCreate, setBlockSectionCreate] = useState(false); const [blockItemCreate, setBlockItemCreate] = useState(false); + const [openingCreateDialog, setOpeningCreateDialog] = useState(false); + const openCreateDialogLockRef = useRef(false); const [createDialogItemCode, setCreateDialogItemCode] = useState(""); const [createDialogItemName, setCreateDialogItemName] = useState(""); const [createDialogItemHits, setCreateDialogItemHits] = useState([]); @@ -420,9 +422,49 @@ const PickerCardList: React.FC = ({ }; }, []); + const applyCreateModeBlocksFromRounds = useCallback( + (rounds: { open?: boolean; stockTakeRoundMode?: string | null }[]) => { + const openRounds = rounds || []; + const blockSection = openRounds.some( + (round) => + round.open && + String(round.stockTakeRoundMode ?? "").trim().toLowerCase() === "item", + ); + const blockItem = openRounds.some((round) => { + if (!round.open) return false; + const mode = String(round.stockTakeRoundMode ?? "section").trim().toLowerCase(); + return mode === "section" || mode === ""; + }); + setBlockSectionCreate(blockSection); + setBlockItemCreate(blockItem); + if (!blockSection) setCreateMode("section"); + else if (!blockItem) setCreateMode("item"); + }, + [], + ); + + const openCreateDialog = useCallback(async () => { + if (openCreateDialogLockRef.current) return; + openCreateDialogLockRef.current = true; + setOpeningCreateDialog(true); + try { + const res = await getStockTakeRoundCards(); + applyCreateModeBlocksFromRounds(res.rounds || []); + } catch (e) { + console.error("Failed to check open stock take create modes:", e); + setBlockSectionCreate(false); + setBlockItemCreate(false); + setCreateMode("section"); + } finally { + setOpeningCreateDialog(false); + openCreateDialogLockRef.current = false; + setOpenConfirmDialog(true); + } + }, [applyCreateModeBlocksFromRounds]); + useEffect(() => { - if (autoOpenCreate) setOpenConfirmDialog(true); - }, [autoOpenCreate]); + if (autoOpenCreate) void openCreateDialog(); + }, [autoOpenCreate, openCreateDialog]); useEffect(() => { if (hideSectionList) { @@ -720,40 +762,6 @@ const PickerCardList: React.FC = ({ }; }, [openConfirmDialog]); - useEffect(() => { - if (!openConfirmDialog) return; - let cancelled = false; - getStockTakeRoundCards() - .then((res) => { - if (cancelled) return; - const openRounds = res.rounds || []; - const blockSection = openRounds.some( - (round) => - round.open && - String(round.stockTakeRoundMode ?? "").trim().toLowerCase() === "item", - ); - const blockItem = openRounds.some((round) => { - if (!round.open) return false; - const mode = String(round.stockTakeRoundMode ?? "section").trim().toLowerCase(); - return mode === "section" || mode === ""; - }); - setBlockSectionCreate(blockSection); - setBlockItemCreate(blockItem); - if (!blockSection) setCreateMode("section"); - else if (!blockItem) setCreateMode("item"); - }) - .catch((e) => { - console.error("Failed to check open stock take create modes:", e); - if (!cancelled) { - setBlockSectionCreate(false); - setBlockItemCreate(false); - } - }); - return () => { - cancelled = true; - }; - }, [openConfirmDialog]); - const handleGoWarehouseSettings = useCallback(() => { setMissingSectionWarnDrawerOpen(false); setOpenConfirmDialog(false); @@ -1602,10 +1610,14 @@ const PickerCardList: React.FC = ({ )} diff --git a/src/components/StockTakeManagement/StockTakeRoundCardList.tsx b/src/components/StockTakeManagement/StockTakeRoundCardList.tsx index 210e1f6b..6071d9c5 100644 --- a/src/components/StockTakeManagement/StockTakeRoundCardList.tsx +++ b/src/components/StockTakeManagement/StockTakeRoundCardList.tsx @@ -11,6 +11,10 @@ import { CardContent, Chip, CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, Grid, LinearProgress, Stack, @@ -81,6 +85,7 @@ const StockTakeRoundCardList: React.FC = ({ 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"); @@ -149,6 +154,7 @@ const StockTakeRoundCardList: React.FC = ({ setClosingId(card.stockTakeRoundId); try { await closeStockTakeRound(card.stockTakeRoundId); + setPendingCloseCard(null); await load(yearMonth); } catch (e) { console.error("Failed to close stock take round:", e); @@ -311,7 +317,7 @@ const StockTakeRoundCardList: React.FC = ({ variant="outlined" color="warning" disabled={closingId === card.stockTakeRoundId} - onClick={() => handleCloseRound(card)} + onClick={() => setPendingCloseCard(card)} > {closingId === card.stockTakeRoundId ? : t("Close round")} @@ -325,6 +331,37 @@ const StockTakeRoundCardList: React.FC = ({ })} )} + { + if (closingId == null) setPendingCloseCard(null); + }} + maxWidth="sm" + fullWidth + > + {t("Confirm close round")} + + {t("Close round confirm message")} + + + + + + ); }; diff --git a/src/components/StockTakeManagement/StockTakeTab.tsx b/src/components/StockTakeManagement/StockTakeTab.tsx index 2d80083f..07b05fda 100644 --- a/src/components/StockTakeManagement/StockTakeTab.tsx +++ b/src/components/StockTakeManagement/StockTakeTab.tsx @@ -5,6 +5,10 @@ import { Box, Button, CircularProgress, + Dialog, + DialogActions, + DialogContent, + DialogTitle, FormControl, InputLabel, MenuItem, @@ -80,6 +84,7 @@ const StockTakeTab: React.FC = () => { const [batchButtonHost, setBatchButtonHost] = useState(null); const [showCreateOnRoundPage, setShowCreateOnRoundPage] = useState(false); const [closingRound, setClosingRound] = useState(false); + const [openCloseRoundConfirm, setOpenCloseRoundConfirm] = useState(false); const closeRoundLock = useRef(false); const [snackbar, setSnackbar] = useState<{ open: boolean; @@ -191,6 +196,7 @@ const StockTakeTab: React.FC = () => { setClosingRound(true); try { await closeStockTakeRound(selectedRound.stockTakeRoundId); + setOpenCloseRoundConfirm(false); handleSnackbar(t("Round closed"), "success"); handleBackToRounds(); } catch (e) { @@ -358,7 +364,7 @@ const StockTakeTab: React.FC = () => { variant="outlined" color="warning" disabled={closingRound} - onClick={handleCloseSelectedRound} + onClick={() => setOpenCloseRoundConfirm(true)} > {closingRound ? : t("Close round")} @@ -429,6 +435,36 @@ const StockTakeTab: React.FC = () => { )} + { + if (!closingRound) setOpenCloseRoundConfirm(false); + }} + maxWidth="sm" + fullWidth + > + {t("Confirm close round")} + + {t("Close round confirm message")} + + + + + + +