From 548548f453280a6244a8ae753b858bd689c8a60b Mon Sep 17 00:00:00 2001 From: "PC-20260115JRSN\\Administrator" Date: Wed, 25 Mar 2026 22:27:28 +0800 Subject: [PATCH] adding onpack 2nd machine zip download, added DO syn test for single DO code --- src/app/(main)/testing/page.tsx | 528 ++++----------------- src/app/api/bagPrint/actions.ts | 18 + src/components/BagPrint/BagPrintSearch.tsx | 62 ++- 3 files changed, 159 insertions(+), 449 deletions(-) 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)} /> - - - ))} - -
-
-
-
- - -
- - 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)} /> - - - ))} - -
-
-
-
- - -
- - - Calls /plastic/get-printer6 to generate CoLOS .job bundle. - - - -
-
- - -
- - 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)} /> - - - - - - - - ))} - -
-
- - 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" - /> - - - - - - ))} - -
-
- - 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 */} - setIsPrinterModalOpen(false)} fullWidth maxWidth="sm"> - OnPack Printer Job Details - - - setPrinterFormData({ ...printerFormData, itemCode: e.target.value })} /> - setPrinterFormData({ ...printerFormData, lotNo: e.target.value })} /> - setPrinterFormData({ ...printerFormData, productName: e.target.value })} /> - setPrinterFormData({ ...printerFormData, expiryDate: e.target.value })} /> + +
+ + setM18DoCode(e.target.value)} + placeholder="e.g. same document code as M18 shop PO" + sx={{ minWidth: 320 }} + /> + - - - - - -
+ + 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} - + +