Files
AGVEmulator/components/AgvStatusPanel.tsx

105 lines
4.4 KiB
TypeScript

import React from 'react';
import { AgvState, AgvError, AgvSignal, SystemFlag0, SystemFlag1 } from '../types';
import { CircleCheck, AlertOctagon, Cpu } from 'lucide-react';
interface AgvStatusPanelProps {
agvState: AgvState;
}
const AgvStatusPanel: React.FC<AgvStatusPanelProps> = ({ agvState }) => {
const renderBit = (label: string, value: number, bitIndex: number, colorClass = 'bg-green-500') => {
const isOn = (value & (1 << bitIndex)) !== 0;
// 필터링에 의해 isOn이 true인 것만 전달되지만 스타일 유지를 위해 체크 로직 유지
return (
<div key={label} className="flex items-center justify-between text-[10px] py-0.5 border-b border-gray-800 last:border-0 animate-in fade-in slide-in-from-left-1 duration-200">
<span className="text-gray-300 truncate pr-2" title={label}>{label.replace(/_/g, ' ')}</span>
<div className={`w-2.5 h-2.5 rounded-full flex-shrink-0 ${isOn ? colorClass : 'bg-gray-700'} shadow-sm`} />
</div>
);
};
const getEnumKeys = (e: any) => {
return Object.keys(e).filter(k => typeof e[k as any] === "number");
};
const getActiveKeys = (enumObj: any, value: number) => {
return getEnumKeys(enumObj).filter(key => (value & (1 << (enumObj[key as any] as number))) !== 0);
};
return (
<div className="flex flex-col h-full bg-gray-900 border-l border-gray-700 overflow-y-auto">
<div className="p-3 bg-gray-800 border-b border-gray-700 font-bold text-xs text-center text-gray-200 sticky top-0 z-10">
AGV PROTOCOL FLAGS (ACTIVE)
</div>
{/* System 1 (Control State) */}
<div className="p-2 border-b border-gray-700">
<h4 className="text-xs font-bold text-blue-400 mb-2 flex items-center gap-1">
<Cpu size={12} /> SYSTEM 1
</h4>
<div className="grid grid-cols-1 gap-0.5 min-h-[1.5rem]">
{getActiveKeys(SystemFlag1, agvState.system1).length > 0 ? (
getActiveKeys(SystemFlag1, agvState.system1).map((key) =>
renderBit(key, agvState.system1, SystemFlag1[key as any] as unknown as number, 'bg-blue-500')
)
) : (
<span className="text-[10px] text-gray-600 italic px-1">No active flags</span>
)}
</div>
</div>
{/* Signals */}
<div className="p-2 border-b border-gray-700">
<h4 className="text-xs font-bold text-yellow-400 mb-2 flex items-center gap-1">
<CircleCheck size={12} /> SIGNALS
</h4>
<div className="grid grid-cols-1 gap-0.5 min-h-[1.5rem]">
{getActiveKeys(AgvSignal, agvState.signalFlags).length > 0 ? (
getActiveKeys(AgvSignal, agvState.signalFlags).map((key) =>
renderBit(key, agvState.signalFlags, AgvSignal[key as any] as unknown as number, 'bg-yellow-500')
)
) : (
<span className="text-[10px] text-gray-600 italic px-1">No active signals</span>
)}
</div>
</div>
{/* Errors */}
<div className="p-2 border-b border-gray-700">
<h4 className="text-xs font-bold text-red-400 mb-2 flex items-center gap-1">
<AlertOctagon size={12} /> ERRORS
</h4>
<div className="grid grid-cols-1 gap-0.5 min-h-[1.5rem]">
{getActiveKeys(AgvError, agvState.errorFlags).length > 0 ? (
getActiveKeys(AgvError, agvState.errorFlags).map((key) =>
renderBit(key, agvState.errorFlags, AgvError[key as any] as unknown as number, 'bg-red-600 shadow-[0_0_5px_rgba(220,38,38,0.5)]')
)
) : (
<span className="text-[10px] text-gray-600 italic px-1">None</span>
)}
</div>
</div>
{/* System 0 (Hardware) */}
<div className="p-2 border-b border-gray-700">
<h4 className="text-xs font-bold text-cyan-400 mb-2 flex items-center gap-1">
<Cpu size={12} /> SYSTEM 0
</h4>
<div className="grid grid-cols-1 gap-0.5 min-h-[1.5rem]">
{getActiveKeys(SystemFlag0, agvState.system0).length > 0 ? (
getActiveKeys(SystemFlag0, agvState.system0).map((key) =>
renderBit(key, agvState.system0, SystemFlag0[key as any] as unknown as number, 'bg-cyan-500')
)
) : (
<span className="text-[10px] text-gray-600 italic px-1">No active flags</span>
)}
</div>
</div>
</div>
);
};
export default AgvStatusPanel;