Files
hmitestWinform/frontend/components/IOPanel.tsx
LGram16 8dc6b0f921 Initial commit: Industrial HMI system with component architecture
- Implement WebView2-based HMI frontend with React + TypeScript + Vite
- Add C# .NET backend with WebSocket communication layer
- Separate UI components into modular structure:
  * RecipePanel: Recipe selection and management
  * IOPanel: I/O monitoring and control (32 inputs/outputs)
  * MotionPanel: Servo control for X/Y/Z axes
  * CameraPanel: Vision system feed with HUD overlay
  * SettingsModal: System configuration management
- Create reusable UI components (CyberPanel, TechButton, PanelHeader)
- Implement dual-mode communication (WebView2 native + WebSocket fallback)
- Add 3D visualization with Three.js/React Three Fiber
- Fix JSON parsing bug in configuration save handler
- Include comprehensive .gitignore for .NET and Node.js projects

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-24 20:40:45 +09:00

51 lines
2.6 KiB
TypeScript

import React, { useState } from 'react';
import { Activity } from 'lucide-react';
import { IOPoint } from '../types';
import { PanelHeader } from './common/PanelHeader';
import { TechButton } from './common/TechButton';
interface IOPanelProps {
ioPoints: IOPoint[];
onToggle: (id: number, type: 'input' | 'output') => void;
}
export const IOPanel: React.FC<IOPanelProps> = ({ ioPoints, onToggle }) => {
const [tab, setTab] = useState<'in' | 'out'>('in');
const points = ioPoints.filter(p => p.type === (tab === 'in' ? 'input' : 'output'));
return (
<div className="h-full flex flex-col">
<PanelHeader title="I/O Monitor" icon={Activity} />
<div className="flex gap-2 mb-4">
<TechButton active={tab === 'in'} onClick={() => setTab('in')} className="flex-1">Inputs</TechButton>
<TechButton active={tab === 'out'} onClick={() => setTab('out')} className="flex-1" variant="blue">Outputs</TechButton>
</div>
<div className="grid grid-cols-4 gap-2 overflow-y-auto pr-2 custom-scrollbar pb-4">
{points.map(p => (
<div
key={p.id}
onClick={() => onToggle(p.id, p.type)}
className={`
aspect-square flex flex-col items-center justify-center p-1 cursor-pointer transition-all border
clip-tech
${p.state
? (p.type === 'output'
? 'bg-neon-green/10 border-neon-green text-neon-green shadow-[0_0_10px_rgba(10,255,0,0.3)]'
: 'bg-neon-yellow/10 border-neon-yellow text-neon-yellow shadow-[0_0_10px_rgba(255,230,0,0.3)]')
: 'bg-slate-900/50 border-slate-700 text-slate-600 hover:border-slate-500'}
`}
>
<div className={`w-2 h-2 rounded-full mb-2 ${p.state ? (p.type === 'output' ? 'bg-neon-green' : 'bg-neon-yellow') : 'bg-slate-800'}`}></div>
<span className="font-mono text-[10px] font-bold">
{p.type === 'input' ? 'I' : 'Q'}{p.id.toString().padStart(2, '0')}
</span>
<span className="text-[8px] text-center uppercase leading-tight mt-1 opacity-80 truncate w-full px-1">
{p.name.replace(/(Sensor|Door|Lamp)/g, '')}
</span>
</div>
))}
</div>
</div>
);
};