adn / frontend /src /components /ControlPanel.tsx
dimensionalpulsar's picture
Upload 38 files
9a16769 verified
Raw History Blame Contribute Delete
6.24 kB
import { useStore } from '../store';
import { useAIAdaptation } from '../hooks/useAIAdaptation';
interface SliderProps {
label: string;
value: number;
min: number;
max: number;
step: number;
unit: string;
onChange: (v: number) => void;
colorClass?: string;
}
function Slider({ label, value, min, max, step, unit, onChange, colorClass }: SliderProps) {
return (
<div className="flex flex-col gap-1">
<div className="flex justify-between text-xs text-blue-200 font-mono">
<span>{label}</span>
<span className={`font-bold ${colorClass ?? 'text-cyan-300'}`}>
{value.toFixed(1)} {unit}
</span>
</div>
<input
type="range"
min={min}
max={max}
step={step}
value={value}
onChange={(e) => onChange(parseFloat(e.target.value))}
className="slider-thumb w-full h-1 appearance-none rounded cursor-pointer
bg-gradient-to-r from-blue-900 to-cyan-500 accent-cyan-400"
/>
<div className="flex justify-between text-[10px] text-blue-600">
<span>{min}{unit}</span>
<span>{max}{unit}</span>
</div>
</div>
);
}
function Badge({ children, color }: { children: React.ReactNode; color: string }) {
return (
<span className={`px-2 py-0.5 rounded text-xs font-bold ${color}`}>
{children}
</span>
);
}
export default function ControlPanel() {
useAIAdaptation(); // attach AI calls
const {
waveAmplitude, setWaveAmplitude,
temperature, setTemperature,
waveExpansion, setWaveExpansion,
finScale, finDesign, resistance, designNote,
isLoadingAI, aiSource,
} = useStore();
return (
<div
className="absolute top-4 left-4 z-10 flex flex-col gap-4 p-5 rounded-2xl
backdrop-blur-md bg-[rgba(8,20,45,0.82)] border border-blue-800
shadow-2xl text-white select-none"
style={{ width: 280 }}
>
{/* Header */}
<div className="flex items-center gap-2 border-b border-blue-800 pb-3">
<span className="text-2xl">🚢</span>
<div>
<h1 className="text-sm font-bold text-cyan-300 leading-none">NavSim 3D</h1>
<p className="text-[10px] text-blue-400">Simulación Hidrodinámica</p>
</div>
{isLoadingAI && (
<span className="ml-auto text-[10px] text-yellow-300 animate-pulse">⟳ IA activa</span>
)}
</div>
{/* Sliders */}
<Slider
label="🌊 Oleaje (Amplitud)"
value={waveAmplitude}
min={0} max={8} step={0.1}
unit="m"
onChange={setWaveAmplitude}
colorClass="text-cyan-300"
/>
<Slider
label="🌡 Temperatura"
value={temperature}
min={0} max={40} step={0.5}
unit="°C"
onChange={setTemperature}
colorClass="text-orange-300"
/>
<Slider
label="↔ Expansión de Olas"
value={waveExpansion}
min={0.1} max={4.0} step={0.1}
unit="×"
onChange={setWaveExpansion}
colorClass="text-purple-300"
/>
{/* AI Results */}
<div className="border-t border-blue-800 pt-3 flex flex-col gap-2">
<div className="flex items-center justify-between">
<span className="text-xs text-blue-300 font-semibold">🤖 IA Adaptativa</span>
{aiSource === 'huggingface' && <Badge color="bg-green-900 text-green-300">HuggingFace</Badge>}
{aiSource === 'algorithmic' && <Badge color="bg-yellow-900 text-yellow-300">Algorítmico</Badge>}
</div>
{/* Fin Scale meter */}
<div className="flex items-center gap-2">
<span className="text-[10px] text-blue-400 w-20">Aletas</span>
<div className="flex-1 bg-blue-950 rounded h-2">
<div
className="h-2 rounded bg-gradient-to-r from-blue-500 to-cyan-400 transition-all duration-700"
style={{ width: `${((finScale - 0.5) / 1.5) * 100}%` }}
/>
</div>
<span className="text-[11px] text-cyan-300 font-mono w-8">{finScale.toFixed(2)}×</span>
</div>
{/* Fin Design */}
{finDesign && (
<div className="bg-blue-950 rounded-lg p-2 text-[10px] text-blue-300 font-mono grid grid-cols-2 gap-x-3 gap-y-0.5">
<span>Perfil:</span><span className="text-cyan-200">{finDesign.profile}</span>
<span>Envergadura:</span><span className="text-cyan-200">{finDesign.span} m</span>
<span>Flecha:</span><span className="text-cyan-200">{finDesign.sweepAngle}°</span>
<span>Espesor:</span><span className="text-cyan-200">{(finDesign.thickness * 100).toFixed(1)}%</span>
<span>Cantidad:</span><span className="text-cyan-200">{finDesign.quantity}</span>
</div>
)}
{/* Resistance */}
{resistance && (
<div className="bg-blue-950 rounded-lg p-2 text-[10px] font-mono">
<p className="text-blue-400 mb-1 font-semibold">Resistencia (N)</p>
<div className="grid grid-cols-2 gap-x-3 gap-y-0.5">
<span className="text-blue-300">Oleaje:</span>
<span className="text-red-300">{resistance.waveResistance.toLocaleString()}</span>
<span className="text-blue-300">Viscosa:</span>
<span className="text-orange-300">{resistance.viscousResistance.toLocaleString()}</span>
<span className="text-blue-300">Aletas:</span>
<span className="text-yellow-300">{resistance.finPenalty.toLocaleString()}</span>
<span className="text-blue-200 font-bold">Total:</span>
<span className="text-white font-bold">{resistance.totalResistance.toLocaleString()}</span>
<span className="text-blue-300">Fn:</span>
<span className="text-purple-300">{resistance.froudeNumber}</span>
<span className="text-blue-300">Re:</span>
<span className="text-purple-300">{resistance.reynoldsNumber}</span>
</div>
</div>
)}
{/* Design note */}
<p className="text-[9px] text-blue-500 italic leading-tight">{designNote}</p>
</div>
</div>
);
}