feat: Migrate IO and Recipe systems to use real data sources

- Enhanced GetIOList() to read from DIO library (inputs, outputs, interlocks)
- Added interlock display to IOMonitorPage with 3-tab layout
- Migrated GetRecipeList() to use PUB.mdm.dataSet.OPModel table
- Migrated GetRecipe() to read from OPModel DataRow
- Migrated SaveRecipe() to save to OPModel table with type conversion
- Updated frontend to handle new structured IO format
- All methods now use real hardware/database instead of mock data

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-11-25 21:09:23 +09:00
parent c625947b2e
commit 8364f7478c
5 changed files with 432 additions and 158 deletions

View File

@@ -7,10 +7,19 @@ interface IOMonitorPageProps {
onToggle: (id: number, type: 'input' | 'output') => void;
}
interface InterlockData {
axisIndex: number;
axisName: string;
nonAxis: boolean;
locks: { id: number; name: string; state: boolean }[];
hexValue: string;
}
export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
const [ioPoints, setIoPoints] = useState<IOPoint[]>([]);
const [interlocks, setInterlocks] = useState<InterlockData[]>([]);
const [isLoading, setIsLoading] = useState(true);
const [activeIOTab, setActiveIOTab] = useState<'in' | 'out'>('in');
const [activeIOTab, setActiveIOTab] = useState<'in' | 'out' | 'interlock'>('in');
// Fetch initial IO list when page mounts
useEffect(() => {
@@ -18,8 +27,21 @@ export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
setIsLoading(true);
try {
const ioStr = await comms.getIOList();
const ioData: IOPoint[] = JSON.parse(ioStr);
setIoPoints(ioData);
const ioData = JSON.parse(ioStr);
// Handle new structured format: { inputs: [...], outputs: [...], interlocks: [...] }
if (ioData.inputs && ioData.outputs) {
// New format
const flatIoList: IOPoint[] = [
...ioData.outputs.map((io: any) => ({ id: io.id, name: io.name, type: 'output' as const, state: io.state })),
...ioData.inputs.map((io: any) => ({ id: io.id, name: io.name, type: 'input' as const, state: io.state }))
];
setIoPoints(flatIoList);
setInterlocks(ioData.interlocks || []);
} else if (Array.isArray(ioData)) {
// Old format - already flat array
setIoPoints(ioData);
}
} catch (e) {
console.error('Failed to fetch IO list:', e);
}
@@ -46,7 +68,9 @@ export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
};
}, []);
const points = ioPoints.filter(p => p.type === (activeIOTab === 'in' ? 'input' : 'output'));
const points = activeIOTab === 'interlock'
? []
: ioPoints.filter(p => p.type === (activeIOTab === 'in' ? 'input' : 'output'));
return (
<main className="relative w-full h-full px-6 pt-6 pb-20">
@@ -60,7 +84,7 @@ export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
</h2>
<div className="h-6 w-px bg-white/20"></div>
<div className="text-sm font-mono text-neon-blue">
TOTAL POINTS: {ioPoints.length}
{activeIOTab === 'interlock' ? `TOTAL AXES: ${interlocks.length}` : `TOTAL POINTS: ${ioPoints.length}`}
</div>
</div>
@@ -77,6 +101,12 @@ export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
>
OUTPUTS ({ioPoints.filter(p => p.type === 'output').length})
</button>
<button
onClick={() => setActiveIOTab('interlock')}
className={`px-6 py-2 rounded-full font-tech font-bold text-sm transition-all ${activeIOTab === 'interlock' ? 'bg-neon-blue/20 text-neon-blue border border-neon-blue shadow-[0_0_15px_rgba(0,255,255,0.3)]' : 'text-slate-400 hover:text-white hover:bg-white/5'}`}
>
INTERLOCKS ({interlocks.length})
</button>
</div>
</div>
@@ -86,6 +116,35 @@ export const IOMonitorPage: React.FC<IOMonitorPageProps> = ({ onToggle }) => {
<RotateCw className="w-16 h-16 text-neon-blue animate-spin" />
<div className="text-xl font-tech text-neon-blue tracking-widest">LOADING IO POINTS...</div>
</div>
) : activeIOTab === 'interlock' ? (
<div className="p-4 space-y-4">
{interlocks.map(axis => (
<div key={axis.axisIndex} className="bg-slate-900/60 border border-slate-700 rounded-lg p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-3">
<span className="text-xl font-tech font-bold text-neon-blue">{axis.axisName}</span>
<span className="text-sm font-mono text-slate-400">({axis.hexValue})</span>
</div>
</div>
<div className="grid grid-cols-3 gap-2">
{axis.locks.map(lock => (
<div
key={lock.id}
className={`
flex items-center gap-2 px-3 py-2 transition-all border rounded
${lock.state
? 'bg-red-500/20 border-red-500 text-red-400 shadow-[0_0_10px_rgba(239,68,68,0.3)]'
: 'bg-slate-800/40 border-slate-700 text-slate-500'}
`}
>
<div className={`w-2 h-2 rounded-full shrink-0 ${lock.state ? 'bg-red-500 shadow-[0_0_6px_#ef4444]' : 'bg-slate-700'}`}></div>
<span className="text-xs font-mono truncate">{lock.name}</span>
</div>
))}
</div>
</div>
))}
</div>
) : (
<div className="grid grid-cols-2 gap-x-8 gap-y-2 p-4">
{points.map(p => (