diff --git a/src/app/(main)/testing/page.tsx b/src/app/(main)/testing/page.tsx
index 420986e..c6bc427 100644
--- a/src/app/(main)/testing/page.tsx
+++ b/src/app/(main)/testing/page.tsx
@@ -1,19 +1,12 @@
"use client";
import React, { useState } from "react";
-import {
- Box, Grid, Paper, Typography, Button, Dialog, DialogTitle,
- DialogContent, DialogActions, TextField, Stack, Table,
- TableBody, TableCell, TableContainer, TableHead, TableRow,
- Tabs, Tab // ← Added for tabs
-} from "@mui/material";
-import { FileDownload, Print, SettingsEthernet, Lan, Router } from "@mui/icons-material";
-import dayjs from "dayjs";
+import { Box, Paper, Typography, Button, TextField, Stack, Tabs, Tab } from "@mui/material";
+import { FileDownload } from "@mui/icons-material";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
import * as XLSX from "xlsx";
-// Simple TabPanel component for conditional rendering
interface TabPanelProps {
children?: React.ReactNode;
index: number;
@@ -30,192 +23,29 @@ function TabPanel(props: TabPanelProps) {
aria-labelledby={`simple-tab-${index}`}
{...other}
>
- {value === index && (
-
- {children}
-
- )}
+ {value === index && {children}}
);
}
export default function TestingPage() {
- // Tab state
const [tabValue, setTabValue] = useState(0);
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
- // --- 1. TSC Section States ---
- const [tscConfig, setTscConfig] = useState({ ip: '192.168.1.100', port: '9100' });
- const [tscItems, setTscItems] = useState([
- { id: 1, itemCode: 'FG-001', itemName: 'Yellow Curry Sauce', lotNo: 'LOT-TSC-01', expiryDate: '2025-12-01' },
- { id: 2, itemCode: 'FG-002', itemName: 'Red Curry Paste', lotNo: 'LOT-TSC-02', expiryDate: '2025-12-05' },
- ]);
-
- // --- 2. DataFlex Section States ---
- const [dfConfig, setDfConfig] = useState({ ip: '192.168.1.101', port: '9100' });
- const [dfItems, setDfItems] = useState([
- { id: 1, itemCode: 'DF-101', itemName: 'Instant Noodle A', lotNo: 'LOT-DF-01', expiryDate: '2026-01-10' },
- { id: 2, itemCode: 'DF-102', itemName: 'Instant Noodle B', lotNo: 'LOT-DF-02', expiryDate: '2026-01-15' },
- ]);
-
- // --- 3. OnPack Section States ---
- const [isPrinterModalOpen, setIsPrinterModalOpen] = useState(false);
- const [printerFormData, setPrinterFormData] = useState({
- itemCode: '',
- lotNo: '',
- expiryDate: dayjs().format('YYYY-MM-DD'),
- productName: ''
- });
-
- // --- 4. Laser Section States ---
- const [laserConfig, setLaserConfig] = useState({ ip: '192.168.1.102', port: '8080' });
- const [laserItems, setLaserItems] = useState([
- { id: 1, templateId: 'JOB_001', lotNo: 'L-LASER-01', expiryDate: '2025-12-31', power: '50' },
- ]);
-
- // --- 5. HANS600S-M Section States ---
- const [hansConfig, setHansConfig] = useState({ ip: '192.168.76.10', port: '45678' });
- const [hansItems, setHansItems] = useState([
- {
- id: 1,
- textChannel3: 'SN-HANS-001-20260117', // channel 3 (e.g. serial / text1)
- textChannel4: 'BATCH-HK-TEST-OK', // channel 4 (e.g. batch / text2)
- text3ObjectName: 'Text3', // EZCAD object name for channel 3
- text4ObjectName: 'Text4' // EZCAD object name for channel 4
- },
- ]);
-
- // --- 6. GRN Preview (M18) ---
+ // --- 1. GRN Preview (M18) ---
const [grnPreviewReceiptDate, setGrnPreviewReceiptDate] = useState("2026-03-16");
- // --- 7. M18 PO Sync by Code ---
+ // --- 2. M18 PO Sync by Code ---
const [m18PoCode, setM18PoCode] = useState("");
const [isSyncingM18Po, setIsSyncingM18Po] = useState(false);
const [m18PoSyncResult, setM18PoSyncResult] = useState("");
+ // --- 3. M18 DO Sync by Code ---
+ const [m18DoCode, setM18DoCode] = useState("");
+ const [isSyncingM18Do, setIsSyncingM18Do] = useState(false);
+ const [m18DoSyncResult, setM18DoSyncResult] = useState("");
- // Generic handler for inline table edits
- const handleItemChange = (setter: any, id: number, field: string, value: string) => {
- setter((prev: any[]) => prev.map(item =>
- item.id === id ? { ...item, [field]: value } : item
- ));
- };
-
- // --- API CALLS ---
-
- // TSC Print (Section 1)
- const handleTscPrint = async (row: any) => {
- const payload = { ...row, printerIp: tscConfig.ip, printerPort: tscConfig.port };
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-tsc`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- if (response.status === 401 || response.status === 403) return;
- if (response.ok) alert(`TSC Print Command Sent for ${row.itemCode}!`);
- else alert("TSC Print Failed");
- } catch (e) { console.error("TSC Error:", e); }
- };
-
- // DataFlex Print (Section 2)
- const handleDfPrint = async (row: any) => {
- const payload = { ...row, printerIp: dfConfig.ip, printerPort: dfConfig.port };
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-dataflex`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- if (response.status === 401 || response.status === 403) return;
- if (response.ok) alert(`DataFlex Print Command Sent for ${row.itemCode}!`);
- else alert("DataFlex Print Failed");
- } catch (e) { console.error("DataFlex Error:", e); }
- };
-
- // OnPack Zip Download (Section 3)
- const handleDownloadPrintJob = async () => {
- const params = new URLSearchParams(printerFormData);
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/get-printer6?${params.toString()}`, {
- method: 'GET',
- });
-
- if (response.status === 401 || response.status === 403) return;
- if (!response.ok) throw new Error('Download failed');
-
- const blob = await response.blob();
- const url = window.URL.createObjectURL(blob);
- const link = document.createElement('a');
- link.href = url;
- link.setAttribute('download', `${printerFormData.lotNo || 'OnPack'}.zip`);
- document.body.appendChild(link);
- link.click();
- link.remove();
- window.URL.revokeObjectURL(url);
-
- setIsPrinterModalOpen(false);
- } catch (e) { console.error("OnPack Error:", e); }
- };
-
- // Laser Print (Section 4 - original)
- const handleLaserPrint = async (row: any) => {
- const payload = { ...row, printerIp: laserConfig.ip, printerPort: laserConfig.port };
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- if (response.status === 401 || response.status === 403) return;
- if (response.ok) alert(`Laser Command Sent: ${row.templateId}`);
- } catch (e) { console.error(e); }
- };
-
- const handleLaserPreview = async (row: any) => {
- const payload = { ...row, printerIp: laserConfig.ip, printerPort: parseInt(laserConfig.port) };
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/preview-laser`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- if (response.status === 401 || response.status === 403) return;
- if (response.ok) alert("Red light preview active!");
- } catch (e) { console.error("Preview Error:", e); }
- };
-
- // HANS600S-M TCP Print (Section 5)
- const handleHansPrint = async (row: any) => {
- const payload = {
- printerIp: hansConfig.ip,
- printerPort: hansConfig.port,
- textChannel3: row.textChannel3,
- textChannel4: row.textChannel4,
- text3ObjectName: row.text3ObjectName,
- text4ObjectName: row.text4ObjectName
- };
- try {
- const response = await clientAuthFetch(`${NEXT_PUBLIC_API_URL}/plastic/print-laser-tcp`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify(payload)
- });
- if (response.status === 401 || response.status === 403) return;
- const result = await response.text();
- if (response.ok) {
- alert(`HANS600S-M Mark Success: ${result}`);
- } else {
- alert(`HANS600S-M Failed: ${result}`);
- }
- } catch (e) {
- console.error("HANS600S-M Error:", e);
- alert("HANS600S-M Connection Error");
- }
- };
-
- // GRN Preview CSV Download (Section 6)
const handleDownloadGrnPreviewXlsx = async () => {
try {
const response = await clientAuthFetch(
@@ -251,7 +81,6 @@ export default function TestingPage() {
}
};
- // M18 PO Sync By Code (Section 7)
const handleSyncM18PoByCode = async () => {
if (!m18PoCode.trim()) {
alert("Please enter PO code.");
@@ -278,258 +107,55 @@ export default function TestingPage() {
}
};
- // Layout Helper
- const Section = ({ title, children }: { title: string, children?: React.ReactNode }) => (
-
-
+ const handleSyncM18DoByCode = async () => {
+ if (!m18DoCode.trim()) {
+ alert("Please enter DO / shop PO code.");
+ return;
+ }
+ 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(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);
+ }
+ };
+
+ const Section = ({ title, children }: { title: string; children?: React.ReactNode }) => (
+
+
{title}
- {children || Waiting for implementation...}
+ {children || Waiting for implementation...}
);
return (
- Printer Testing
-
-
-
-
-
-
-
-
-
+
+ Testing
+
+
+
+
+
+
-
-
- setTscConfig({...tscConfig, ip: e.target.value})} />
- setTscConfig({...tscConfig, port: e.target.value})} />
-
-
-
-
-
-
- Code
- Name
- Lot
- Expiry
- Action
-
-
-
- {tscItems.map(row => (
-
- handleItemChange(setTscItems, row.id, 'itemCode', e.target.value)} />
- handleItemChange(setTscItems, row.id, 'itemName', e.target.value)} />
- handleItemChange(setTscItems, row.id, 'lotNo', e.target.value)} />
- handleItemChange(setTscItems, row.id, 'expiryDate', e.target.value)} />
- } onClick={() => handleTscPrint(row)}>Print
-
- ))}
-
-
-
-
-
-
-
-
-
- setDfConfig({...dfConfig, ip: e.target.value})} />
- setDfConfig({...dfConfig, port: e.target.value})} />
-
-
-
-
-
-
- Code
- Name
- Lot
- Expiry
- Action
-
-
-
- {dfItems.map(row => (
-
- handleItemChange(setDfItems, row.id, 'itemCode', e.target.value)} />
- handleItemChange(setDfItems, row.id, 'itemName', e.target.value)} />
- handleItemChange(setDfItems, row.id, 'lotNo', e.target.value)} />
- handleItemChange(setDfItems, row.id, 'expiryDate', e.target.value)} />
- } onClick={() => handleDfPrint(row)}>Print
-
- ))}
-
-
-
-
-
-
-
-
-
-
- Calls /plastic/get-printer6 to generate CoLOS .job bundle.
-
- } onClick={() => setIsPrinterModalOpen(true)}>
- Generate CoLOS Files
-
-
-
-
-
-
-
-
- setLaserConfig({...laserConfig, ip: e.target.value})} />
- setLaserConfig({...laserConfig, port: e.target.value})} />
-
-
-
-
-
-
-
- Template
- Lot
- Exp
- Pwr%
- Action
-
-
-
- {laserItems.map(row => (
-
- handleItemChange(setLaserItems, row.id, 'templateId', e.target.value)} />
- handleItemChange(setLaserItems, row.id, 'lotNo', e.target.value)} />
- handleItemChange(setLaserItems, row.id, 'expiryDate', e.target.value)} />
- handleItemChange(setLaserItems, row.id, 'power', e.target.value)} />
-
-
-
- }
- onClick={() => handleLaserPrint(row)}
- >
- Mark
-
-
-
-
- ))}
-
-
-
-
- Note: HANS Laser requires pre-saved templates on the controller.
-
-
-
-
-
-
-
- setHansConfig({...hansConfig, ip: e.target.value})}
- />
- setHansConfig({...hansConfig, port: e.target.value})}
- />
-
-
-
-
-
-
-
- Ch3 Text (SN)
- Ch4 Text (Batch)
- Obj3 Name
- Obj4 Name
- Action
-
-
-
- {hansItems.map(row => (
-
-
- handleItemChange(setHansItems, row.id, 'textChannel3', e.target.value)}
- sx={{ minWidth: 180 }}
- />
-
-
- handleItemChange(setHansItems, row.id, 'textChannel4', e.target.value)}
- sx={{ minWidth: 140 }}
- />
-
-
- handleItemChange(setHansItems, row.id, 'text3ObjectName', e.target.value)}
- size="small"
- />
-
-
- handleItemChange(setHansItems, row.id, 'text4ObjectName', e.target.value)}
- size="small"
- />
-
-
- }
- onClick={() => handleHansPrint(row)}
- sx={{ minWidth: 80 }}
- >
- TCP Mark
-
-
-
- ))}
-
-
-
-
- TCP Push to EZCAD3 (Ch3/Ch4 via E3_SetTextObject) | IP:192.168.76.10:45678 | Backend: /print-laser-tcp
-
-
-
-
-
-
-
-
+
+
-
@@ -592,22 +213,37 @@ export default function TestingPage() {
- {/* Dialog for OnPack */}
-
+
+ Backend endpoint: /m18/test/do-by-code?code=YOUR_CODE
+
+ {m18DoSyncResult ? (
+
+ ) : null}
+
+
);
-}
\ No newline at end of file
+}
diff --git a/src/app/api/bagPrint/actions.ts b/src/app/api/bagPrint/actions.ts
index b6bf3e1..e9ac655 100644
--- a/src/app/api/bagPrint/actions.ts
+++ b/src/app/api/bagPrint/actions.ts
@@ -80,3 +80,21 @@ export async function downloadOnPackQrZip(
return res.blob();
}
+
+/** OnPack2023 檸檬機 — text QR template (`onpack2030_2`), no separate .bmp */
+export async function downloadOnPackTextQrZip(
+ request: OnPackQrDownloadRequest,
+): Promise {
+ const url = `${NEXT_PUBLIC_API_URL}/plastic/download-onpack-qr-text`;
+ const res = await clientAuthFetch(url, {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(request),
+ });
+
+ if (!res.ok) {
+ throw new Error((await res.text()) || "Download failed");
+ }
+
+ return res.blob();
+}
diff --git a/src/components/BagPrint/BagPrintSearch.tsx b/src/components/BagPrint/BagPrintSearch.tsx
index c9a6418..8b63294 100644
--- a/src/components/BagPrint/BagPrintSearch.tsx
+++ b/src/components/BagPrint/BagPrintSearch.tsx
@@ -25,7 +25,13 @@ import ChevronRight from "@mui/icons-material/ChevronRight";
import Settings from "@mui/icons-material/Settings";
import Print from "@mui/icons-material/Print";
import Download from "@mui/icons-material/Download";
-import { checkPrinterStatus, downloadOnPackQrZip, fetchJobOrders, JobOrderListItem } from "@/app/api/bagPrint/actions";
+import {
+ checkPrinterStatus,
+ downloadOnPackQrZip,
+ downloadOnPackTextQrZip,
+ fetchJobOrders,
+ JobOrderListItem,
+} from "@/app/api/bagPrint/actions";
import dayjs from "dayjs";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import { clientAuthFetch } from "@/app/utils/clientAuthFetch";
@@ -107,6 +113,7 @@ const BagPrintSearch: React.FC = () => {
const [printerConnected, setPrinterConnected] = useState(false);
const [printerMessage, setPrinterMessage] = useState("列印機未連接");
const [downloadingOnPack, setDownloadingOnPack] = useState(false);
+ const [downloadingOnPackText, setDownloadingOnPackText] = useState(false);
useEffect(() => {
setSettings(loadSettings());
@@ -306,6 +313,46 @@ const BagPrintSearch: React.FC = () => {
}
};
+ const handleDownloadOnPackTextQr = async () => {
+ const onPackJobOrders = jobOrders
+ .map((jobOrder) => ({
+ jobOrderId: jobOrder.id,
+ itemCode: jobOrder.itemCode?.trim() || "",
+ }))
+ .filter((jobOrder) => jobOrder.itemCode.length > 0);
+
+ if (onPackJobOrders.length === 0) {
+ setSnackbar({ open: true, message: "當日沒有可下載的 job order", severity: "error" });
+ return;
+ }
+
+ setDownloadingOnPackText(true);
+ try {
+ const blob = await downloadOnPackTextQrZip({
+ jobOrders: onPackJobOrders,
+ });
+
+ const url = window.URL.createObjectURL(blob);
+ const link = document.createElement("a");
+ link.href = url;
+ link.setAttribute("download", `onpack2023_lemon_qr_${planDate}.zip`);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ window.URL.revokeObjectURL(url);
+
+ setSnackbar({ open: true, message: "OnPack2023檸檬機 ZIP 已下載", severity: "success" });
+ } catch (e) {
+ setSnackbar({
+ open: true,
+ message: e instanceof Error ? e.message : "下載 OnPack2023檸檬機 失敗",
+ severity: "error",
+ });
+ } finally {
+ setDownloadingOnPackText(false);
+ }
+ };
+
return (
{/* Top: date nav + printer + settings */}
@@ -360,15 +407,24 @@ const BagPrintSearch: React.FC = () => {
{printerMessage}
-
+
}
onClick={handleDownloadOnPackQr}
- disabled={loading || downloadingOnPack || jobOrders.length === 0}
+ disabled={loading || downloadingOnPack || downloadingOnPackText || jobOrders.length === 0}
>
{downloadingOnPack ? "下載中..." : "下載 OnPack 汁水機 QR code"}
+ }
+ onClick={handleDownloadOnPackTextQr}
+ disabled={loading || downloadingOnPack || downloadingOnPackText || jobOrders.length === 0}
+ >
+ {downloadingOnPackText ? "下載中..." : "下載 OnPack2023檸檬機"}
+