"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 (
{value === index && {children}}
);
}
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}
);
}