"use client"; import { Box, Tab, Tabs, Snackbar, Alert, CircularProgress, Typography, TextField, Button, Stack } from "@mui/material"; import { useState, useCallback, useEffect, useRef } from "react"; import { useSession } from "next-auth/react"; import { useTranslation } from "react-i18next"; import { AUTH } from "@/authorities"; import { SessionWithTokens } from "@/config/authConfig"; import { AllPickedStockTakeListReponse, getLatestApproverStockTakeHeader } from "@/app/api/stockTake/actions"; import PickerCardList from "./PickerCardList"; import type { PickerCardListFilters } from "./PickerCardList"; import PickerStockTake from "./PickerStockTake"; import PickerReStockTake from "./PickerReStockTake"; import ApproverStockTakeAll from "./ApproverStockTakeAll"; import { useStockTakeQtyGapWarnPercent } from "./useStockTakeQtyGapWarnPercent"; import { parseQtyGapWarnPercent, saveStockTakeQtyGapWarnPercent, } from "./qtyGapWarnSettingClient"; import { STOCK_TAKE_QTY_GAP_WARN_PERCENT } from "./stockTakeQtyGapWarning"; type ViewScope = "picker" | "approver-all"; const DEFAULT_PICKER_CARD_LIST_FILTERS: PickerCardListFilters = { sectionDescription: "All", stockTakeSession: "", status: "All", area: "", storeId: "All", }; const StockTakeTab: React.FC = () => { const { t } = useTranslation(["stockTake", "common"]); const { data: session } = useSession() as { data: SessionWithTokens | null }; const isAdmin = (session?.abilities ?? session?.user?.abilities ?? []).some( (ability) => String(ability).trim() === AUTH.ADMIN, ); const qtyGapWarnPercent = useStockTakeQtyGapWarnPercent(); const [qtyGapDraft, setQtyGapDraft] = useState(String(STOCK_TAKE_QTY_GAP_WARN_PERCENT)); const [qtyGapSaving, setQtyGapSaving] = useState(false); const qtyGapSaveLock = useRef(false); const [tabValue, setTabValue] = useState(0); const [selectedSession, setSelectedSession] = useState(null); const [viewMode, setViewMode] = useState<"details" | "reStockTake">("details"); const [viewScope, setViewScope] = useState("picker"); const [approverSession, setApproverSession] = useState(null); const [approverLoading, setApproverLoading] = useState(false); /** 從卡片列表進入明細後返回時保留分頁 */ const [pickerListPage, setPickerListPage] = useState(0); const [pickerListPageSize] = useState(6); const [pickerSearchFilters, setPickerSearchFilters] = useState(DEFAULT_PICKER_CARD_LIST_FILTERS); const [pickerAppliedFilters, setPickerAppliedFilters] = useState(DEFAULT_PICKER_CARD_LIST_FILTERS); const [snackbar, setSnackbar] = useState<{ open: boolean; message: string; severity: "success" | "error" | "warning" }>({ open: false, message: "", severity: "success", }); const handleCardClick = useCallback((session: AllPickedStockTakeListReponse) => { setSelectedSession(session); setViewMode("details"); }, []); const handleReStockTakeClick = useCallback((session: AllPickedStockTakeListReponse) => { setSelectedSession(session); setViewMode("reStockTake"); setViewScope("picker"); }, []); const handleBackToList = useCallback(() => { setSelectedSession(null); setViewMode("details"); }, []); const handleSnackbar = useCallback((message: string, severity: "success" | "error" | "warning") => { setSnackbar({ open: true, message, severity, }); }, []); useEffect(() => { setQtyGapDraft(String(qtyGapWarnPercent)); }, [qtyGapWarnPercent]); const saveQtyGapWarnPercent = useCallback(async () => { if (!isAdmin || qtyGapSaveLock.current) return; const parsed = Number(qtyGapDraft.trim()); if (!Number.isInteger(parsed) || parsed < 0 || parsed > 1000) { handleSnackbar(t("qtyGapWarnPercentInvalid"), "warning"); return; } qtyGapSaveLock.current = true; setQtyGapSaving(true); try { await saveStockTakeQtyGapWarnPercent(parsed); setQtyGapDraft(String(parseQtyGapWarnPercent(String(parsed)))); handleSnackbar(t("qtyGapWarnPercentSaved"), "success"); } catch (e) { handleSnackbar(e instanceof Error ? e.message : t("qtyGapWarnPercentInvalid"), "error"); } finally { qtyGapSaveLock.current = false; setQtyGapSaving(false); } }, [handleSnackbar, isAdmin, qtyGapDraft, t]); useEffect(() => { if (tabValue !== 1 && tabValue !== 2) return; setApproverLoading(true); getLatestApproverStockTakeHeader() .then((header) => { setApproverSession(header ?? null); }) .catch((e) => { console.error(e); setApproverSession(null); }) .finally(() => setApproverLoading(false)); }, [tabValue]); if (selectedSession && viewScope === "picker") { return ( {viewScope === "picker" && ( tabValue === 0 ? ( viewMode === "reStockTake" ? ( ) : ( ) ) : null )} setSnackbar({ ...snackbar, open: false })} > setSnackbar({ ...snackbar, open: false })} severity={snackbar.severity}> {snackbar.message} ); } return ( {isAdmin && ( {t("qtyGapWarnPercent")} setQtyGapDraft(e.target.value.replace(/[^\d]/g, ""))} inputProps={{ min: 0, max: 1000, inputMode: "numeric" }} sx={{ width: 88, m: 0, "& .MuiFilledInput-root": { height: 40 }, "& .MuiFilledInput-input.MuiInputBase-inputSizeSmall": { height: 40, boxSizing: "border-box", paddingTop: 0, paddingBottom: 0, lineHeight: "40px", }, }} /> )} { setTabValue(newValue); if (newValue === 0) { setViewScope("picker"); } else { setViewScope("approver-all"); } }} sx={{ mb: 2 }} > {tabValue === 0 && ( { setViewScope("picker"); handleCardClick(session); }} onReStockTakeClick={handleReStockTakeClick} /> )} {tabValue === 1 && ( {approverLoading ? ( ) : approverSession ? ( ) : ( {t("No data")} )} )} {tabValue === 2 && ( {approverSession ? ( ) : ( {t("No data")} )} )} setSnackbar({ ...snackbar, open: false })} > setSnackbar({ ...snackbar, open: false })} severity={snackbar.severity}> {snackbar.message} ); }; export default StockTakeTab;