feat: Implement manual print dialog with full label printing functionality
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 <noreply@anthropic.com>
This commit is contained in:
246
FrontEnd/components/ManualPrintDialog.tsx
Normal file
246
FrontEnd/components/ManualPrintDialog.tsx
Normal file
@@ -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<ManualPrintDialogProps> = ({ 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<React.SetStateAction<string>>) => {
|
||||
setter('');
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-[100] flex items-center justify-center">
|
||||
{/* Backdrop */}
|
||||
<div
|
||||
className="absolute inset-0 bg-black/80 backdrop-blur-sm"
|
||||
onClick={onClose}
|
||||
/>
|
||||
|
||||
{/* Dialog */}
|
||||
<div className="relative bg-black/95 backdrop-blur-md border-2 border-neon-blue rounded-lg shadow-2xl w-full max-w-2xl mx-4 animate-fadeIn">
|
||||
{/* Header */}
|
||||
<div className="flex items-center justify-between p-6 border-b border-white/10">
|
||||
<div className="flex items-center gap-3">
|
||||
<Printer className="w-6 h-6 text-neon-blue" />
|
||||
<h2 className="text-xl font-tech font-bold text-white uppercase tracking-wider">
|
||||
MANUAL PRINT
|
||||
</h2>
|
||||
</div>
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="text-slate-400 hover:text-white transition-colors"
|
||||
>
|
||||
<X className="w-6 h-6" />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Content */}
|
||||
<div className="p-6 max-h-[70vh] overflow-y-auto">
|
||||
{/* Printer Selection */}
|
||||
<div className="bg-slate-800/50 border border-white/10 rounded-lg p-4 mb-4">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex gap-4">
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
value="left"
|
||||
checked={printer === 'left'}
|
||||
onChange={(e) => setPrinter(e.target.value as 'left')}
|
||||
className="w-4 h-4 text-neon-blue"
|
||||
/>
|
||||
<span className="text-white font-tech">Left</span>
|
||||
</label>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="radio"
|
||||
value="right"
|
||||
checked={printer === 'right'}
|
||||
onChange={(e) => setPrinter(e.target.value as 'right')}
|
||||
className="w-4 h-4 text-neon-blue"
|
||||
/>
|
||||
<span className="text-white font-tech">Right</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-4">
|
||||
<label className="text-slate-400 font-tech text-sm">Count</label>
|
||||
<input
|
||||
type="number"
|
||||
min="1"
|
||||
max="99999"
|
||||
value={count}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<label className="flex items-center gap-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={deleteAfterPrint}
|
||||
onChange={(e) => setDeleteAfterPrint(e.target.checked)}
|
||||
className="w-4 h-4 text-neon-blue"
|
||||
/>
|
||||
<span className="text-slate-400 font-tech text-sm">Delete after printing</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Input Fields */}
|
||||
<div className="space-y-3">
|
||||
{[
|
||||
{ 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) => (
|
||||
<div key={field.label} className="flex items-center gap-3">
|
||||
<label className="w-32 text-slate-400 font-tech text-sm">
|
||||
{field.label}
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={field.value}
|
||||
onChange={(e) => 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"
|
||||
/>
|
||||
<button
|
||||
onClick={() => clearField(field.setter)}
|
||||
className="px-4 py-2 bg-slate-700 hover:bg-slate-600 border border-white/20 rounded text-white font-tech text-sm transition-colors"
|
||||
>
|
||||
Clear
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Barcode Input */}
|
||||
<div className="mt-6 p-4 bg-slate-800/50 border border-white/10 rounded-lg">
|
||||
<label className="block text-slate-400 font-tech text-sm mb-2">
|
||||
Barcode input field (move cursor and enter)
|
||||
</label>
|
||||
<input
|
||||
type="text"
|
||||
value={barcodeInput}
|
||||
onChange={(e) => 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..."
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Footer */}
|
||||
<div className="flex justify-end gap-3 p-6 border-t border-white/10">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="px-6 py-2 bg-slate-700 hover:bg-slate-600 border border-white/20 text-white font-tech font-bold rounded transition-colors"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
onClick={handlePrint}
|
||||
className="px-6 py-2 bg-neon-blue/20 hover:bg-neon-blue/30 border border-neon-blue text-neon-blue font-tech font-bold rounded transition-colors shadow-glow-blue"
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<Printer className="w-4 h-4" />
|
||||
<span>PRINT</span>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
Reference in New Issue
Block a user