"use client"; import React, { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { useTranslation } from "react-i18next"; import EditOutlined from "@mui/icons-material/EditOutlined"; import DeleteOutline from "@mui/icons-material/DeleteOutline"; import Add from "@mui/icons-material/Add"; import { Alert, Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, FormControl, IconButton, InputLabel, MenuItem, Select, type SelectChangeEvent, Stack, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, TextField, Typography, } from "@mui/material"; import { fetchDoFloorSettingsClient, fetchSupplierComboClient, postSettingClient, type ShopComboRow, } from "@/app/api/settings/deliveryOrderFloor/client"; import { SETTING_DO_FLOOR_SUPPLIERS_2F, SETTING_DO_FLOOR_SUPPLIERS_4F, } from "@/app/api/settings/deliveryOrderFloor/constants"; function normalizeCodesCsv(raw: string): string { return raw .split(",") .map((s) => s.trim()) .filter(Boolean) .join(","); } /** 顯示為 `[XXX, YYY]`;無代碼時為 `[]` */ function formatBracketList(codesCsv: string): string { const n = normalizeCodesCsv(codesCsv); if (!n) return "[]"; return `[${n.split(",").join(", ")}]`; } type EditFloor = "2F" | "4F"; type FloorRow = { code: string; name: string }; function findSupplierRow(combo: ShopComboRow[], raw: string): ShopComboRow | undefined { const t = raw.trim(); if (!t) return undefined; const lower = t.toLowerCase(); const exact = combo.find((r) => r.code?.trim() === t); if (exact) return exact; return combo.find((r) => (r.code?.trim().toLowerCase() ?? "") === lower); } function csvToFloorRows(csv: string, combo: ShopComboRow[]): FloorRow[] { const n = normalizeCodesCsv(csv); if (!n) return []; return n.split(",").map((code) => { const hit = findSupplierRow(combo, code); const canonical = hit?.code?.trim() || code; return { code: canonical, name: hit?.name?.trim() || "" }; }); } function floorRowsToCsv(rows: FloorRow[]): string { return rows.map((r) => r.code.trim()).filter(Boolean).join(","); } const DeliveryOrderFloorSettings: React.FC = () => { const { t } = useTranslation("deliveryOrderFloor"); const saveInFlightRef = useRef(false); const addInFlightRef = useRef(false); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [success, setSuccess] = useState(null); const [codes2F, setCodes2F] = useState(""); const [codes4F, setCodes4F] = useState(""); const [editOpen, setEditOpen] = useState(false); const [editFloor, setEditFloor] = useState("2F"); const [dialogSaving, setDialogSaving] = useState(false); const [comboLoading, setComboLoading] = useState(false); const [supplierCombo, setSupplierCombo] = useState(null); const [draftRows2F, setDraftRows2F] = useState([]); const [draftRows4F, setDraftRows4F] = useState([]); const [addOpen, setAddOpen] = useState(false); const [addCodeInput, setAddCodeInput] = useState(""); const [addError, setAddError] = useState(null); const load = useCallback(async () => { setLoading(true); setError(null); setSuccess(null); try { const floor = await fetchDoFloorSettingsClient(); setCodes2F(floor.suppliers2F); setCodes4F(floor.suppliers4F); } catch (e: unknown) { setError(e instanceof Error ? e.message : String(e)); } finally { setLoading(false); } }, []); useEffect(() => { void load(); }, [load]); const display2F = useMemo(() => formatBracketList(codes2F), [codes2F]); const display4F = useMemo(() => formatBracketList(codes4F), [codes4F]); const currentDraftRows = editFloor === "2F" ? draftRows2F : draftRows4F; const setCurrentDraftRows = editFloor === "2F" ? setDraftRows2F : setDraftRows4F; const openEdit = async (floor: EditFloor) => { setEditFloor(floor); setEditOpen(true); setError(null); setSuccess(null); setAddOpen(false); setAddCodeInput(""); setAddError(null); setComboLoading(true); try { const combo = await fetchSupplierComboClient(); setSupplierCombo(combo); setDraftRows2F(csvToFloorRows(codes2F, combo)); setDraftRows4F(csvToFloorRows(codes4F, combo)); } catch (e: unknown) { setError(e instanceof Error ? e.message : String(e)); setDraftRows2F([]); setDraftRows4F([]); setSupplierCombo(null); } finally { setComboLoading(false); } }; const closeEdit = () => { if (dialogSaving) return; setEditOpen(false); setAddOpen(false); setAddCodeInput(""); setAddError(null); }; const saveCurrentFloor = async () => { if (saveInFlightRef.current) return; saveInFlightRef.current = true; setDialogSaving(true); setError(null); setSuccess(null); try { const rows = editFloor === "2F" ? draftRows2F : draftRows4F; const normalized = normalizeCodesCsv(floorRowsToCsv(rows)); const key = editFloor === "2F" ? SETTING_DO_FLOOR_SUPPLIERS_2F : SETTING_DO_FLOOR_SUPPLIERS_4F; await postSettingClient(key, normalized); if (editFloor === "2F") setCodes2F(normalized); else setCodes4F(normalized); setSuccess(t("Saved")); setEditOpen(false); setAddOpen(false); setAddCodeInput(""); setAddError(null); } catch (e: unknown) { setError(e instanceof Error ? e.message : String(e)); } finally { setDialogSaving(false); saveInFlightRef.current = false; } }; const onFloorSelectChange = (e: SelectChangeEvent) => { setEditFloor(e.target.value as EditFloor); setAddOpen(false); setAddCodeInput(""); setAddError(null); }; const removeRow = (code: string) => { const next = currentDraftRows.filter((r) => r.code !== code); setCurrentDraftRows(next); }; const openAddMapping = () => { if (!supplierCombo?.length) { setError(t("Supplier list unavailable")); return; } setAddCodeInput(""); setAddError(null); setAddOpen(true); }; const closeAdd = () => { if (addInFlightRef.current) return; setAddOpen(false); setAddCodeInput(""); setAddError(null); }; const confirmAddMapping = () => { if (addInFlightRef.current) return; addInFlightRef.current = true; setAddError(null); try { const raw = addCodeInput.trim(); if (!raw) { setAddError(t("Enter supplier code")); return; } const hit = findSupplierRow(supplierCombo ?? [], raw); if (!hit?.code) { setAddError(t("Supplier code not found")); return; } const canonical = hit.code.trim(); const otherRows = editFloor === "2F" ? draftRows4F : draftRows2F; if (currentDraftRows.some((r) => r.code.toLowerCase() === canonical.toLowerCase())) { setAddError(t("Duplicate in floor")); return; } if (otherRows.some((r) => r.code.toLowerCase() === canonical.toLowerCase())) { setAddError(t("Duplicate in other floor")); return; } const name = hit.name?.trim() || ""; setCurrentDraftRows([...currentDraftRows, { code: canonical, name }]); setAddOpen(false); setAddCodeInput(""); } finally { addInFlightRef.current = false; } }; if (loading) { return ( ); } return ( {t("Intro")} {error ? {error} : null} {success ? {success} : null} {t("2F supplier")} {display2F} void openEdit("2F")} size="small" > {t("4F supplier")} {display4F} void openEdit("4F")} size="small" > {t("Edit dialog title")} {t("Floor label")} {comboLoading ? ( ) : ( {t("Col code")} {t("Col name")} {t("Col type")} {t("Col actions")} {currentDraftRows.length === 0 ? ( {t("Empty floor list")} ) : ( currentDraftRows.map((row) => ( {row.code} {row.name || ( {t("Unknown supplier name")} )} {editFloor} removeRow(row.code)} disabled={dialogSaving} > )) )}
)}
{t("Add mapping title")} { setAddCodeInput(e.target.value); setAddError(null); }} placeholder={t("Add code placeholder")} /> {addError ? {addError} : null}
); }; export default DeliveryOrderFloorSettings;