Initial commit: Refactor AgvAutoRunControls
This commit is contained in:
167
components/AgvControls.tsx
Normal file
167
components/AgvControls.tsx
Normal file
@@ -0,0 +1,167 @@
|
||||
|
||||
import React from 'react';
|
||||
import { StopCircle, Play, Square, AlertTriangle, ChevronsUp, ChevronsDown, Magnet, Radar, ArrowLeft, ArrowRight } from 'lucide-react';
|
||||
import { AgvState, AgvMotionState, AgvRunConfig } from '../types';
|
||||
import AgvManualControls from './AgvManualControls';
|
||||
import AgvAutoRunControls from './AgvAutoRunControls';
|
||||
|
||||
interface AgvControlsProps {
|
||||
agvState: AgvState;
|
||||
setMotion: (state: AgvMotionState) => void;
|
||||
setLift: (val: number) => void;
|
||||
setRunConfig: (config: AgvRunConfig) => void;
|
||||
setError: (error: string | null) => void;
|
||||
onTurn180: (direction: 'LEFT' | 'RIGHT') => void;
|
||||
setMagnet: (isOn: boolean) => void;
|
||||
setLiftStatus: (status: 'IDLE' | 'UP' | 'DOWN') => void;
|
||||
setLidar: (isOn: boolean) => void;
|
||||
}
|
||||
|
||||
const AgvControls: React.FC<AgvControlsProps> = ({ agvState, setMotion, setLift, setRunConfig, setError, onTurn180, setMagnet, setLiftStatus, setLidar }) => {
|
||||
const isRunning = agvState.motionState === AgvMotionState.RUNNING || agvState.motionState === AgvMotionState.MARK_STOPPING;
|
||||
const isError = agvState.error !== null;
|
||||
|
||||
const updateRunConfig = (key: keyof AgvRunConfig, value: any) => {
|
||||
if (isError) return;
|
||||
setRunConfig({
|
||||
...agvState.runConfig,
|
||||
[key]: value
|
||||
});
|
||||
};
|
||||
|
||||
const toggleRun = () => {
|
||||
if (isError) return;
|
||||
|
||||
if (isRunning) {
|
||||
setMotion(AgvMotionState.IDLE);
|
||||
} else {
|
||||
const isFwd = agvState.runConfig.direction === 'FWD';
|
||||
const hasLine = isFwd ? agvState.sensorLineFront : agvState.sensorLineRear;
|
||||
|
||||
if (!hasLine) {
|
||||
setError('LINE_OUT');
|
||||
return;
|
||||
}
|
||||
setMotion(AgvMotionState.RUNNING);
|
||||
}
|
||||
};
|
||||
|
||||
const handleMarkStop = () => {
|
||||
if (agvState.motionState === AgvMotionState.RUNNING) {
|
||||
setRunConfig({
|
||||
...agvState.runConfig,
|
||||
speedLevel: 'L'
|
||||
});
|
||||
setMotion(AgvMotionState.MARK_STOPPING);
|
||||
}
|
||||
};
|
||||
|
||||
const resetError = () => {
|
||||
setError(null);
|
||||
};
|
||||
|
||||
const handleLiftSliderChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
if (agvState.liftStatus !== 'IDLE') {
|
||||
setLiftStatus('IDLE');
|
||||
}
|
||||
setLift(parseInt(e.target.value));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 p-3 bg-gray-900 border-b border-gray-700">
|
||||
|
||||
{/* Error Overlay */}
|
||||
{isError && (
|
||||
<div className="bg-red-900/80 border border-red-600 p-3 rounded text-center animate-pulse">
|
||||
<div className="text-red-100 font-bold text-sm flex items-center justify-center gap-2 mb-2">
|
||||
<AlertTriangle size={18} />
|
||||
{agvState.error}
|
||||
</div>
|
||||
<button onClick={resetError} className="bg-red-600 hover:bg-red-500 text-white text-xs font-bold px-3 py-1 rounded shadow-sm">
|
||||
RESET ERROR
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Manual Operation (분리된 콤포넌트) */}
|
||||
<AgvManualControls
|
||||
agvState={agvState}
|
||||
setMotion={setMotion}
|
||||
updateRunConfig={updateRunConfig}
|
||||
onTurn180={onTurn180}
|
||||
handleMarkStop={handleMarkStop}
|
||||
isRunning={isRunning}
|
||||
isError={isError}
|
||||
/>
|
||||
|
||||
{/* Lift & Magnet */}
|
||||
<div className={`px-1 ${isError ? 'opacity-50 pointer-events-none' : ''}`}>
|
||||
<div className="flex justify-between text-xs text-gray-400 mb-1">
|
||||
<span className="flex items-center gap-1"><ChevronsUp size={12} /> Lift Height</span>
|
||||
<span className="font-mono text-white">{Math.round(agvState.liftHeight)}%</span>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
<button
|
||||
onClick={() => setLiftStatus('DOWN')}
|
||||
disabled={agvState.liftHeight === 0}
|
||||
className={`p-1.5 rounded disabled:opacity-30 transition-colors ${agvState.liftStatus === 'DOWN'
|
||||
? 'bg-blue-600 text-white shadow-[0_0_10px_rgba(37,99,235,0.5)]'
|
||||
: 'bg-gray-700 hover:bg-gray-600 text-gray-300'
|
||||
}`}
|
||||
title="Lower Lift"
|
||||
>
|
||||
<ChevronsDown size={16} />
|
||||
</button>
|
||||
|
||||
<input
|
||||
type="range"
|
||||
min="0"
|
||||
max="100"
|
||||
value={agvState.liftHeight}
|
||||
onChange={handleLiftSliderChange}
|
||||
className="flex-1 h-1.5 bg-gray-700 rounded-lg appearance-none cursor-pointer accent-blue-500"
|
||||
/>
|
||||
|
||||
<button
|
||||
onClick={() => setLiftStatus('UP')}
|
||||
disabled={agvState.liftHeight === 100}
|
||||
className={`p-1.5 rounded disabled:opacity-30 transition-colors ${agvState.liftStatus === 'UP'
|
||||
? 'bg-blue-600 text-white shadow-[0_0_10px_rgba(37,99,235,0.5)]'
|
||||
: 'bg-gray-700 hover:bg-gray-600 text-gray-300'
|
||||
}`}
|
||||
title="Raise Lift"
|
||||
>
|
||||
<ChevronsUp size={16} />
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button
|
||||
onClick={() => setMagnet(!agvState.magnetOn)}
|
||||
className={`w-full py-1.5 text-xs rounded border flex items-center justify-center gap-2 transition-colors ${agvState.magnetOn
|
||||
? 'bg-orange-600 text-white border-orange-500'
|
||||
: 'bg-gray-800 text-gray-400 border-gray-600 hover:bg-gray-700'
|
||||
}`}
|
||||
>
|
||||
<Magnet size={14} className={agvState.magnetOn ? "animate-pulse" : ""} />
|
||||
MAGNET {agvState.magnetOn ? 'ON' : 'OFF'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div className="h-px bg-gray-800" />
|
||||
|
||||
{/* Auto Run Controls */}
|
||||
{/* Auto Run Controls (분리된 콤포넌트) */}
|
||||
<AgvAutoRunControls
|
||||
agvState={agvState}
|
||||
updateRunConfig={updateRunConfig}
|
||||
toggleRun={toggleRun}
|
||||
isRunning={isRunning}
|
||||
isError={isError}
|
||||
setLidar={setLidar}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default AgvControls;
|
||||
Reference in New Issue
Block a user