"use client"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { Box, Button, Dialog, DialogActions, DialogContent, DialogTitle, Grid, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, IconButton, CircularProgress, } from "@mui/material"; import { useTranslation } from "react-i18next"; import { Add, Delete, Edit } from "@mui/icons-material"; import SearchBox, { Criterion } from "../SearchBox/SearchBox"; import SearchResults, { Column } from "../SearchResults/SearchResults"; import { saveItemQcCategoryMapping, deleteItemQcCategoryMapping, getItemQcCategoryMappings, fetchQcCategoriesForAll, fetchItemsForAll, getItemByCode, getCategoryType, updateCategoryType, } from "@/app/api/settings/qcItemAll/actions"; import { QcCategoryResult, ItemsResult, } from "@/app/api/settings/qcItemAll"; import { ItemQcCategoryMappingInfo } from "@/app/api/settings/qcItemAll"; import { deleteDialog, errorDialogWithContent, submitDialog, successDialog, } from "../Swal/CustomAlerts"; type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; const Tab0ItemQcCategoryMapping: React.FC = () => { const { t } = useTranslation("qcItemAll"); const [qcCategories, setQcCategories] = useState([]); const [filteredQcCategories, setFilteredQcCategories] = useState([]); const [selectedCategory, setSelectedCategory] = useState(null); const [mappings, setMappings] = useState([]); const [openDialog, setOpenDialog] = useState(false); const [openAddDialog, setOpenAddDialog] = useState(false); const [itemCode, setItemCode] = useState(""); const [validatedItem, setValidatedItem] = useState(null); const [itemCodeError, setItemCodeError] = useState(""); const [validatingItemCode, setValidatingItemCode] = useState(false); const [selectedType, setSelectedType] = useState("IQC"); const [loading, setLoading] = useState(true); const [categoryType, setCategoryType] = useState("IQC"); const [savingCategoryType, setSavingCategoryType] = useState(false); useEffect(() => { const loadData = async () => { setLoading(true); try { // Only load categories list (same as Tab 2) - fast! const categories = await fetchQcCategoriesForAll(); setQcCategories(categories || []); setFilteredQcCategories(categories || []); } catch (error) { console.error("Tab0: Error loading data:", error); setQcCategories([]); setFilteredQcCategories([]); if (error instanceof Error) { errorDialogWithContent(t("Error"), error.message, t); } } finally { setLoading(false); } }; loadData(); }, []); const handleViewMappings = useCallback(async (category: QcCategoryResult) => { setSelectedCategory(category); const [mappingData, typeFromApi] = await Promise.all([ getItemQcCategoryMappings(category.id), getCategoryType(category.id), ]); setMappings(mappingData); setCategoryType(typeFromApi ?? "IQC"); // 方案 A: no mappings -> default IQC setOpenDialog(true); }, []); const handleAddMapping = useCallback(() => { if (!selectedCategory) return; setItemCode(""); setValidatedItem(null); setItemCodeError(""); setSelectedType(categoryType); setOpenAddDialog(true); }, [selectedCategory, categoryType]); const handleItemCodeChange = useCallback(async (code: string) => { setItemCode(code); setValidatedItem(null); setItemCodeError(""); if (!code || code.trim() === "") { return; } if (code.trim().length !== 6) { return; } setValidatingItemCode(true); try { const item = await getItemByCode(code.trim()); if (item) { setValidatedItem(item); setItemCodeError(""); } else { setValidatedItem(null); setItemCodeError(t("Item code not found")); } } catch (error) { setValidatedItem(null); setItemCodeError(t("Error validating item code")); } finally { setValidatingItemCode(false); } }, [t]); const handleSaveMapping = useCallback(async () => { if (!selectedCategory || !validatedItem) return; await submitDialog(async () => { try { await saveItemQcCategoryMapping( validatedItem.id as number, selectedCategory.id, selectedType //categoryType ); // Close add dialog first setOpenAddDialog(false); setItemCode(""); setValidatedItem(null); setItemCodeError(""); // Reload mappings to update the view const mappingData = await getItemQcCategoryMappings(selectedCategory.id); setMappings(mappingData); // Show success message after closing dialogs await successDialog(t("Submit Success"), t); // Keep the view dialog open to show updated data } catch (error: unknown) { let message: string; if (error && typeof error === "object" && "message" in error) { message = String((error as { message?: string }).message); } else { message = String(error); } // 嘗試從 message 裡解析出後端 FailureRes.error try { const jsonStart = message.indexOf("{"); if (jsonStart >= 0) { const jsonPart = message.slice(jsonStart); const parsed = JSON.parse(jsonPart); if (parsed.error) { message = parsed.error; } } } catch { // 解析失敗就維持原本的 message } let displayMessage = message; if (displayMessage.includes("already has type") && displayMessage.includes("linked to QcCategory")) { const match = displayMessage.match(/type "([^"]+)" linked to QcCategory[:\s]+(.+?)(?:\.|One item)/); const type = match?.[1] ?? ""; const categoryName = match?.[2]?.trim() ?? ""; displayMessage = t("Item already has type \"{{type}}\" in QcCategory \"{{category}}\". One item can only have each type in one QcCategory.", { type, category: categoryName, }); } errorDialogWithContent(t("Submit Error"), displayMessage || t("Submit Error"), t); } }, t); }, [selectedCategory, validatedItem, selectedType, t]); const handleDeleteMapping = useCallback( async (mappingId: number) => { if (!selectedCategory) return; deleteDialog(async () => { try { await deleteItemQcCategoryMapping(mappingId); await successDialog(t("Delete Success"), t); // Reload mappings const mappingData = await getItemQcCategoryMappings(selectedCategory.id); setMappings(mappingData); // No need to reload categories list - it doesn't change } catch (error) { errorDialogWithContent(t("Delete Error"), String(error), t); } }, t); }, [selectedCategory, t] ); const typeOptions = ["IQC", "IPQC", "EPQC"]; function formatTypeDisplay(value: unknown): string { if (value == null) return "null"; if (typeof value === "string") return value; if (typeof value === "object" && value !== null && "type" in value) { const v = (value as { type?: unknown }).type; if (typeof v === "string") return v; if (v != null && typeof v === "object") return "null"; // 避免 [object Object] return "null"; } return "null"; } const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Code"), paramName: "code", type: "text" }, { label: t("Name"), paramName: "name", type: "text" }, ], [t] ); const onReset = useCallback(() => { setFilteredQcCategories(qcCategories); }, [qcCategories]); const columnWidthSx = (width = "10%") => { return { width: width, whiteSpace: "nowrap" }; }; const columns = useMemo[]>( () => [ { name: "code", label: t("Qc Category Code"), sx: columnWidthSx("20%") }, { name: "name", label: t("Qc Category Name"), sx: columnWidthSx("40%") }, { name: "type", label: t("Type"), sx: columnWidthSx("10%"), renderCell: (row) => { const t = row.type; if (t == null) return " "; // 原来是 "null" if (typeof t === "string") return t; if (typeof t === "object" && t !== null && "type" in t) { const v = (t as { type?: unknown }).type; return typeof v === "string" ? v : " "; // 原来是 "null" } return " "; // 原来是 "null" }, }, { name: "id", label: t("Actions"), onClick: (category) => handleViewMappings(category), buttonIcon: , buttonIcons: {} as any, sx: columnWidthSx("10%"), }, ], [t, handleViewMappings] ); if (loading) { return ( ); } return ( { setFilteredQcCategories( qcCategories.filter( (qc) => (!query.code || qc.code.toLowerCase().includes(query.code.toLowerCase())) && (!query.name || qc.name.toLowerCase().includes(query.name.toLowerCase())) ) ); }} onReset={onReset} /> items={filteredQcCategories} columns={columns} /> {/* View Mappings Dialog */} setOpenDialog(false)} maxWidth="md" fullWidth sx={{ zIndex: 1000 }}> {t("Mapping Details")} - {selectedCategory?.name} {t("Category Type")} setCategoryType(e.target.value)} SelectProps={{ native: true }} > {typeOptions.map((opt) => ( ))} {t("Item Code")} {t("Item Name")} {t("Type")} {t("Actions")} {mappings.length === 0 ? ( {t("No mappings found")} ) : ( mappings.map((mapping) => ( {mapping.itemCode} {mapping.itemName} {formatTypeDisplay(mapping.type)} handleDeleteMapping(mapping.id)} > )) )}
{/* Add Mapping Dialog */} setOpenAddDialog(false)} maxWidth="sm" fullWidth sx={{ zIndex: 1000 }}> {t("Add Mapping")} handleItemCodeChange(e.target.value)} error={!!itemCodeError} helperText={itemCodeError || (validatedItem ? `${validatedItem.code} - ${validatedItem.name}` : t("Enter item code to validate"))} fullWidth disabled={validatingItemCode} InputProps={{ endAdornment: validatingItemCode ? : null, }} /> setSelectedType(e.target.value)} SelectProps={{ native: true, }} fullWidth > {typeOptions.map((type) => ( ))}
); }; export default Tab0ItemQcCategoryMapping;