Procházet zdrojové kódy

update report round list

stockTakeUpdate
CANCERYS\kw093 před 2 týdny
rodič
revize
372a2571aa
6 změnil soubory, kde provedl 168 přidání a 43 odebrání
  1. +38
    -2
      src/app/(main)/report/page.tsx
  2. +51
    -39
      src/components/StockTakeManagement/PickerCardList.tsx
  3. +38
    -1
      src/components/StockTakeManagement/StockTakeRoundCardList.tsx
  4. +37
    -1
      src/components/StockTakeManagement/StockTakeTab.tsx
  5. +2
    -0
      src/i18n/en/stockTake.json
  6. +2
    -0
      src/i18n/zh/stockTake.json

+ 38
- 2
src/app/(main)/report/page.tsx Zobrazit soubor

@@ -131,7 +131,9 @@ export default function ReportPage() {
};
const [loading, setLoading] = useState(false);
const excelInFlightRef = useRef(false);
const [dynamicOptions, setDynamicOptions] = useState<Record<string, { label: string; value: string }[]>>({});
const [dynamicOptions, setDynamicOptions] = useState<
Record<string, { label: string; value: string; mode?: string }[]>
>({});
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const [showNoDataDialog, setShowNoDataDialog] = useState(false);
const [fieldErrors, setFieldErrors] = useState<Record<string, string>>({});
@@ -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


+ 51
- 39
src/components/StockTakeManagement/PickerCardList.tsx Zobrazit soubor

@@ -351,6 +351,8 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
/** 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<CreateDialogItemHit[]>([]);
@@ -420,9 +422,49 @@ const PickerCardList: React.FC<PickerCardListProps> = ({
};
}, []);

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<PickerCardListProps> = ({
};
}, [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<PickerCardListProps> = ({
<Button
variant="contained"
color="primary"
onClick={() => setOpenConfirmDialog(true)}
disabled={creating || !canManageStockTake}
onClick={() => void openCreateDialog()}
disabled={creating || openingCreateDialog || !canManageStockTake}
>
{creating ? <CircularProgress size={20} /> : t("Create Stock Take (Select Sections)")}
{creating || openingCreateDialog ? (
<CircularProgress size={20} />
) : (
t("Create Stock Take (Select Sections)")
)}
</Button>
)}
</Box>


+ 38
- 1
src/components/StockTakeManagement/StockTakeRoundCardList.tsx Zobrazit soubor

@@ -11,6 +11,10 @@ import {
CardContent,
Chip,
CircularProgress,
Dialog,
DialogActions,
DialogContent,
DialogTitle,
Grid,
LinearProgress,
Stack,
@@ -81,6 +85,7 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
const [rounds, setRounds] = useState<StockTakeRoundCard[]>([]);
const [completedYearMonths, setCompletedYearMonths] = useState<string[]>([]);
const [closingId, setClosingId] = useState<number | null>(null);
const [pendingCloseCard, setPendingCloseCard] = useState<StockTakeRoundCard | null>(null);
const closeLockRef = useRef(false);
const thisMonth = dayjs().format("YYYY-MM");

@@ -149,6 +154,7 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
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<StockTakeRoundCardListProps> = ({
variant="outlined"
color="warning"
disabled={closingId === card.stockTakeRoundId}
onClick={() => handleCloseRound(card)}
onClick={() => setPendingCloseCard(card)}
>
{closingId === card.stockTakeRoundId ? <CircularProgress size={16} /> : t("Close round")}
</Button>
@@ -325,6 +331,37 @@ const StockTakeRoundCardList: React.FC<StockTakeRoundCardListProps> = ({
})}
</Grid>
)}
<Dialog
open={Boolean(pendingCloseCard)}
onClose={() => {
if (closingId == null) setPendingCloseCard(null);
}}
maxWidth="sm"
fullWidth
>
<DialogTitle>{t("Confirm close round")}</DialogTitle>
<DialogContent dividers>
<Typography variant="body2">{t("Close round confirm message")}</Typography>
</DialogContent>
<DialogActions sx={{ px: 3, py: 2 }}>
<Button
onClick={() => setPendingCloseCard(null)}
disabled={closingId != null}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
color="warning"
disabled={!pendingCloseCard || closingId != null}
onClick={() => {
if (pendingCloseCard) void handleCloseRound(pendingCloseCard);
}}
>
{closingId != null ? <CircularProgress size={18} /> : t("Close round")}
</Button>
</DialogActions>
</Dialog>
</Box>
);
};


+ 37
- 1
src/components/StockTakeManagement/StockTakeTab.tsx Zobrazit soubor

@@ -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<HTMLDivElement | null>(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 ? <CircularProgress size={18} /> : t("Close round")}
</Button>
@@ -429,6 +435,36 @@ const StockTakeTab: React.FC = () => {
)}
</Box>

<Dialog
open={openCloseRoundConfirm}
onClose={() => {
if (!closingRound) setOpenCloseRoundConfirm(false);
}}
maxWidth="sm"
fullWidth
>
<DialogTitle>{t("Confirm close round")}</DialogTitle>
<DialogContent dividers>
<Typography variant="body2">{t("Close round confirm message")}</Typography>
</DialogContent>
<DialogActions sx={{ px: 3, py: 2 }}>
<Button
onClick={() => setOpenCloseRoundConfirm(false)}
disabled={closingRound}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
color="warning"
disabled={closingRound}
onClick={() => void handleCloseSelectedRound()}
>
{closingRound ? <CircularProgress size={18} /> : t("Close round")}
</Button>
</DialogActions>
</Dialog>

<Snackbar
open={snackbar.open}
autoHideDuration={6000}


+ 2
- 0
src/i18n/en/stockTake.json Zobrazit soubor

@@ -20,6 +20,8 @@
"Back to List": "Back to List",
"Back to rounds": "Back to rounds",
"Close round": "Close round",
"Confirm close round": "Close this round?",
"Close round confirm message": "This button is for emergency use only. Closing marks this round as completed and releases auto put-aways held by stock take. Items not yet approved will not be auto-approved or given approver quantities. Already approved (saved) items stay as they are. This does not delete stock-take data, and it is not cancel / void. Continue?",
"Completed rounds for month": "Completed rounds ({{month}})",
"Failed to close round": "Failed to close round",
"In progress": "In progress",


+ 2
- 0
src/i18n/zh/stockTake.json Zobrazit soubor

@@ -3,6 +3,8 @@
"Back to List": "返回列表",
"Back to rounds": "返回輪次列表",
"Close round": "關閉此輪",
"Confirm close round": "確認關閉此輪?",
"Close round confirm message": "此按鈕僅供緊急用途。關閉後此輪會標記為已完成,並釋放因盤點暫緩的自動上架。尚未審核的項目不會因此被改成已審核或自動帶入審核數量;已審核並儲存的項目維持現狀。確定要關閉?",
"Completed rounds for month": "已完成輪次({{month}})",
"Failed to close round": "關閉輪次失敗",
"In progress": "進行中",


Načítá se…
Zrušit
Uložit