- Implement real-time IO value updates via IOValueChanged event - Add interlock toggle and real-time interlock change events - Fix ToggleLight to check return value of DIO.SetRoomLight - Add HW status display in Footer matching WinForms HWState - Implement GetHWStatus API and 250ms broadcast interval - Create HistoryPage React component for work history viewing - Add GetHistoryData API for database queries - Add date range selection, search, filter, and CSV export - Add History button in Header navigation - Add PickerMoveDialog component for manage operations - Fix DataSet column names (idx, PRNATTACH, PRNVALID, qtymax) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
447 lines
21 KiB
TypeScript
447 lines
21 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
|
import { X, Move, Square, ChevronLeft, ChevronRight, ChevronUp, ChevronDown, Home, Printer, XCircle, Settings } from 'lucide-react';
|
|
import { comms } from '../communication';
|
|
import { useAlert } from '../contexts/AlertContext';
|
|
|
|
interface PickerMoveDialogProps {
|
|
isOpen: boolean;
|
|
onClose: () => void;
|
|
}
|
|
|
|
interface PickerStatus {
|
|
xEnabled: boolean;
|
|
zEnabled: boolean;
|
|
pickerSafe: boolean;
|
|
managementEnabled: boolean;
|
|
manPosL: boolean;
|
|
manPosR: boolean;
|
|
}
|
|
|
|
export const PickerMoveDialog: React.FC<PickerMoveDialogProps> = ({ isOpen, onClose }) => {
|
|
const { showAlert, showConfirm } = useAlert();
|
|
const [status, setStatus] = useState<PickerStatus>({
|
|
xEnabled: false,
|
|
zEnabled: false,
|
|
pickerSafe: false,
|
|
managementEnabled: false,
|
|
manPosL: false,
|
|
manPosR: false
|
|
});
|
|
const [isJogging, setIsJogging] = useState(false);
|
|
|
|
// Subscribe to picker status updates
|
|
useEffect(() => {
|
|
if (!isOpen) return;
|
|
|
|
const unsubscribe = comms.subscribe((data: any) => {
|
|
if (data.type === 'PICKER_STATUS') {
|
|
setStatus(data.data);
|
|
}
|
|
});
|
|
|
|
// Request initial status
|
|
comms.getPickerStatus();
|
|
|
|
// Set up polling interval
|
|
const intervalId = setInterval(() => {
|
|
comms.getPickerStatus();
|
|
}, 200);
|
|
|
|
return () => {
|
|
unsubscribe();
|
|
clearInterval(intervalId);
|
|
};
|
|
}, [isOpen]);
|
|
|
|
const handleClose = async () => {
|
|
try {
|
|
const result = await comms.canCloseManage();
|
|
if (!result.canClose) {
|
|
showAlert({
|
|
type: 'error',
|
|
title: 'Cannot Close',
|
|
message: result.message || 'Printer motion is in management position.\nReturn to position and try again.'
|
|
});
|
|
return;
|
|
}
|
|
onClose();
|
|
} catch (error) {
|
|
// If check fails, still allow close
|
|
onClose();
|
|
}
|
|
};
|
|
|
|
// === Position Move Buttons ===
|
|
const handleMoveLeft = async () => {
|
|
const result = await comms.pickerMoveLeft();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleMoveLeftWait = async () => {
|
|
const result = await comms.pickerMoveLeftWait();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleMoveCenter = async () => {
|
|
const result = await comms.pickerMoveCenter();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleMoveRightWait = async () => {
|
|
const result = await comms.pickerMoveRightWait();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleMoveRight = async () => {
|
|
const result = await comms.pickerMoveRight();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
// === Jog Control ===
|
|
const handleJogStart = useCallback(async (direction: 'up' | 'down' | 'left' | 'right') => {
|
|
setIsJogging(true);
|
|
await comms.pickerJogStart(direction);
|
|
}, []);
|
|
|
|
const handleJogStop = useCallback(async () => {
|
|
setIsJogging(false);
|
|
await comms.pickerJogStop();
|
|
}, []);
|
|
|
|
const handleStop = async () => {
|
|
await comms.pickerStop();
|
|
};
|
|
|
|
// === Vision Validation Cancel ===
|
|
const handleCancelVisionL = async () => {
|
|
const confirmed = await showConfirm({
|
|
title: 'Cancel Vision Validation',
|
|
message: 'Do you want to cancel LEFT-QR code verification?'
|
|
});
|
|
if (confirmed) {
|
|
const result = await comms.cancelVisionValidation('left');
|
|
if (result.success) {
|
|
showAlert({ type: 'success', title: 'Cancelled', message: 'LEFT-QR verification cancelled' });
|
|
} else {
|
|
showAlert({ type: 'error', title: 'Cancel Failed', message: result.message });
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleCancelVisionR = async () => {
|
|
const confirmed = await showConfirm({
|
|
title: 'Cancel Vision Validation',
|
|
message: 'Do you want to cancel RIGHT-QR code verification?'
|
|
});
|
|
if (confirmed) {
|
|
const result = await comms.cancelVisionValidation('right');
|
|
if (result.success) {
|
|
showAlert({ type: 'success', title: 'Cancelled', message: 'RIGHT-QR verification cancelled' });
|
|
} else {
|
|
showAlert({ type: 'error', title: 'Cancel Failed', message: result.message });
|
|
}
|
|
}
|
|
};
|
|
|
|
// === Management Position ===
|
|
const handleManagePosL = async () => {
|
|
const result = await comms.pickerManagePosition('left');
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleManagePosR = async () => {
|
|
const result = await comms.pickerManagePosition('right');
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handleManagePosReturn = async () => {
|
|
const result = await comms.pickerManageReturn();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Return Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
// === Z-Axis Control ===
|
|
const handleZHome = async () => {
|
|
const confirmed = await showConfirm({
|
|
title: 'Z-Axis Home',
|
|
message: 'Do you want to proceed with picker Z-axis home search?'
|
|
});
|
|
if (confirmed) {
|
|
const result = await comms.pickerZHome();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Home Failed', message: result.message });
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleZZero = async () => {
|
|
const confirmed = await showConfirm({
|
|
title: 'Z-Axis Zero',
|
|
message: 'Do you want to move picker Z-axis to coordinate:0?'
|
|
});
|
|
if (confirmed) {
|
|
const result = await comms.pickerZZero();
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Move Failed', message: result.message });
|
|
}
|
|
}
|
|
};
|
|
|
|
// === Print Test ===
|
|
const handlePrintL = async () => {
|
|
const result = await comms.pickerTestPrint('left');
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Print Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
const handlePrintR = async () => {
|
|
const result = await comms.pickerTestPrint('right');
|
|
if (!result.success) {
|
|
showAlert({ type: 'error', title: 'Print Failed', message: result.message });
|
|
}
|
|
};
|
|
|
|
if (!isOpen) return null;
|
|
|
|
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={handleClose}
|
|
/>
|
|
|
|
{/* Dialog */}
|
|
<div className="relative bg-black/95 backdrop-blur-md border-2 border-neon-blue rounded-lg shadow-2xl w-full max-w-4xl mx-4 animate-fadeIn">
|
|
{/* Header */}
|
|
<div className="flex items-center justify-between p-4 border-b border-white/10">
|
|
<div className="flex items-center gap-3">
|
|
<Move className="w-6 h-6 text-neon-blue" />
|
|
<h2 className="text-xl font-tech font-bold text-white uppercase tracking-wider">
|
|
Picker(X) Movement and Management
|
|
</h2>
|
|
</div>
|
|
<button
|
|
onClick={handleClose}
|
|
className="text-slate-400 hover:text-white transition-colors"
|
|
>
|
|
<X className="w-6 h-6" />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Content */}
|
|
<div className="p-4">
|
|
{/* Row 1: Jog Controls */}
|
|
<div className="grid grid-cols-5 gap-2 mb-2">
|
|
{/* Z Up */}
|
|
<button
|
|
onMouseDown={() => handleJogStart('up')}
|
|
onMouseUp={handleJogStop}
|
|
onMouseLeave={handleJogStop}
|
|
disabled={!status.zEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-purple-400 transition-colors"
|
|
>
|
|
<ChevronUp className="w-16 h-16" strokeWidth={3} />
|
|
</button>
|
|
|
|
{/* X Left Jog */}
|
|
<button
|
|
onMouseDown={() => handleJogStart('left')}
|
|
onMouseUp={handleJogStop}
|
|
onMouseLeave={handleJogStop}
|
|
disabled={!status.xEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-blue-900 transition-colors"
|
|
>
|
|
<ChevronLeft className="w-16 h-16" strokeWidth={3} />
|
|
</button>
|
|
|
|
{/* Stop */}
|
|
<button
|
|
onClick={handleStop}
|
|
className={`h-24 flex items-center justify-center border border-white/10 rounded-lg transition-colors ${
|
|
status.pickerSafe
|
|
? 'bg-green-500/30 hover:bg-green-500/40 text-green-400'
|
|
: 'bg-red-500/30 hover:bg-red-500/40 text-red-400'
|
|
}`}
|
|
>
|
|
<Square className="w-16 h-16" fill="currentColor" />
|
|
</button>
|
|
|
|
{/* X Right Jog */}
|
|
<button
|
|
onMouseDown={() => handleJogStart('right')}
|
|
onMouseUp={handleJogStop}
|
|
onMouseLeave={handleJogStop}
|
|
disabled={!status.xEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-blue-900 transition-colors"
|
|
>
|
|
<ChevronRight className="w-16 h-16" strokeWidth={3} />
|
|
</button>
|
|
|
|
{/* Z Down */}
|
|
<button
|
|
onMouseDown={() => handleJogStart('down')}
|
|
onMouseUp={handleJogStop}
|
|
onMouseLeave={handleJogStop}
|
|
disabled={!status.zEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-purple-400 transition-colors"
|
|
>
|
|
<ChevronDown className="w-16 h-16" strokeWidth={3} />
|
|
</button>
|
|
</div>
|
|
|
|
{/* Row 2: Position Buttons */}
|
|
<div className="grid grid-cols-5 gap-2 mb-2">
|
|
<button
|
|
onClick={handleMoveLeft}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-white font-tech text-2xl font-bold transition-colors"
|
|
>
|
|
Left
|
|
</button>
|
|
<button
|
|
onClick={handleMoveLeftWait}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-white font-tech text-2xl font-bold transition-colors"
|
|
>
|
|
Wait
|
|
</button>
|
|
<button
|
|
onClick={handleMoveCenter}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-green-400 font-tech text-2xl font-bold transition-colors"
|
|
>
|
|
Center
|
|
</button>
|
|
<button
|
|
onClick={handleMoveRightWait}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-white font-tech text-2xl font-bold transition-colors"
|
|
>
|
|
Wait
|
|
</button>
|
|
<button
|
|
onClick={handleMoveRight}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 disabled:opacity-50 disabled:cursor-not-allowed border border-white/10 rounded-lg text-white font-tech text-2xl font-bold transition-colors"
|
|
>
|
|
Right
|
|
</button>
|
|
</div>
|
|
|
|
{/* Row 3: Vision Cancel & Management Position */}
|
|
<div className="grid grid-cols-5 gap-2 mb-2">
|
|
<button
|
|
onClick={handleCancelVisionL}
|
|
className="h-24 flex flex-col items-center justify-center bg-amber-900/30 hover:bg-amber-900/50 border border-amber-500/50 rounded-lg text-amber-300 font-tech font-bold transition-colors"
|
|
>
|
|
<XCircle className="w-6 h-6 mb-1" />
|
|
<span className="text-sm">Vision Validation</span>
|
|
<span className="text-sm">Cancel(L)</span>
|
|
</button>
|
|
<button
|
|
onClick={handleManagePosL}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex flex-col items-center justify-center bg-purple-900/30 hover:bg-purple-900/50 disabled:opacity-50 disabled:cursor-not-allowed border border-purple-500/50 rounded-lg text-purple-300 font-tech font-bold transition-colors"
|
|
>
|
|
<Settings className="w-6 h-6 mb-1" />
|
|
<span className="text-sm">Print Management</span>
|
|
<span className="text-sm">Position(L)</span>
|
|
</button>
|
|
<button
|
|
onClick={handleManagePosReturn}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex flex-col items-center justify-center bg-purple-900/30 hover:bg-purple-900/50 disabled:opacity-50 disabled:cursor-not-allowed border border-purple-500/50 rounded-lg text-purple-300 font-tech font-bold transition-colors"
|
|
>
|
|
<Home className="w-6 h-6 mb-1" />
|
|
<span className="text-sm">Management Position</span>
|
|
<span className="text-sm">Return</span>
|
|
</button>
|
|
<button
|
|
onClick={handleManagePosR}
|
|
disabled={!status.managementEnabled}
|
|
className="h-24 flex flex-col items-center justify-center bg-purple-900/30 hover:bg-purple-900/50 disabled:opacity-50 disabled:cursor-not-allowed border border-purple-500/50 rounded-lg text-purple-300 font-tech font-bold transition-colors"
|
|
>
|
|
<Settings className="w-6 h-6 mb-1" />
|
|
<span className="text-sm">Print Management</span>
|
|
<span className="text-sm">Position(R)</span>
|
|
</button>
|
|
<button
|
|
onClick={handleCancelVisionR}
|
|
className="h-24 flex flex-col items-center justify-center bg-amber-900/30 hover:bg-amber-900/50 border border-amber-500/50 rounded-lg text-amber-300 font-tech font-bold transition-colors"
|
|
>
|
|
<XCircle className="w-6 h-6 mb-1" />
|
|
<span className="text-sm">Vision Validation</span>
|
|
<span className="text-sm">Cancel(R)</span>
|
|
</button>
|
|
</div>
|
|
|
|
{/* Row 4: Z-Home, Print, Z-Zero */}
|
|
<div className="grid grid-cols-5 gap-2">
|
|
<button
|
|
onClick={handleZHome}
|
|
className="h-24 flex flex-col items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 border border-white/10 rounded-lg text-white font-tech text-lg font-bold transition-colors"
|
|
>
|
|
<Home className="w-6 h-6 mb-1" />
|
|
<span>Z-HOME</span>
|
|
</button>
|
|
<button
|
|
onClick={handlePrintL}
|
|
className="h-24 flex flex-col items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 border border-white/10 rounded-lg text-white font-tech text-lg font-bold transition-colors"
|
|
>
|
|
<Printer className="w-6 h-6 mb-1" />
|
|
<span>PRINT(L)</span>
|
|
</button>
|
|
<button
|
|
disabled
|
|
className="h-24 flex items-center justify-center bg-slate-800/30 border border-white/10 rounded-lg text-slate-500 font-tech text-lg font-bold cursor-not-allowed"
|
|
>
|
|
--
|
|
</button>
|
|
<button
|
|
onClick={handlePrintR}
|
|
className="h-24 flex flex-col items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 border border-white/10 rounded-lg text-white font-tech text-lg font-bold transition-colors"
|
|
>
|
|
<Printer className="w-6 h-6 mb-1" />
|
|
<span>PRINT(R)</span>
|
|
</button>
|
|
<button
|
|
onClick={handleZZero}
|
|
className="h-24 flex flex-col items-center justify-center bg-slate-800/50 hover:bg-slate-700/50 border border-white/10 rounded-lg text-white font-tech text-lg font-bold transition-colors"
|
|
>
|
|
<span className="text-2xl mb-1">0</span>
|
|
<span>Z-ZERO</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Footer */}
|
|
<div className="flex justify-end p-4 border-t border-white/10">
|
|
<button
|
|
onClick={handleClose}
|
|
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"
|
|
>
|
|
Close
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|