"use client"; import { Box, Button, Stack, Typography, Chip, CircularProgress, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Paper, TextField, } from "@mui/material"; import { useState, useCallback, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import { AllPickedStockTakeListReponse, getInventoryLotDetailsBySection, InventoryLotDetailResponse, saveStockTakeRecord, SaveStockTakeRecordRequest, BatchSaveStockTakeRecordRequest, batchSaveStockTakeRecords, } from "@/app/api/stockTake/actions"; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; interface PickerStockTakeProps { selectedSession: AllPickedStockTakeListReponse; onBack: () => void; onSnackbar: (message: string, severity: "success" | "error" | "warning") => void; } const PickerStockTake: React.FC = ({ selectedSession, onBack, onSnackbar, }) => { const { t } = useTranslation(["inventory", "common"]); const { data: session } = useSession() as { data: SessionWithTokens | null }; const [inventoryLotDetails, setInventoryLotDetails] = useState([]); const [loadingDetails, setLoadingDetails] = useState(false); // 编辑状态 const [editingRecord, setEditingRecord] = useState(null); const [firstQty, setFirstQty] = useState(""); const [secondQty, setSecondQty] = useState(""); const [firstBadQty, setFirstBadQty] = useState(""); const [secondBadQty, setSecondBadQty] = useState(""); const [remark, setRemark] = useState(""); const [saving, setSaving] = useState(false); const [batchSaving, setBatchSaving] = useState(false); const [shortcutInput, setShortcutInput] = useState(""); const currentUserId = session?.id ? parseInt(session.id) : undefined; const handleBatchSubmitAllRef = useRef<() => Promise>(); useEffect(() => { const loadDetails = async () => { setLoadingDetails(true); try { const details = await getInventoryLotDetailsBySection( selectedSession.stockTakeSession, selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null ); setInventoryLotDetails(Array.isArray(details) ? details : []); } catch (e) { console.error(e); setInventoryLotDetails([]); } finally { setLoadingDetails(false); } }; loadDetails(); }, [selectedSession]); const handleStartEdit = useCallback((detail: InventoryLotDetailResponse) => { setEditingRecord(detail); setFirstQty(detail.firstStockTakeQty?.toString() || ""); setSecondQty(detail.secondStockTakeQty?.toString() || ""); setFirstBadQty(detail.firstBadQty?.toString() || ""); setSecondBadQty(detail.secondBadQty?.toString() || ""); setRemark(detail.remarks || ""); }, []); const handleCancelEdit = useCallback(() => { setEditingRecord(null); setFirstQty(""); setSecondQty(""); setFirstBadQty(""); setSecondBadQty(""); setRemark(""); }, []); const handleSaveStockTake = useCallback(async (detail: InventoryLotDetailResponse) => { if (!selectedSession || !currentUserId) { return; } const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; const qty = isFirstSubmit ? firstQty : secondQty; const badQty = isFirstSubmit ? firstBadQty : secondBadQty; if (!qty || !badQty) { onSnackbar( isFirstSubmit ? t("Please enter QTY and Bad QTY") : t("Please enter Second QTY and Bad QTY"), "error" ); return; } setSaving(true); try { const request: SaveStockTakeRecordRequest = { stockTakeRecordId: detail.stockTakeRecordId || null, inventoryLotLineId: detail.id, qty: parseFloat(qty), badQty: parseFloat(badQty), remark: isSecondSubmit ? (remark || null) : null, }; console.log('handleSaveStockTake: request:', request); console.log('handleSaveStockTake: selectedSession.stockTakeId:', selectedSession.stockTakeId); console.log('handleSaveStockTake: currentUserId:', currentUserId); await saveStockTakeRecord( request, selectedSession.stockTakeId, currentUserId ); onSnackbar(t("Stock take record saved successfully"), "success"); handleCancelEdit(); const details = await getInventoryLotDetailsBySection( selectedSession.stockTakeSession, selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null ); setInventoryLotDetails(Array.isArray(details) ? details : []); } catch (e: any) { console.error("Save stock take record error:", e); let errorMessage = t("Failed to save stock take record"); if (e?.message) { errorMessage = e.message; } else if (e?.response) { try { const errorData = await e.response.json(); errorMessage = errorData.message || errorData.error || errorMessage; } catch { // ignore } } onSnackbar(errorMessage, "error"); } finally { setSaving(false); } }, [selectedSession, firstQty, secondQty, firstBadQty, secondBadQty, remark, handleCancelEdit, t, currentUserId, onSnackbar]); const handleBatchSubmitAll = useCallback(async () => { if (!selectedSession || !currentUserId) { console.log('handleBatchSubmitAll: Missing selectedSession or currentUserId'); return; } console.log('handleBatchSubmitAll: Starting batch save...'); setBatchSaving(true); try { const request: BatchSaveStockTakeRecordRequest = { stockTakeId: selectedSession.stockTakeId, stockTakeSection: selectedSession.stockTakeSession, stockTakerId: currentUserId, }; const result = await batchSaveStockTakeRecords(request); console.log('handleBatchSubmitAll: Result:', result); onSnackbar( t("Batch save completed: {{success}} success, {{errors}} errors", { success: result.successCount, errors: result.errorCount, }), result.errorCount > 0 ? "warning" : "success" ); const details = await getInventoryLotDetailsBySection( selectedSession.stockTakeSession, selectedSession.stockTakeId > 0 ? selectedSession.stockTakeId : null ); setInventoryLotDetails(Array.isArray(details) ? details : []); } catch (e: any) { console.error("handleBatchSubmitAll: Error:", e); let errorMessage = t("Failed to batch save stock take records"); if (e?.message) { errorMessage = e.message; } else if (e?.response) { try { const errorData = await e.response.json(); errorMessage = errorData.message || errorData.error || errorMessage; } catch { // ignore } } onSnackbar(errorMessage, "error"); } finally { setBatchSaving(false); } }, [selectedSession, t, currentUserId, onSnackbar]); useEffect(() => { handleBatchSubmitAllRef.current = handleBatchSubmitAll; }, [handleBatchSubmitAll]); useEffect(() => { const handleKeyPress = (e: KeyboardEvent) => { const target = e.target as HTMLElement; if (target && ( target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable )) { return; } if (e.ctrlKey || e.metaKey || e.altKey) { return; } if (e.key.length === 1) { setShortcutInput(prev => { const newInput = prev + e.key; if (newInput === '{2fitestall}') { console.log('✅ Shortcut {2fitestall} detected!'); setTimeout(() => { if (handleBatchSubmitAllRef.current) { console.log('Calling handleBatchSubmitAll...'); handleBatchSubmitAllRef.current().catch(err => { console.error('Error in handleBatchSubmitAll:', err); }); } else { console.error('handleBatchSubmitAllRef.current is null'); } }, 0); return ""; } if (newInput.length > 15) { return ""; } if (newInput.length > 0 && !newInput.startsWith('{')) { return ""; } if (newInput.length > 5 && !newInput.startsWith('{2fi')) { return ""; } return newInput; }); } else if (e.key === 'Backspace') { setShortcutInput(prev => prev.slice(0, -1)); } else if (e.key === 'Escape') { setShortcutInput(""); } }; window.addEventListener('keydown', handleKeyPress); return () => { window.removeEventListener('keydown', handleKeyPress); }; }, []); const isSubmitDisabled = useCallback((detail: InventoryLotDetailResponse): boolean => { if (detail.stockTakeRecordStatus === "pass") { return true; } return false; }, []); return ( {t("Stock Take Section")}: {selectedSession.stockTakeSession} {/* {shortcutInput && ( {t("Shortcut Input")}: {shortcutInput} )} */} {loadingDetails ? ( ) : ( {t("Warehouse Location")} {t("Item")} {/*{t("Item Name")}*/} {/*{t("Lot No")}*/} {t("Expiry Date")} {t("Qty")} {t("Bad Qty")} {/*{inventoryLotDetails.some(d => editingRecord?.id === d.id) && (*/} {t("Remark")} {t("UOM")} {t("Status")} {t("Record Status")} {t("Action")} {inventoryLotDetails.length === 0 ? ( {t("No data")} ) : ( inventoryLotDetails.map((detail) => { const isEditing = editingRecord?.id === detail.id; const submitDisabled = isSubmitDisabled(detail); const isFirstSubmit = !detail.stockTakeRecordId || !detail.firstStockTakeQty; const isSecondSubmit = detail.stockTakeRecordId && detail.firstStockTakeQty && !detail.secondStockTakeQty; return ( {detail.warehouseCode || "-"} {detail.itemCode || "-"}{detail.lotNo || "-"}{detail.itemName ? ` - ${detail.itemName}` : ""} {/* {detail.itemName || "-"} */} {/*{detail.lotNo || "-"}*/} {detail.expiryDate ? dayjs(detail.expiryDate).format(OUTPUT_DATE_FORMAT) : "-"} {isEditing && isFirstSubmit ? ( setFirstQty(e.target.value)} sx={{ width: 100 }} /> ) : detail.firstStockTakeQty ? ( {t("First")}: {detail.firstStockTakeQty.toFixed(2)} ) : null} {isEditing && isSecondSubmit ? ( setSecondQty(e.target.value)} sx={{ width: 100 }} /> ) : detail.secondStockTakeQty ? ( {t("Second")}: {detail.secondStockTakeQty.toFixed(2)} ) : null} {!detail.firstStockTakeQty && !detail.secondStockTakeQty && !isEditing && ( - )} {isEditing && isFirstSubmit ? ( setFirstBadQty(e.target.value)} sx={{ width: 100 }} /> ) : detail.firstBadQty ? ( {t("First")}: {detail.firstBadQty.toFixed(2)} ) : null} {isEditing && isSecondSubmit ? ( setSecondBadQty(e.target.value)} sx={{ width: 100 }} /> ) : detail.secondBadQty ? ( {t("Second")}: {detail.secondBadQty.toFixed(2)} ) : null} {!detail.firstBadQty && !detail.secondBadQty && !isEditing && ( - )} {isEditing && isSecondSubmit ? ( <> {t("Remark")} setRemark(e.target.value)} sx={{ width: 150 }} // If you want a single-line input, remove multiline/rows: // multiline // rows={2} /> ) : ( {detail.remarks || "-"} )} {detail.uom || "-"} {detail.status ? ( ) : ( "-" )} {detail.stockTakeRecordStatus === "pass" ? ( ) : detail.stockTakeRecordStatus === "notMatch" ? ( ) : ( )} {isEditing ? ( ) : ( )} ); }) )}
)}
); }; export default PickerStockTake;