fix: arreglar errores menores y ajustes visuales

This commit is contained in:
Alexis
2026-03-26 13:06:19 +01:00
parent 11f3fb1415
commit 62070970c8
8 changed files with 175 additions and 231 deletions
+4 -7
View File
@@ -1,14 +1,11 @@
export default function AddLevelButton({ handleAddLevel }) { export default function AddLevelButton({ handleAddLevel }) {
return ( return (
<div className="flex flex-col items-center mx-2 my-4"> <div className="flex flex-col items-center mx-2 my-2">
<button <button onClick={handleAddLevel} className="w-40 h-52 border-4 border-dashed border-slate-300 rounded-2xl flex flex-col items-center justify-center gap-2 text-slate-400 font-semibold hover:bg-blue-50 hover:border-blue-400 hover:text-blue-500 transition-all active:scale-[0.98] group">
onClick={handleAddLevel}
className="w-44 h-60 border-4 border-dashed border-slate-300 rounded-2xl flex flex-col items-center justify-center gap-2 text-slate-400 font-semibold hover:bg-blue-50 hover:border-blue-400 hover:text-blue-500 transition-all active:scale-[0.98] group"
>
<span className="text-4xl font-light leading-none group-hover:scale-110 transition-transform">+</span> <span className="text-4xl font-light leading-none group-hover:scale-110 transition-transform">+</span>
<span className="text-sm uppercase tracking-widest font-bold text-center px-4">Añadir Carta</span> <span className="text-xs uppercase tracking-widest font-bold text-center px-4">Añadir Carta</span>
</button> </button>
<div className="h-6 mt-3"></div> <div className="h-6 mt-2"></div>
</div> </div>
); );
} }
+39 -24
View File
@@ -1,36 +1,51 @@
export default function BlankCardsCounter({ index, blankCardsCount, handleBlankCardChange }) { export default function BlankCardsCounter({ index, blankCardsCount, handleBlankCardChange }) {
const maxCardsPerRow = 7;
const rows = [];
for (let i = 0; i < blankCardsCount; i += maxCardsPerRow) {
rows.push(Array.from({ length: Math.min(maxCardsPerRow, blankCardsCount - i) }));
}
return ( return (
<div className="flex flex-col items-center relative mx-1 mb-10"> <div className="flex flex-col items-center mx-1 my-2 z-10 w-32">
<div className="absolute w-full h-1 bg-slate-200 top-[40%] -translate-y-1/2 -z-10 rounded"></div> <div className="relative w-full h-52 flex flex-col items-center justify-center shrink-0">
{/* Botones de - y + */}
<div className="flex items-center gap-1 bg-white px-2 py-1.5 rounded-full shadow-sm border border-slate-200 z-10">
<button
onClick={() => handleBlankCardChange(index, -1)}
className="w-7 h-7 flex items-center justify-center rounded-full bg-slate-50 hover:bg-slate-200 text-slate-600 font-bold transition-colors"
>-</button>
<div className="flex flex-col items-center leading-none min-w-[3rem]"> {/* Línea conectora horizontal */}
<span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1">Blancas</span> <div className="absolute w-[calc(100%+2rem)] h-1 bg-slate-200 top-[50%] -translate-y-1/2 z-0 rounded"></div>
<span className="text-base font-black text-blue-600">{blankCardsCount}</span>
{/* Botones - y + */}
<div className="flex items-center gap-1 bg-white px-2 py-1.5 rounded-full shadow-sm border border-slate-200 z-10 relative">
<button
onClick={() => handleBlankCardChange(index, -1)}
className="w-7 h-7 flex items-center justify-center rounded-full bg-slate-50 hover:bg-slate-200 text-slate-600 font-bold transition-colors"
>-</button>
<div className="flex flex-col items-center leading-none min-w-[3rem]">
<span className="text-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1">Blancas</span>
<span className="text-base font-black text-blue-600">{blankCardsCount}</span>
</div>
<button
onClick={() => handleBlankCardChange(index, 1)}
className="w-7 h-7 flex items-center justify-center rounded-full bg-slate-50 hover:bg-slate-200 text-slate-600 font-bold transition-colors"
>+</button>
</div> </div>
<button
onClick={() => handleBlankCardChange(index, 1)}
className="w-7 h-7 flex items-center justify-center rounded-full bg-slate-50 hover:bg-slate-200 text-slate-600 font-bold transition-colors"
>+</button>
</div> </div>
{/* Cartas blancas */} {/* Cartas blancas */}
{blankCardsCount > 0 && ( {blankCardsCount > 0 && (
<div className="absolute top-[75px] flex flex-row items-center -space-x-4"> <div className="flex flex-col items-center gap-y-3 w-full justify-center -mt-16 relative z-0">
{Array.from({ length: blankCardsCount }).map((_, i) => ( {rows.map((row, rowIndex) => (
<div <div key={rowIndex} className="flex flex-row items-center justify-center -space-x-4">
key={i} {row.map((_, colIndex) => (
className="w-8 h-12 bg-white border-2 border-dashed border-slate-300 rounded shadow-sm opacity-90 transition-all hover:-translate-y-1" <div
style={{ zIndex: i }} key={`${rowIndex}-${colIndex}`}
></div> className="w-8 h-12 bg-white border-2 border-dashed border-slate-300 rounded shadow-sm opacity-90 transition-all hover:-translate-y-1"
style={{ zIndex: colIndex }}
></div>
))}
</div>
))} ))}
</div> </div>
)} )}
+6 -28
View File
@@ -1,40 +1,18 @@
export default function CardEditor({ index, level, handleLevelChange, handleRemoveLevel, totalLevels, error }) { export default function CardEditor({ index, level, handleLevelChange, handleRemoveLevel, totalLevels, error }) {
return ( return (
<div className="flex flex-col items-center mx-2 my-4"> <div className="flex flex-col items-center mx-2 my-2">
<div className={`relative w-44 h-60 bg-white border-2 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.08)] flex flex-col items-center justify-center transition-transform hover:-translate-y-2 hover:shadow-[0_12px_40px_rgb(0,0,0,0.12)] group ${ <div className={`relative w-40 h-52 bg-white border-2 rounded-2xl shadow-[0_8px_30px_rgb(0,0,0,0.08)] flex flex-col items-center justify-center transition-transform hover:-translate-y-2 hover:shadow-[0_12px_40px_rgb(0,0,0,0.12)] group ${
error ? 'border-red-400 shadow-red-100' : 'border-slate-200' error ? 'border-red-400 shadow-red-100' : 'border-slate-200'
}`}> }`}>
{totalLevels > 3 && ( {totalLevels > 3 && (
<button <button onClick={() => handleRemoveLevel(index)} className="absolute -top-3 -right-3 w-8 h-8 bg-white text-slate-400 rounded-full border border-slate-200 flex items-center justify-center font-bold hover:bg-red-500 hover:text-white hover:border-red-500 transition-colors z-10 opacity-0 group-hover:opacity-100 shadow-sm" title="Eliminar carta">×</button>
onClick={() => handleRemoveLevel(index)}
className="absolute -top-3 -right-3 w-8 h-8 bg-white text-slate-400 rounded-full border border-slate-200 flex items-center justify-center font-bold hover:bg-red-500 hover:text-white hover:border-red-500 transition-colors z-10 opacity-0 group-hover:opacity-100 shadow-sm"
title="Eliminar carta"
>
×
</button>
)} )}
<span className="absolute top-3 left-4 text-sm font-black text-slate-300">{index + 1}</span> <span className="absolute top-3 left-4 text-sm font-black text-slate-300">{index + 1}</span>
<span className="absolute bottom-3 right-4 text-sm font-black text-slate-300 rotate-180">{index + 1}</span> <span className="absolute bottom-3 right-4 text-sm font-black text-slate-300 rotate-180">{index + 1}</span>
<input type="text" placeholder="Etiqueta..." value={level} onChange={(e) => handleLevelChange(index, e.target.value)} className={`w-10/12 text-center text-lg font-bold text-slate-700 bg-transparent border-b-2 border-dashed outline-none pb-1 ${error ? 'border-red-300 focus:border-red-500 placeholder:text-red-200' : 'border-slate-300 focus:border-blue-500'}`} />
<input
type="text"
placeholder="Etiqueta..."
value={level}
onChange={(e) => handleLevelChange(index, e.target.value)}
className={`w-10/12 text-center text-xl font-bold text-slate-700 bg-transparent border-b-2 border-dashed outline-none pb-1 ${
error ? 'border-red-300 focus:border-red-500 placeholder:text-red-200' : 'border-slate-300 focus:border-blue-500'
}`}
/>
</div>
<div className="h-6 mt-3">
{error && (
<p className="text-red-500 text-sm font-semibold animate-pulse">
Escribe una etiqueta
</p>
)}
</div> </div>
<div className="h-6 mt-2">{error && <p className="text-red-500 text-xs font-semibold animate-pulse">Escribe una etiqueta</p>}</div>
</div> </div>
); );
} }
+15 -40
View File
@@ -1,49 +1,24 @@
import { Outlet, NavLink } from 'react-router-dom'; import { Outlet } from 'react-router-dom';
export const MainLayout = () => { export default function MainLayout() {
return ( return (
<div className="min-h-screen bg-slate-50 p-8 font-sans text-slate-800 flex flex-col items-center"> <div className="min-h-screen bg-slate-50 font-sans text-slate-900">
{/* Cabecera */}
<header className="text-center mb-8 flex flex-col items-center gap-6"> <header className="bg-white border-b border-slate-200 sticky top-0 z-50 shadow-sm">
<h1 className="text-4xl font-extrabold text-slate-900 tracking-tight"> <div className="max-w-7xl mx-auto px-4 h-14 flex items-center gap-3">
Método <span className="text-blue-600">Deck of Cards</span> <div className="w-8 h-8 bg-blue-600 rounded-lg flex items-center justify-center shadow-inner">
<span className="text-white font-black text-xl leading-none">DoC</span>
</div>
<h1 className="text-xl font-bold text-slate-800">
Deck of Cards Method
</h1> </h1>
</div>
</header> </header>
<div className="flex justify-center mb-10 w-full"> {/* Contenido principal */}
<div className="bg-slate-200/60 p-1.5 rounded-2xl flex gap-2 shadow-inner"> <main className="max-w-7xl mx-auto px-4 py-6">
<NavLink
to="/basico"
className={({ isActive }) =>
`px-8 py-3 rounded-xl font-bold transition-all duration-300 ${
isActive
? 'bg-white text-blue-600 shadow-md transform scale-105'
: 'text-slate-500 hover:text-slate-700 hover:bg-slate-300/50'
}`
}
>
DoC Básico
</NavLink>
<NavLink
to="/avanzado"
className={({ isActive }) =>
`px-8 py-3 rounded-xl font-bold transition-all duration-300 ${
isActive
? 'bg-white text-blue-600 shadow-md transform scale-105'
: 'text-slate-500 hover:text-slate-700 hover:bg-slate-300/50'
}`
}
>
DoC-MF Avanzado
</NavLink>
</div>
</div>
<main className="w-full flex flex-col items-center">
<Outlet /> <Outlet />
</main> </main>
</div> </div>
); );
}; }
@@ -5,41 +5,28 @@ export default function Chart({ baseScale, mfDefinitions, selectedTerm, colors }
const scaleKeys = Object.keys(baseScale); const scaleKeys = Object.keys(baseScale);
return ( return (
<div className="w-full bg-slate-50/50 rounded-2xl border border-slate-200 p-4 mb-10"> <div className="w-full bg-slate-50/50 rounded-2xl border border-slate-200 p-2 mb-6">
<ResponsiveContainer width="99%" height={450}> <ResponsiveContainer width="99%" height={320}>
<ComposedChart margin={{ top: 30, right: 30, left: 20, bottom: 20 }}> <ComposedChart margin={{ top: 20, right: 30, left: 10, bottom: 10 }}>
<CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" /> <CartesianGrid strokeDasharray="3 3" stroke="#e2e8f0" />
<XAxis type="number" dataKey="x" domain={[0, 1]} ticks={[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]} tick={{ fill: '#475569', fontWeight: 600 }} /> <XAxis type="number" dataKey="x" domain={[0, 1]} ticks={[0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1]} tick={{ fill: '#475569', fontWeight: 600, fontSize: 12 }} />
<YAxis domain={[0, 1]} tick={{ fill: '#475569' }} /> <YAxis domain={[0, 1]} tick={{ fill: '#475569', fontSize: 12 }} />
<Tooltip formatter={(value) => typeof value === 'number' ? value.toFixed(2) : value} /> <Tooltip formatter={(value) => typeof value === 'number' ? value.toFixed(2) : value} />
{scaleKeys.map((name, index) => { {scaleKeys.map((name, index) => {
const val = baseScale[name]; const val = baseScale[name];
const mf = mfDefinitions[name]; const mf = mfDefinitions[name];
if (!mf) return null; if (!mf) return null;
const color = colors[index % colors.length]; const color = colors[index % colors.length];
const isSelected = selectedTerm === name; const isSelected = selectedTerm === name;
const trapezeData = [ { x: mf.supportStart, y: 0 }, { x: mf.coreStart, y: 1 }, { x: mf.coreEnd, y: 1 }, { x: mf.supportEnd, y: 0 } ];
const trapezeData = [
{ x: mf.supportStart, y: 0 },
{ x: mf.coreStart, y: 1 },
{ x: mf.coreEnd, y: 1 },
{ x: mf.supportEnd, y: 0 },
];
return ( return (
<React.Fragment key={`mf-${name}`}> <React.Fragment key={`mf-${name}`}>
<ReferenceLine x={val} stroke={color} strokeDasharray="4 4" strokeWidth={isSelected ? 2 : 1} label={{ position: 'top', value: name, fill: color, fontWeight: isSelected ? '900' : 'normal' }} /> <ReferenceLine x={val} stroke={color} strokeDasharray="4 4" strokeWidth={isSelected ? 2 : 1} label={{ position: 'top', value: name, fill: color, fontWeight: isSelected ? '900' : 'normal', fontSize: 12 }} />
<ReferenceArea x1={mf.supportStart} x2={mf.supportEnd} fill={color} fillOpacity={isSelected ? 0.3 : 0.05} /> <ReferenceArea x1={mf.supportStart} x2={mf.supportEnd} fill={color} fillOpacity={isSelected ? 0.3 : 0.05} />
<ReferenceArea x1={mf.coreStart} x2={mf.coreEnd} fill={color} fillOpacity={isSelected ? 0.6 : 0.15} /> <ReferenceArea x1={mf.coreStart} x2={mf.coreEnd} fill={color} fillOpacity={isSelected ? 0.6 : 0.15} />
<Line data={trapezeData} dataKey="y" type="linear" stroke={color} strokeWidth={isSelected ? 4 : 2} dot={isSelected ? { r: 5, fill: color, stroke: '#fff', strokeWidth: 2 } : false} activeDot={false} isAnimationActive={false} />
<Line
data={trapezeData} dataKey="y" type="linear" stroke={color} strokeWidth={isSelected ? 4 : 2}
dot={isSelected ? { r: 6, fill: color, stroke: '#fff', strokeWidth: 2 } : false}
activeDot={false} isAnimationActive={false}
/>
</React.Fragment> </React.Fragment>
); );
})} })}
@@ -4,10 +4,7 @@ export default function Controls({ selectedTerm, currentMf, selectedColor, baseS
const scaleKeys = Object.keys(baseScale); const scaleKeys = Object.keys(baseScale);
const selectedIndex = scaleKeys.indexOf(selectedTerm); const selectedIndex = scaleKeys.indexOf(selectedTerm);
let prevCoreEnd = 0; let prevCoreEnd = 0, prevSupportEnd = 0, nextCoreStart = 1, nextSupportStart = 1;
let prevSupportEnd = 0;
let nextCoreStart = 1;
let nextSupportStart = 1;
if (selectedIndex > 0) { if (selectedIndex > 0) {
const prevTerm = scaleKeys[selectedIndex - 1]; const prevTerm = scaleKeys[selectedIndex - 1];
@@ -21,52 +18,42 @@ export default function Controls({ selectedTerm, currentMf, selectedColor, baseS
} }
return ( return (
<div className="bg-white p-8 rounded-2xl border border-slate-200 shadow-lg relative overflow-hidden"> <div className="bg-white p-6 rounded-2xl border border-slate-200 shadow-md relative overflow-hidden">
<div className="absolute top-0 left-0 w-full h-2" style={{ backgroundColor: selectedColor }}></div> <div className="absolute top-0 left-0 w-full h-1.5" style={{ backgroundColor: selectedColor }}></div>
<h3 className="text-2xl font-bold text-slate-800 mb-6 flex items-center gap-3"> <h3 className="text-xl font-bold text-slate-800 mb-4 flex items-center gap-2">
Ajustando franjas para: <span style={{ color: selectedColor }}>"{selectedTerm}"</span> Ajustando: <span style={{ color: selectedColor }}>"{selectedTerm}"</span>
</h3> </h3>
<div className="grid grid-cols-1 md:grid-cols-2 gap-10"> <div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-4">
{/* Columna izquierda: Inicios */}
<div className="space-y-6">
<div> <div>
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2"> <label className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Inicio del Núcleo (Límite: {prevSupportEnd.toFixed(2)})</span> <span>Inicio del Núcleo (Límite: {prevSupportEnd.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.coreStart.toFixed(3)}</span>
<span style={{ color: selectedColor }}>{currentMf.coreStart.toFixed(3)}</span>
</label> </label>
<input type="range" min={prevSupportEnd} max={nextCoreStart} step="0.001" value={currentMf.coreStart} onChange={(e) => updateCurrentMf('coreStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} /> <input type="range" min={prevSupportEnd} max={nextCoreStart} step="0.001" value={currentMf.coreStart} onChange={(e) => updateCurrentMf('coreStart', e.target.value)} className="w-full cursor-pointer h-1.5" style={{ accentColor: selectedColor }} />
</div> </div>
<div> <div>
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2"> <label className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Inicio del Soporte (Límite: {prevCoreEnd.toFixed(2)})</span> <span>Inicio del Soporte (Límite: {prevCoreEnd.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.supportStart.toFixed(3)}</span>
<span style={{ color: selectedColor }}>{currentMf.supportStart.toFixed(3)}</span>
</label> </label>
<input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportStart} onChange={(e) => updateCurrentMf('supportStart', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} /> <input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportStart} onChange={(e) => updateCurrentMf('supportStart', e.target.value)} className="w-full cursor-pointer h-1.5" style={{ accentColor: selectedColor, opacity: 0.7 }} />
</div> </div>
</div> </div>
{/* Columna derecha: Fines */} <div className="space-y-4">
<div className="space-y-6">
<div> <div>
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2"> <label className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Fin del Núcleo (Límite: {nextSupportStart.toFixed(2)})</span> <span>Fin del Núcleo (Límite: {nextSupportStart.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.coreEnd.toFixed(3)}</span>
<span style={{ color: selectedColor }}>{currentMf.coreEnd.toFixed(3)}</span>
</label> </label>
<input type="range" min={prevCoreEnd} max={nextSupportStart} step="0.001" value={currentMf.coreEnd} onChange={(e) => updateCurrentMf('coreEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor }} /> <input type="range" min={prevCoreEnd} max={nextSupportStart} step="0.001" value={currentMf.coreEnd} onChange={(e) => updateCurrentMf('coreEnd', e.target.value)} className="w-full cursor-pointer h-1.5" style={{ accentColor: selectedColor }} />
</div> </div>
<div> <div>
<label className="flex justify-between text-sm font-bold text-slate-600 mb-2"> <label className="flex justify-between text-xs font-bold text-slate-600 mb-1">
<span>Fin del Soporte (Límite: {nextCoreStart.toFixed(2)})</span> <span>Fin del Soporte (Límite: {nextCoreStart.toFixed(2)})</span><span style={{ color: selectedColor }}>{currentMf.supportEnd.toFixed(3)}</span>
<span style={{ color: selectedColor }}>{currentMf.supportEnd.toFixed(3)}</span>
</label> </label>
<input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportEnd} onChange={(e) => updateCurrentMf('supportEnd', e.target.value)} className="w-full cursor-pointer" style={{ accentColor: selectedColor, opacity: 0.7 }} /> <input type="range" min={prevCoreEnd} max={nextCoreStart} step="0.001" value={currentMf.supportEnd} onChange={(e) => updateCurrentMf('supportEnd', e.target.value)} className="w-full cursor-pointer h-1.5" style={{ accentColor: selectedColor, opacity: 0.7 }} />
</div> </div>
</div> </div>
</div> </div>
</div> </div>
); );
+71 -61
View File
@@ -1,4 +1,4 @@
import React, { useState } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import CriterionInput from '../components/CriterionInput'; import CriterionInput from '../components/CriterionInput';
import CardEditor from '../components/CardEditor'; import CardEditor from '../components/CardEditor';
import BlankCardsCounter from '../components/BlankCardsCounter'; import BlankCardsCounter from '../components/BlankCardsCounter';
@@ -22,6 +22,28 @@ export default function AdvancedMode() {
// Estado para controlar la lupa (Zoom) // Estado para controlar la lupa (Zoom)
const [isZoomActive, setIsZoomActive] = useState(true); const [isZoomActive, setIsZoomActive] = useState(true);
// Medición exacta con Refs
const containerRef = useRef(null);
const tableRef = useRef(null);
const [dimensions, setDimensions] = useState({ container: 1000, table: 0 });
useEffect(() => {
const updateMeasurements = () => {
if (containerRef.current && tableRef.current) {
setDimensions({
container: containerRef.current.offsetWidth,
table: tableRef.current.scrollWidth
});
}
};
const timeoutId = setTimeout(updateMeasurements, 50);
window.addEventListener('resize', updateMeasurements);
return () => {
clearTimeout(timeoutId);
window.removeEventListener('resize', updateMeasurements);
};
}, [levels, blankCards, step]);
// Estados Fase 2 (Franjas) // Estados Fase 2 (Franjas)
const [baseScale, setBaseScale] = useState({}); const [baseScale, setBaseScale] = useState({});
const [selectedTerm, setSelectedTerm] = useState(null); const [selectedTerm, setSelectedTerm] = useState(null);
@@ -56,7 +78,6 @@ export default function AdvancedMode() {
} catch (error) { alert("Error: " + error); } finally { setIsLoading(false); } } catch (error) { alert("Error: " + error); } finally { setIsLoading(false); }
}; };
// Manejadores de Franjas
const updateCurrentMf = (field, value) => { const updateCurrentMf = (field, value) => {
if (!selectedTerm) return; if (!selectedTerm) return;
let numValue = parseFloat(value); let numValue = parseFloat(value);
@@ -64,7 +85,6 @@ export default function AdvancedMode() {
setMfDefinitions(prev => { setMfDefinitions(prev => {
const scaleKeys = Object.keys(baseScale); const scaleKeys = Object.keys(baseScale);
const selectedIndex = scaleKeys.indexOf(selectedTerm); const selectedIndex = scaleKeys.indexOf(selectedTerm);
let prevCoreEnd = 0, prevSupportEnd = 0, nextCoreStart = 1, nextSupportStart = 1; let prevCoreEnd = 0, prevSupportEnd = 0, nextCoreStart = 1, nextSupportStart = 1;
if (selectedIndex > 0) { if (selectedIndex > 0) {
@@ -100,66 +120,62 @@ export default function AdvancedMode() {
const scaleKeys = Object.keys(baseScale); const scaleKeys = Object.keys(baseScale);
const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb'; const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb';
const totalElements = levels.length; const needsZoom = dimensions.table > dimensions.container;
const dynamicScale = totalElements > 6 ? 6.2 / totalElements : 1; const dynamicScale = needsZoom ? (dimensions.container / dimensions.table) * 0.95 : 1;
const currentScale = isZoomActive ? Math.max(0.4, dynamicScale) : 1; const currentScale = isZoomActive && needsZoom ? dynamicScale : 1;
return ( return (
<div className="w-full flex flex-col items-center"> <div className="w-full flex flex-col items-center">
{/* PASO 1 */} {/* PASO 1 */}
{step === 1 && ( {step === 1 && (
<div className="w-full bg-white p-8 rounded-3xl shadow-sm border border-slate-200 mb-12 flex flex-col items-center animate-fade-in overflow-hidden"> <div className="w-full bg-white p-6 rounded-2xl shadow-sm border border-slate-200 mb-6 flex flex-col items-center animate-fade-in relative overflow-visible">
<div className="flex justify-between items-center w-full mb-8 border-b pb-4"> <div className="flex justify-between items-center w-full mb-4 border-b pb-3 relative z-30">
<h2 className="text-2xl font-bold text-slate-800"> <h2 className="text-xl font-bold text-slate-800">
Paso 1: Escala de Referencia (Mesa) Paso 1: Escala de Referencia (Mesa)
</h2> </h2>
{needsZoom && (
{totalElements > 6 && ( <button onClick={() => setIsZoomActive(!isZoomActive)} className={`flex items-center gap-2 px-3 py-1.5 rounded-lg font-bold transition-all shadow-sm border text-sm ${isZoomActive ? 'bg-blue-50 border-blue-200 text-blue-700' : 'bg-white border-slate-200 text-slate-600'}`}>
<button <span>{isZoomActive ? '🔍' : '🖼️'}</span>
onClick={() => setIsZoomActive(!isZoomActive)} {isZoomActive ? 'Ver de cerca (Scroll)' : 'Ajustar mesa'}
className={`flex items-center gap-2 px-4 py-2 rounded-xl font-bold transition-all shadow-sm border-2 ${
isZoomActive
? 'bg-blue-50 border-blue-200 text-blue-700 hover:bg-blue-100'
: 'bg-white border-slate-200 text-slate-600 hover:bg-slate-50'
}`}
>
<span className="text-xl">{isZoomActive ? '🔍' : '🖼️'}</span>
{isZoomActive ? 'Ver de cerca' : 'Ajustar mesa a la pantalla'}
</button> </button>
)} )}
</div> </div>
<CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} /> <CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} />
<div className={`w-full mt-6 flex ${!isZoomActive ? 'overflow-x-auto pb-12 justify-start px-4' : 'overflow-hidden justify-center'}`}> <div ref={containerRef} className={`w-full mt-6 transition-all relative ${!isZoomActive && needsZoom ? 'overflow-x-auto flex justify-start pb-8 pt-8 px-4' : 'overflow-hidden flex justify-center pb-8 pt-8'}`}>
<div className={`flex flex-row items-start min-w-max transition-transform duration-500 ease-out px-4 origin-top`} style={{ transform: `scale(${currentScale})`, marginBottom: isZoomActive && currentScale < 1 ? `-${(1 - currentScale) * 300}px` : '0px' }}>
<div
className={`flex flex-row items-center min-w-max px-2 transition-transform duration-500 ease-out ${isZoomActive ? 'origin-top' : 'origin-top-left'}`}
style={{
transform: `scale(${currentScale})`,
marginBottom: isZoomActive && currentScale < 1 ? `-${(1 - currentScale) * 350}px` : '0px'
}}
>
{levels.map((level, index) => (
<React.Fragment key={index}>
<CardEditor index={index} level={level} handleLevelChange={handleLevelChange} handleRemoveLevel={handleRemoveLevel} totalLevels={levels.length} error={errors.levels[index]} canRemove={levels.length > 3} />
{index < levels.length - 1 && (
<BlankCardsCounter index={index} blankCardsCount={blankCards[index]} handleBlankCardChange={handleBlankCardChange} />
)}
</React.Fragment>
))}
<div className="w-6 h-1 bg-slate-200 mx-1 top-[40%]"></div> <div ref={tableRef} className="flex flex-row items-start relative px-10 overflow-visible">
<AddLevelButton handleAddLevel={handleAddLevel} />
</div> {levels.map((level, index) => (
<React.Fragment key={index}>
<div className="flex flex-col items-center mx-2 my-2 relative z-20">
<CardEditor index={index} level={level} handleLevelChange={handleLevelChange} handleRemoveLevel={handleRemoveLevel} totalLevels={levels.length} error={errors.levels[index]} canRemove={levels.length > 3} />
</div>
{index < levels.length - 1 && (
<BlankCardsCounter index={index} blankCardsCount={blankCards[index]} handleBlankCardChange={handleBlankCardChange} />
)}
</React.Fragment>
))}
<div className="mx-1 my-2 h-52 flex items-center justify-center">
<div className="w-10 h-1 bg-slate-200 rounded"></div>
</div>
<AddLevelButton handleAddLevel={handleAddLevel} />
</div>
</div>
</div> </div>
<div className="w-full max-w-lg mt-12 pt-8 border-t-2 border-slate-200 flex flex-col items-center z-20 relative bg-white"> <div className="w-full max-w-lg mt-2 pt-6 border-t border-slate-200 flex flex-col items-center z-20 relative bg-white">
<button onClick={handleGenerateBaseScale} disabled={isLoading} className={`w-full py-4 text-white text-xl font-bold rounded-xl shadow-lg transition-all active:scale-[0.98] ${isLoading ? 'bg-slate-400 cursor-not-allowed' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'}`}> <button onClick={handleGenerateBaseScale} disabled={isLoading} className={`w-full py-3 text-white text-lg font-bold rounded-xl shadow-md transition-all active:scale-[0.98] ${isLoading ? 'bg-slate-400 cursor-not-allowed' : 'bg-gradient-to-r from-blue-600 to-indigo-600 hover:from-blue-700 hover:to-indigo-700'}`}>
{isLoading ? 'Calculando...' : 'Generar Gráfica Continua'} {isLoading ? 'Calculando...' : 'Generar Gráfica Continua'}
</button> </button>
</div> </div>
@@ -168,19 +184,19 @@ export default function AdvancedMode() {
{/* PASO 2 */} {/* PASO 2 */}
{step === 2 && ( {step === 2 && (
<div className="w-full max-w-6xl bg-white p-10 rounded-3xl shadow-sm border border-slate-200 animate-fade-in"> <div className="w-full max-w-6xl bg-white p-6 rounded-2xl shadow-sm border border-slate-200 animate-fade-in relative overflow-visible">
<div className="flex justify-between items-center mb-8 border-b pb-4"> <div className="flex justify-between items-center mb-6 border-b pb-3">
<h2 className="text-2xl font-bold text-slate-800">Paso 2: Modelar Conceptos Difusos</h2> <h2 className="text-xl font-bold text-slate-800">Paso 2: Modelar Conceptos Difusos</h2>
<button onClick={() => setStep(1)} className="text-slate-500 hover:text-blue-600 font-semibold underline"> Volver a las cartas</button> <button onClick={() => setStep(1)} className="text-slate-500 hover:text-blue-600 text-sm font-semibold underline"> Volver a las cartas</button>
</div> </div>
<div className="flex flex-wrap justify-center gap-4 mb-8"> <div className="flex flex-wrap justify-center gap-3 mb-6">
{scaleKeys.map((name, index) => { {scaleKeys.map((name, index) => {
const color = COLORS[index % COLORS.length]; const color = COLORS[index % COLORS.length];
const isSelected = selectedTerm === name; const isSelected = selectedTerm === name;
return ( return (
<button key={name} onClick={() => setSelectedTerm(name)} style={isSelected ? { backgroundColor: color, borderColor: color, color: '#fff' } : { borderColor: color, color: '#475569' }} className={`px-6 py-3 rounded-xl font-bold border-2 transition-all duration-300 flex flex-col items-center shadow-sm hover:shadow-md ${isSelected ? 'transform scale-105' : 'bg-white opacity-80 hover:opacity-100'}`}> <button key={name} onClick={() => setSelectedTerm(name)} style={isSelected ? { backgroundColor: color, borderColor: color, color: '#fff' } : { borderColor: color, color: '#475569' }} className={`px-5 py-2 rounded-lg font-bold border-2 transition-all duration-300 flex flex-col items-center shadow-sm hover:shadow-md ${isSelected ? 'transform scale-105' : 'bg-white opacity-80 hover:opacity-100'}`}>
<span>{name}</span><span className="text-xs font-normal opacity-80">(X: {baseScale[name].toFixed(2)})</span> <span>{name}</span><span className="text-[10px] font-normal opacity-80">(X: {baseScale[name].toFixed(2)})</span>
</button> </button>
); );
})} })}
@@ -188,16 +204,10 @@ export default function AdvancedMode() {
<Chart baseScale={baseScale} mfDefinitions={mfDefinitions} selectedTerm={selectedTerm} colors={COLORS} /> <Chart baseScale={baseScale} mfDefinitions={mfDefinitions} selectedTerm={selectedTerm} colors={COLORS} />
<Controls <Controls selectedTerm={selectedTerm} currentMf={mfDefinitions[selectedTerm]} selectedColor={selectedColor} baseScale={baseScale} mfDefinitions={mfDefinitions} updateCurrentMf={updateCurrentMf} />
selectedTerm={selectedTerm}
currentMf={mfDefinitions[selectedTerm]} <div className="w-full mt-8 flex justify-center">
selectedColor={selectedColor} <button onClick={handleFinalSubmit} className="px-10 py-3 bg-slate-900 text-white text-lg font-bold rounded-xl shadow-md hover:bg-black hover:shadow-lg transition-all">
baseScale={baseScale}
mfDefinitions={mfDefinitions}
updateCurrentMf={updateCurrentMf}
/>
<div className="w-full mt-12 flex justify-center">
<button onClick={handleFinalSubmit} className="px-12 py-4 bg-slate-900 text-white text-xl font-bold rounded-xl shadow-lg hover:bg-black hover:shadow-xl transition-all">
Guardar Todo el Espectro Difuso Guardar Todo el Espectro Difuso
</button> </button>
</div> </div>
+12 -17
View File
@@ -1,21 +1,16 @@
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import BasicMode from '../pages/BasicMode'; import MainLayout from '../components/layout/MainLayout';
import AdvancedMode from '../pages/AdvancedMode'; import AdvancedMode from '../pages/AdvancedMode';
import { MainLayout } from '../components/layout/MainLayout';
export const AppRouter = () => { export function AppRouter() {
return ( return (
<Router> <BrowserRouter>
<Routes> <Routes>
<Route path="/" element={<MainLayout />}>
<Route element={<MainLayout />}> <Route index element={<AdvancedMode />} />
<Route path="/basico" element={<BasicMode />} /> <Route path="*" element={<Navigate to="/" replace />} />
<Route path="/avanzado" element={<AdvancedMode />} /> </Route>
</Route> </Routes>
</BrowserRouter>
<Route path="/*" element={<Navigate to="/basico" />} />
</Routes>
</Router>
); );
}; }