Files
WebUITest-RealProjecT/FrontEnd/components/layout/Header.tsx
arDTDev 3bd35ad852 feat: Add real-time IO/interlock updates, HW status display, and history page
- 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>
2025-11-27 00:18:27 +09:00

222 lines
12 KiB
TypeScript

import React, { useState } from 'react';
import { useNavigate, useLocation } from 'react-router-dom';
import { Activity, Settings, Move, Camera, Layers, Cpu, Target, Lightbulb, Printer, XCircle, Package, BookOpen, History, Clock } 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';
interface HeaderProps {
currentTime: Date;
onTabChange: (tab: 'recipe' | 'motion' | 'camera' | 'setting' | 'initialize' | null) => void;
activeTab: 'recipe' | 'motion' | 'camera' | 'setting' | 'initialize' | null;
}
export const Header: React.FC<HeaderProps> = ({ currentTime, onTabChange, activeTab }) => {
const navigate = useNavigate();
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;
const isIOPage = location.pathname === '/io-monitor';
const handleCommand = async (commandFn: () => Promise<{ success: boolean; message: string }>, actionName: string) => {
try {
const result = await commandFn();
if (result.success) {
console.log(`[Header] ${actionName}: ${result.message}`);
// Show success message if needed
} else {
console.error(`[Header] ${actionName} failed: ${result.message}`);
showAlert({
type: 'error',
title: `${actionName} Failed`,
message: result.message
});
}
} catch (error: any) {
console.error(`[Header] ${actionName} error:`, error);
showAlert({
type: 'error',
title: `${actionName} Error`,
message: error.message || 'Unknown error'
});
}
};
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 (
<>
<VisionMenu isOpen={showVisionMenu} onClose={() => setShowVisionMenu(false)} />
<FunctionMenu isOpen={showFunctionMenu} onClose={() => setShowFunctionMenu(false)} />
<ManualPrintDialog isOpen={showManualPrintDialog} onClose={() => setShowManualPrintDialog(false)} onPrint={handleManualPrint} />
<header className="absolute top-0 left-0 right-0 h-20 px-6 flex items-center justify-between z-40 bg-gradient-to-b from-black/80 to-transparent pointer-events-none">
<div
className="flex items-center gap-4 pointer-events-auto cursor-pointer group"
onClick={() => {
navigate('/');
onTabChange(null);
}}
>
<div className="w-12 h-12 border-2 border-neon-blue flex items-center justify-center rounded shadow-glow-blue bg-black/50 backdrop-blur group-hover:bg-neon-blue/10 transition-colors">
<Cpu className="text-neon-blue w-8 h-8 animate-pulse-slow" />
</div>
<div>
<h1 className="text-3xl font-tech font-bold text-white tracking-widest uppercase italic text-shadow-glow-blue group-hover:text-neon-blue transition-colors">
EQUI-HANDLER <span className="text-neon-blue text-sm not-italic">PRO</span>
</h1>
<div className="flex gap-2 text-[10px] text-neon-blue/70 font-mono">
<span>SYS.VER 4.2.0</span>
<span>|</span>
<span className={isWebView ? "text-neon-green" : "text-amber-500"}>
LINK: {isWebView ? "NATIVE" : "SIMULATION"}
</span>
</div>
</div>
</div>
{/* Top Navigation */}
<div className="flex items-center gap-2 pointer-events-auto">
{/* Quick Action Buttons */}
<div className="bg-black/40 backdrop-blur-md p-1 rounded-2xl border border-white/10 flex gap-1 mr-2">
<button
onClick={() => handleCommand(() => comms.openManual(), 'Manual')}
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-blue-400 hover:bg-white/5"
title="Open Manual"
>
<BookOpen className="w-5 h-5" />
<span className="leading-tight">MANUAL</span>
</button>
<button
onClick={() => handleCommand(() => comms.toggleLight(), 'Toggle Light')}
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-amber-400 hover:bg-white/5"
title="Toggle Room Light"
>
<Lightbulb className="w-5 h-5" />
<span className="leading-tight">LIGHT</span>
</button>
<button
onClick={() => setShowManualPrintDialog(true)}
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-cyan-400 hover:bg-white/5"
title="Open Manual Print"
>
<Printer className="w-5 h-5" />
<span className="leading-tight">PRINT</span>
</button>
<button
onClick={() => handleCommand(() => comms.cancelJob(), 'Cancel Job')}
className="flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] text-slate-400 hover:text-red-400 hover:bg-white/5"
title="Cancel Current Job"
>
<XCircle className="w-5 h-5" />
<span className="leading-tight">CANCEL</span>
</button>
<button
onClick={() => {
navigate('/history');
onTabChange(null);
}}
className={`flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px] ${location.pathname === '/history'
? 'bg-neon-blue/10 text-neon-blue border-neon-blue shadow-glow-blue'
: 'text-slate-400 hover:text-purple-400 hover:bg-white/5'
}`}
title="View Work History"
>
<Clock className="w-5 h-5" />
<span className="leading-tight">HISTORY</span>
</button>
</div>
{/* Main Navigation */}
<div className="bg-black/40 backdrop-blur-md p-1 rounded-2xl border border-white/10 flex gap-1">
{[
{ id: 'recipe', icon: Layers, label: 'RECIPE', path: '/' },
{ id: 'io', icon: Activity, label: 'I/O MONITOR', path: '/io-monitor' },
{ id: 'motion', icon: Move, label: 'MOTION', path: '/' },
{ id: 'camera', icon: Camera, label: 'VISION', path: '/' },
{ id: 'function', icon: Package, label: 'FUNCTION', path: '/' },
{ id: 'setting', icon: Settings, label: 'CONFIG', path: '/' },
{ id: 'initialize', icon: Target, label: 'INITIALIZE', path: '/' }
].map(item => {
const isActive = item.id === 'io'
? location.pathname === '/io-monitor'
: activeTab === item.id;
return (
<button
key={item.id}
onClick={() => {
if (item.id === 'io') {
navigate('/io-monitor');
onTabChange(null);
setShowVisionMenu(false);
setShowFunctionMenu(false);
} else if (item.id === 'camera') {
// Camera button does nothing on click
} else if (item.id === 'function') {
// Function button does nothing on click
} else {
if (location.pathname !== '/') {
navigate('/');
}
setShowVisionMenu(false);
setShowFunctionMenu(false);
onTabChange(activeTab === item.id ? null : item.id as any);
}
}}
onMouseEnter={() => {
if (item.id === 'camera') {
setShowVisionMenu(true);
setShowFunctionMenu(false);
} else if (item.id === 'function') {
setShowFunctionMenu(true);
setShowVisionMenu(false);
} else {
setShowVisionMenu(false);
setShowFunctionMenu(false);
}
}}
className={`
flex flex-col items-center justify-center gap-1 px-3 py-2 rounded-xl font-tech font-bold text-[10px] transition-all border border-transparent min-w-[70px]
${isActive || (item.id === 'camera' && showVisionMenu) || (item.id === 'function' && showFunctionMenu)
? 'bg-neon-blue/10 text-neon-blue border-neon-blue shadow-glow-blue'
: 'text-slate-400 hover:text-white hover:bg-white/5'}
`}
>
<item.icon className="w-5 h-5" />
<span className="leading-tight">{item.label}</span>
</button>
);
})}
</div>
</div>
<div className="text-right pointer-events-auto">
<div className="text-2xl font-mono font-bold text-white text-shadow-glow-blue">
{currentTime.toLocaleTimeString('en-GB')}
</div>
<div className="text-xs font-tech text-slate-400 tracking-[0.3em]">
{currentTime.toLocaleDateString().toUpperCase()}
</div>
</div>
</header>
</>
);
};