"use client"; import React, { useRef, useState } from "react"; import { Box, Button, Paper, Stack, Tab, Tabs, TextField, Typography } from "@mui/material"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; interface TabPanelProps { children?: React.ReactNode; index: number; value: number; } function TabPanel(props: TabPanelProps) { const { children, value, index, ...other } = props; return ( ); } export default function M18SynPage() { const [tabValue, setTabValue] = useState(0); type SyncResultDto = { totalProcessed: number; totalSuccess: number; totalFail: number; query?: string | null; }; type ParsedSyncResult = | { kind: "exists"; message: string } | { kind: "not_found"; message: string } | { kind: "success"; message: string; totalSuccess: number } | { kind: "fail"; message: string; totalFail: number } | { kind: "mixed"; message: string; totalSuccess: number; totalFail: number } | { kind: "raw"; message: string }; const parseSyncResult = (docName: string, rawText: string): ParsedSyncResult => { const text = rawText?.trim() ?? ""; if (!text) return { kind: "raw", message: "未收到回應" }; try { const obj = JSON.parse(text) as Partial; const totalSuccess = Number(obj.totalSuccess ?? 0); const totalFail = Number(obj.totalFail ?? 0); const totalProcessed = Number(obj.totalProcessed ?? 0); const query = (obj.query ?? "").toString(); // newOnly skip message from backend if (query.includes("skipped") && query.includes("already exists")) { return { kind: "exists", message: `${docName}已存在系統` }; } // "not found in M18" (sync-by-code returns processed=1, fail=1) if ( docName === "送貨訂單" && totalProcessed === 1 && totalSuccess === 0 && totalFail === 1 && query.includes("code=equal=") ) { return { kind: "not_found", message: `在M18找不到${docName}` }; } if (totalSuccess > 0 && totalFail === 0) { return { kind: "success", totalSuccess, message: `成功同步: ${totalSuccess}張${docName}` }; } if (totalSuccess === 0 && totalFail > 0) { return { kind: "fail", totalFail, message: `失敗: 無法同步${docName}` }; } return { kind: "mixed", totalSuccess, totalFail, message: `完成: 成功 ${totalSuccess} / 失敗 ${totalFail} (${docName})` }; } catch { // Non-JSON error body or plain text return { kind: "raw", message: text }; } }; const formatSyncResultText = (docName: string, rawText: string): string => parseSyncResult(docName, rawText).message; const [m18PoCode, setM18PoCode] = useState(""); const [isSyncingM18Po, setIsSyncingM18Po] = useState(false); const [m18PoSyncResult, setM18PoSyncResult] = useState(""); const m18PoInFlightRef = useRef(false); const [m18DoCode, setM18DoCode] = useState(""); const [isSyncingM18Do, setIsSyncingM18Do] = useState(false); const [m18DoSyncResult, setM18DoSyncResult] = useState(""); const m18DoInFlightRef = useRef(false); const [m18DoExtraCode, setM18DoExtraCode] = useState(""); const [isSyncingM18DoExtra, setIsSyncingM18DoExtra] = useState(false); const [m18DoExtraSyncResult, setM18DoExtraSyncResult] = useState(""); const m18DoExtraInFlightRef = useRef(false); const [m18ProductCode, setM18ProductCode] = useState(""); const [isSyncingM18Product, setIsSyncingM18Product] = useState(false); const [m18ProductSyncResult, setM18ProductSyncResult] = useState(""); const m18ProductInFlightRef = useRef(false); const handleSyncM18PoByCode = async () => { if (m18PoInFlightRef.current) return; if (!m18PoCode.trim()) { alert("Please enter PO code."); return; } m18PoInFlightRef.current = true; setIsSyncingM18Po(true); setM18PoSyncResult(""); try { const response = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/m18/test/po-by-code?code=${encodeURIComponent(m18PoCode.trim())}`, { method: "GET" }, ); if (response.status === 401 || response.status === 403) return; const text = await response.text(); setM18PoSyncResult(formatSyncResultText("採購單", text)); if (!response.ok) { alert(`Sync failed: ${response.status}`); } } catch (e) { console.error("M18 PO Sync By Code Error:", e); alert("M18 PO sync failed. Check console/network."); } finally { setIsSyncingM18Po(false); m18PoInFlightRef.current = false; } }; const handleSyncM18DoByCode = async () => { if (m18DoInFlightRef.current) return; if (!m18DoCode.trim()) { alert("Please enter DO / shop PO code."); return; } m18DoInFlightRef.current = true; setIsSyncingM18Do(true); setM18DoSyncResult(""); try { const response = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/m18/test/do-by-code?code=${encodeURIComponent(m18DoCode.trim())}`, { method: "GET" }, ); if (response.status === 401 || response.status === 403) return; const text = await response.text(); setM18DoSyncResult(formatSyncResultText("送貨訂單", text)); if (!response.ok) { alert(`Sync failed: ${response.status}`); } } catch (e) { console.error("M18 DO Sync By Code Error:", e); alert("M18 DO sync failed. Check console/network."); } finally { setIsSyncingM18Do(false); m18DoInFlightRef.current = false; } }; /** DO(加單):手動按 code 同步,並寫入本地 isExtra=true(可輸入多個 code,用逗號或換行分隔) */ const handleSyncM18DoExtraByCode = async () => { if (m18DoExtraInFlightRef.current) return; const raw = m18DoExtraCode.trim(); if (!raw) { alert("Please enter DO / shop PO code(s) (加單)."); return; } const codes = raw .split(/[\n,]+/g) .map((s) => s.trim()) .filter(Boolean); if (codes.length === 0) { alert("Please enter at least one code."); return; } m18DoExtraInFlightRef.current = true; setIsSyncingM18DoExtra(true); setM18DoExtraSyncResult(""); try { const outputs: string[] = []; const okCodes: string[] = []; const existsCodes: string[] = []; const notFoundCodes: string[] = []; const otherFailCodes: string[] = []; for (const code of codes) { const response = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/m18/test/do-by-code-extra?code=${encodeURIComponent(code)}`, { method: "GET" }, ); if (response.status === 401 || response.status === 403) return; const text = await response.text(); const parsed = parseSyncResult("送貨訂單", text || ""); if (parsed.kind === "success") okCodes.push(code); else if (parsed.kind === "exists") existsCodes.push(code); else if (parsed.kind === "not_found") notFoundCodes.push(code); else otherFailCodes.push(code); const perCodeMsg = parsed.kind === "success" ? "成功同步" : parsed.kind === "exists" ? `失敗: ${parsed.message}` : parsed.kind === "not_found" ? parsed.message : parsed.message; outputs.push(`${code}: ${perCodeMsg}${response.ok ? "" : ` (HTTP ${response.status})`}`); } // Summary outputs.push(""); outputs.push(`共${okCodes.length}張送貨訂單成功`); if (notFoundCodes.length > 0) outputs.push(`共${notFoundCodes.length}張在M18找不到訂單 (${notFoundCodes.join(", ")})`); if (existsCodes.length > 0) outputs.push(`共${existsCodes.length}張訂單已存在系統 (${existsCodes.join(", ")})`); if (otherFailCodes.length > 0) outputs.push(`共${otherFailCodes.length}張同步失敗 (${otherFailCodes.join(", ")})`); setM18DoExtraSyncResult(outputs.join("\n")); } catch (e) { console.error("M18 DO (加單) Sync By Code Error:", e); alert("M18 DO (加單) sync failed. Check console/network."); } finally { setIsSyncingM18DoExtra(false); m18DoExtraInFlightRef.current = false; } }; const handleSyncM18ProductByCode = async () => { if (m18ProductInFlightRef.current) return; if (!m18ProductCode.trim()) { alert("Please enter M18 item / product code."); return; } m18ProductInFlightRef.current = true; setIsSyncingM18Product(true); setM18ProductSyncResult(""); try { const response = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/m18/test/product-by-code?code=${encodeURIComponent(m18ProductCode.trim())}`, { method: "GET" }, ); if (response.status === 401 || response.status === 403) return; const text = await response.text(); setM18ProductSyncResult(formatSyncResultText("產品", text)); if (!response.ok) { alert(`Sync failed: ${response.status}`); } } catch (e) { console.error("M18 Product Sync By Code Error:", e); alert("M18 product sync failed. Check console/network."); } finally { setIsSyncingM18Product(false); m18ProductInFlightRef.current = false; } }; const Section = ({ title, children }: { title: string; children?: React.ReactNode }) => ( {title} {children || Waiting for implementation...} ); return ( M18 Sync (by code) ADMIN only. Sync Purchase Order, Delivery Order, or product/material from M18 using document or item code. setTabValue(v)} aria-label="M18 sync by code" centered variant="fullWidth">
setM18PoCode(e.target.value)} placeholder="e.g. PFP002PO26030341" sx={{ minWidth: 320 }} /> {m18PoSyncResult ? ( ) : null}
setM18DoCode(e.target.value)} placeholder="e.g. same document code as M18 shop PO" sx={{ minWidth: 320 }} /> {m18DoSyncResult ? ( ) : null}
setM18DoExtraCode(e.target.value)} placeholder="可輸入多個 code,用逗號或換行分隔" fullWidth multiline minRows={6} maxRows={14} /> {m18DoExtraSyncResult ? ( ) : null}
setM18ProductCode(e.target.value)} placeholder="e.g. PP1175 (M18 item code)" sx={{ minWidth: 320 }} /> {m18ProductSyncResult ? ( ) : null}
); }