From bb67d04d902068c972f423d4f11c548f89b6bd2c Mon Sep 17 00:00:00 2001 From: arDTDev Date: Tue, 25 Nov 2025 23:48:18 +0900 Subject: [PATCH] feat: Implement manual print dialog with full label printing functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Added complete manual print dialog to Web UI based on fManualPrint.cs: - Created ManualPrintDialog component with all input fields (SID, Vendor Lot, Qty, MFG Date, Reel ID, Supplier, Part No) - Added printer selection (Left/Right), print count, delete after print checkbox, and barcode input - Implemented ExecuteManualPrint backend method with ZPL label generation and printer integration - Added WebSocketServer handler for EXECUTE_MANUAL_PRINT command with full parameter support - Integrated dialog into Header component with proper error handling via AlertContext 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- FrontEnd/communication.ts | 19 +- FrontEnd/components/ManualPrintDialog.tsx | 246 ++++++++++++++++++++++ FrontEnd/components/layout/Header.tsx | 17 +- Handler/Project/WebUI/MachineBridge.cs | 51 ++++- Handler/Project/WebUI/WebSocketServer.cs | 14 +- 5 files changed, 334 insertions(+), 13 deletions(-) create mode 100644 FrontEnd/components/ManualPrintDialog.tsx diff --git a/FrontEnd/communication.ts b/FrontEnd/communication.ts index d732674..d50d496 100644 --- a/FrontEnd/communication.ts +++ b/FrontEnd/communication.ts @@ -525,7 +525,17 @@ class CommunicationLayer { } } - public async openManualPrint(): Promise<{ success: boolean; message: string }> { + public async executeManualPrint(printData: { + sid: string; + venderLot: string; + qty: string; + mfg: string; + rid: string; + spy: string; + partNo: string; + printer: string; + count: number; + }): Promise<{ success: boolean; message: string }> { if (isWebView && machine) { return { success: false, message: 'Manual print not yet implemented in WebView2 mode' }; } else { @@ -533,7 +543,7 @@ class CommunicationLayer { const timeoutId = setTimeout(() => { this.listeners = this.listeners.filter(cb => cb !== handler); reject(new Error('Manual print timeout')); - }, 5000); + }, 10000); const handler = (data: any) => { if (data.type === 'MANUAL_PRINT_RESULT') { @@ -544,7 +554,10 @@ class CommunicationLayer { }; this.listeners.push(handler); - this.ws?.send(JSON.stringify({ type: 'OPEN_MANUAL_PRINT' })); + this.ws?.send(JSON.stringify({ + type: 'EXECUTE_MANUAL_PRINT', + ...printData + })); }); } } diff --git a/FrontEnd/components/ManualPrintDialog.tsx b/FrontEnd/components/ManualPrintDialog.tsx new file mode 100644 index 0000000..fe0b07c --- /dev/null +++ b/FrontEnd/components/ManualPrintDialog.tsx @@ -0,0 +1,246 @@ +import React, { useState } from 'react'; +import { X, Printer } from 'lucide-react'; +import { useAlert } from '../contexts/AlertContext'; + +interface ManualPrintDialogProps { + isOpen: boolean; + onClose: () => void; + onPrint: (data: PrintData) => Promise<{ success: boolean; message: string }>; +} + +export interface PrintData { + sid: string; + venderLot: string; + qty: string; + mfg: string; + rid: string; + spy: string; + partNo: string; + printer: 'left' | 'right'; + count: number; + deleteAfterPrint: boolean; +} + +export const ManualPrintDialog: React.FC = ({ isOpen, onClose, onPrint }) => { + const [sid, setSid] = useState(''); + const [venderLot, setVenderLot] = useState(''); + const [qty, setQty] = useState(''); + const [mfg, setMfg] = useState(''); + const [rid, setRid] = useState(''); + const [spy, setSpy] = useState(''); + const [partNo, setPartNo] = useState(''); + const [printer, setPrinter] = useState<'left' | 'right'>('right'); + const [count, setCount] = useState(1); + const [deleteAfterPrint, setDeleteAfterPrint] = useState(false); + const [barcodeInput, setBarcodeInput] = useState(''); + const { showAlert } = useAlert(); + + if (!isOpen) return null; + + const handlePrint = async () => { + // Validate qty is a number + if (qty && isNaN(Number(qty))) { + showAlert({ + type: 'error', + title: 'Invalid Input', + message: 'Please enter quantity as a number' + }); + return; + } + + const printData: PrintData = { + sid, + venderLot, + qty, + mfg, + rid, + spy, + partNo, + printer, + count, + deleteAfterPrint + }; + + try { + const result = await onPrint(printData); + if (result.success) { + showAlert({ + type: 'success', + title: 'Print Success', + message: result.message + }); + + if (deleteAfterPrint) { + setSid(''); + setVenderLot(''); + setQty(''); + setMfg(''); + setRid(''); + setSpy(''); + setPartNo(''); + } + } else { + showAlert({ + type: 'error', + title: 'Print Failed', + message: result.message + }); + } + } catch (error: any) { + showAlert({ + type: 'error', + title: 'Print Error', + message: error.message || 'Unknown error' + }); + } + }; + + const clearField = (setter: React.Dispatch>) => { + setter(''); + }; + + return ( +
+ {/* Backdrop */} +
+ + {/* Dialog */} +
+ {/* Header */} +
+
+ +

+ MANUAL PRINT +

+
+ +
+ + {/* Content */} +
+ {/* Printer Selection */} +
+
+
+ + +
+ +
+ + setCount(Number(e.target.value))} + className="w-24 px-3 py-1 bg-slate-900 border border-white/20 rounded text-white text-center font-mono" + /> + +
+
+
+ + {/* Input Fields */} +
+ {[ + { label: 'SID', value: sid, setter: setSid }, + { label: 'Vendor Lot', value: venderLot, setter: setVenderLot }, + { label: 'Quantity', value: qty, setter: setQty }, + { label: 'MFG Date', value: mfg, setter: setMfg }, + { label: 'Reel ID', value: rid, setter: setRid }, + { label: 'Supplier', value: spy, setter: setSpy }, + { label: 'Part No', value: partNo, setter: setPartNo }, + ].map((field) => ( +
+ + field.setter(e.target.value)} + className="flex-1 px-4 py-2 bg-slate-900 border border-white/20 rounded text-white font-mono focus:border-neon-blue focus:outline-none" + /> + +
+ ))} +
+ + {/* Barcode Input */} +
+ + setBarcodeInput(e.target.value)} + className="w-full px-4 py-2 bg-slate-900 border border-white/20 rounded text-white font-mono focus:border-neon-blue focus:outline-none" + placeholder="Scan or enter barcode..." + /> +
+
+ + {/* Footer */} +
+ + +
+
+
+ ); +}; diff --git a/FrontEnd/components/layout/Header.tsx b/FrontEnd/components/layout/Header.tsx index 8d610b9..e9674ca 100644 --- a/FrontEnd/components/layout/Header.tsx +++ b/FrontEnd/components/layout/Header.tsx @@ -3,6 +3,7 @@ import { useNavigate, useLocation } from 'react-router-dom'; import { Activity, Settings, Move, Camera, Layers, Cpu, Target, Lightbulb, Printer, XCircle, Package, BookOpen } from 'lucide-react'; import { VisionMenu } from '../VisionMenu'; import { FunctionMenu } from '../FunctionMenu'; +import { ManualPrintDialog, PrintData } from '../ManualPrintDialog'; import { comms } from '../../communication'; import { useAlert } from '../../contexts/AlertContext'; @@ -18,6 +19,7 @@ export const Header: React.FC = ({ currentTime, onTabChange, active const location = useLocation(); const [showVisionMenu, setShowVisionMenu] = useState(false); const [showFunctionMenu, setShowFunctionMenu] = useState(false); + const [showManualPrintDialog, setShowManualPrintDialog] = useState(false); const { showAlert } = useAlert(); const isWebView = typeof window !== 'undefined' && !!window.chrome?.webview; @@ -47,10 +49,23 @@ export const Header: React.FC = ({ currentTime, onTabChange, active } }; + const handleManualPrint = async (printData: PrintData): Promise<{ success: boolean; message: string }> => { + try { + const result = await comms.executeManualPrint(printData); + return result; + } catch (error: any) { + return { + success: false, + message: error.message || 'Unknown error' + }; + } + }; + return ( <> setShowVisionMenu(false)} /> setShowFunctionMenu(false)} /> + setShowManualPrintDialog(false)} onPrint={handleManualPrint} />
= ({ currentTime, onTabChange, active LIGHT