refactor: mejorar diseño y funcionalidad de componentes en el editor

This commit is contained in:
Alexis
2026-04-07 12:47:13 +02:00
parent 392a1fb36c
commit 9602c4f509
5 changed files with 107 additions and 86 deletions
+18 -22
View File
@@ -1,3 +1,5 @@
import React from 'react';
export default function BlankCardsCounter({ index, blankCardsCount, handleBlankCardChange }) { export default function BlankCardsCounter({ index, blankCardsCount, handleBlankCardChange }) {
const maxCardsPerRow = 7; const maxCardsPerRow = 7;
@@ -7,35 +9,29 @@ export default function BlankCardsCounter({ index, blankCardsCount, handleBlankC
} }
return ( return (
<div className="flex flex-col items-center mx-1 my-2 z-10 w-32"> <div className="flex flex-col items-center w-full">
<div className="relative w-full h-52 flex flex-col items-center justify-center shrink-0"> {/* Bloque de botones */}
<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 shrink-0">
<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>
{/* Línea conectora horizontal */} <div className="flex flex-col items-center leading-none min-w-[3rem]">
<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-[9px] font-bold text-slate-400 uppercase tracking-widest mb-1">Blancas</span>
<span className="text-base font-black text-blue-600 leading-none">{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="flex flex-col items-center gap-y-3 w-full justify-center -mt-16 relative z-0"> <div className="flex flex-col items-center gap-y-2 w-full mt-3 relative z-0">
{rows.map((row, rowIndex) => ( {rows.map((row, rowIndex) => (
<div key={rowIndex} className="flex flex-row items-center justify-center -space-x-4"> <div key={rowIndex} className="flex flex-row items-center justify-center -space-x-4">
{row.map((_, colIndex) => ( {row.map((_, colIndex) => (
+2 -2
View File
@@ -10,9 +10,9 @@ export default function CardEditor({ index, level, handleLevelChange, handleRemo
)} )}
<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="Término..." 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'}`} />
</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 className="h-6 mt-2">{error && <p className="text-red-500 text-xs font-semibold animate-pulse">Escribe un término</p>}</div>
</div> </div>
); );
} }
@@ -59,30 +59,48 @@ export default function Step1BaseScale({
<CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} /> <CriterionInput criterionName={criterionName} setCriterionName={handleCriterionChange} error={errors.criterion} />
<div ref={containerRef} className={`w-full mt-2 transition-all relative ${!isZoomActive && needsZoom ? 'overflow-x-auto flex justify-start pb-8 pt-4 px-4' : 'overflow-hidden flex justify-center pb-8 pt-4'}`}> <div ref={containerRef} className={`w-full mt-2 transition-all relative ${!isZoomActive && needsZoom ? 'overflow-x-auto flex justify-start pb-12 pt-4 px-4 custom-scrollbar' : 'overflow-visible flex justify-center pb-12 pt-4'}`}>
<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-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 ref={tableRef} className="flex flex-row items-start relative px-10 overflow-visible"> <div ref={tableRef} className="flex flex-row items-start relative px-10 overflow-visible">
{levels.map((level, index) => ( {levels.map((level, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
<div className="flex flex-col items-center mx-2 my-2 relative z-20">
{/* CARTA DE NIVEL */}
<div className="flex flex-col items-center mx-2 relative z-20">
<CardEditor index={index} level={level} handleLevelChange={handleLevelChange} handleRemoveLevel={handleRemoveLevel} totalLevels={levels.length} error={errors.levels[index]} canRemove={levels.length > 3} /> <CardEditor index={index} level={level} handleLevelChange={handleLevelChange} handleRemoveLevel={handleRemoveLevel} totalLevels={levels.length} error={errors.levels[index]} canRemove={levels.length > 3} />
</div> </div>
{/* HUECO ENTRE CARTAS Y CONTADOR */}
{index < levels.length - 1 && ( {index < levels.length - 1 && (
<BlankCardsCounter index={index} blankCardsCount={blankCards[index]} handleBlankCardChange={handleBlankCardChange} /> <div className="flex flex-col items-center justify-start mx-1 relative min-w-[120px]">
<div className="absolute w-[calc(100%+2rem)] h-1 bg-slate-200 top-[80px] -translate-y-1/2 z-0"></div>
<div className="mt-[60px] flex flex-col items-center relative z-10 w-full">
<BlankCardsCounter index={index} blankCardsCount={blankCards[index]} handleBlankCardChange={handleBlankCardChange} />
</div>
</div>
)} )}
</React.Fragment> </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> {/* LÍNEA HACIA EL BOTÓN DE AÑADIR */}
<div className="flex flex-col items-center justify-start relative min-w-[40px]">
<div className="absolute w-full h-1 bg-slate-200 top-[80px] -translate-y-1/2 z-0 rounded"></div>
</div> </div>
<AddLevelButton handleAddLevel={handleAddLevel} />
{/* BOTÓN AÑADIR NIVEL */}
<div className="flex flex-col items-center mx-2 relative z-20">
<AddLevelButton handleAddLevel={handleAddLevel} />
</div>
</div> </div>
</div> </div>
</div> </div>
<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"> {/* Generar Gráfica Continua */}
<div className="w-full max-w-lg mt-8 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-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'}`}> <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>
@@ -79,7 +79,7 @@ export default function Step2FuzzyModeling({
<div className="w-full mt-8 flex justify-center"> <div className="w-full mt-8 flex justify-center">
<button onClick={handleFinalSubmit} className="px-10 py-3 bg-slate-900 text-white text-lg font-bold rounded-xl shadow-md hover:bg-slate-800 transition-colors"> <button onClick={handleFinalSubmit} className="px-10 py-3 bg-slate-900 text-white text-lg font-bold rounded-xl shadow-md hover:bg-slate-800 transition-colors">
Guardar Todo el Espectro Difuso Generar el Espectro Difuso
</button> </button>
</div> </div>
</div> </div>
@@ -3,10 +3,19 @@ import BlankCardsCounter from '../BlankCardsCounter';
export default function SubscaleModal({ onClose, onSave, targetInfo }) { export default function SubscaleModal({ onClose, onSave, targetInfo }) {
const [cardsCount, setCardsCount] = useState(targetInfo?.initialData?.cardsCount || 2); const initialCount = Math.max(3, targetInfo?.initialData?.cardsCount || 3);
const [cardsCount, setCardsCount] = useState(initialCount);
const [blankCards, setBlankCards] = useState(() => { const [blankCards, setBlankCards] = useState(() => {
const initialBlanks = targetInfo?.initialData?.blankCards || [0]; let initialBlanks = targetInfo?.initialData?.blankCards;
if (!initialBlanks || initialBlanks.length === 0) {
initialBlanks = [0, 0];
} else if (initialBlanks.length < initialCount - 1) {
const padding = Array(initialCount - 1 - initialBlanks.length).fill(0);
initialBlanks = [...initialBlanks, ...padding];
}
return initialBlanks.map(b => { return initialBlanks.map(b => {
if (Array.isArray(b)) { if (Array.isArray(b)) {
return { min: b[0], max: b[1], isRange: true }; return { min: b[0], max: b[1], isRange: true };
@@ -21,7 +30,7 @@ export default function SubscaleModal({ onClose, onSave, targetInfo }) {
}; };
const handleRemoveCard = () => { const handleRemoveCard = () => {
if (cardsCount <= 2) return; if (cardsCount <= 3) return;
setCardsCount(prev => prev - 1); setCardsCount(prev => prev - 1);
setBlankCards(blankCards.slice(0, -1)); setBlankCards(blankCards.slice(0, -1));
}; };
@@ -73,10 +82,10 @@ export default function SubscaleModal({ onClose, onSave, targetInfo }) {
}; };
return ( return (
<div className="fixed inset-0 z-[100] flex items-center justify-center bg-slate-900/40 backdrop-blur-sm animate-fade-in"> <div className="fixed inset-0 z-[100] flex items-center justify-center bg-slate-900/40 backdrop-blur-sm animate-fade-in py-4">
<div className="bg-white w-full max-w-6xl p-8 rounded-3xl shadow-2xl mx-4 flex flex-col"> <div className="bg-white w-full max-w-6xl p-8 rounded-3xl shadow-2xl mx-4 flex flex-col max-h-[95vh]">
<div className="flex justify-between items-center mb-6 border-b pb-4"> <div className="flex justify-between items-center mb-4 border-b pb-4 shrink-0">
<div> <div>
<h2 className="text-2xl font-bold text-slate-800">Diseñar Subescala</h2> <h2 className="text-2xl font-bold text-slate-800">Diseñar Subescala</h2>
<p className="text-slate-500 font-medium"> <p className="text-slate-500 font-medium">
@@ -86,16 +95,15 @@ export default function SubscaleModal({ onClose, onSave, targetInfo }) {
<button onClick={onClose} className="w-10 h-10 bg-slate-100 hover:bg-slate-200 text-slate-600 rounded-full font-bold transition-colors"></button> <button onClick={onClose} className="w-10 h-10 bg-slate-100 hover:bg-slate-200 text-slate-600 rounded-full font-bold transition-colors"></button>
</div> </div>
{/* Tablero de Cartas */} <div className="w-full overflow-y-auto overflow-x-auto flex justify-start flex-1 custom-scrollbar px-2 pt-6 pb-12">
<div className="w-full py-10 overflow-x-auto flex justify-start px-4">
<div className="flex flex-row items-start min-w-max relative"> <div className="flex flex-row items-start min-w-max relative">
{Array.from({ length: cardsCount }).map((_, index) => ( {Array.from({ length: cardsCount }).map((_, index) => (
<React.Fragment key={index}> <React.Fragment key={index}>
{/* CARTA DE REFERENCIA */} {/* CARTA DE REFERENCIA */}
<div className="flex flex-col items-center mx-2 my-2 relative z-20"> <div className="flex flex-col items-center mx-2 relative z-20">
<div className="relative w-32 h-40 bg-slate-50 border-2 border-slate-300 rounded-2xl shadow-sm flex flex-col items-center justify-center group"> <div className="relative w-32 h-40 bg-slate-50 border-2 border-slate-300 rounded-2xl shadow-sm flex flex-col items-center justify-center group">
{cardsCount > 2 && index === cardsCount - 1 && ( {cardsCount > 3 && index === cardsCount - 1 && (
<button onClick={handleRemoveCard} 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 transition-colors z-10 shadow-sm opacity-0 group-hover:opacity-100"></button> <button onClick={handleRemoveCard} 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 transition-colors z-10 shadow-sm opacity-0 group-hover:opacity-100"></button>
)} )}
<span className="text-4xl font-black text-slate-200">{index + 1}</span> <span className="text-4xl font-black text-slate-200">{index + 1}</span>
@@ -104,58 +112,57 @@ export default function SubscaleModal({ onClose, onSave, targetInfo }) {
{/* HUECO ENTRE CARTAS */} {/* HUECO ENTRE CARTAS */}
{index < cardsCount - 1 && ( {index < cardsCount - 1 && (
<div className="flex flex-col items-center justify-center mx-1 h-40 relative"> <div className="flex flex-col items-center justify-start mx-1 relative min-w-[120px]">
<div className="absolute w-[calc(100%+2rem)] h-1 bg-slate-200 top-[80px] -translate-y-1/2 z-0"></div>
{/* Controles de números */}
{blankCards[index].isRange ? ( <div className="mt-[60px] flex flex-col items-center relative z-10 w-full">
// MODO RANGO {blankCards[index].isRange ? (
<div className="flex gap-1 items-center"> <div className="flex gap-2 items-start w-full justify-center">
<div className="flex flex-col items-center"> <div className="flex flex-col items-center relative">
<span className="text-[10px] font-bold text-slate-500 mb-1">MÍN</span> <span className="absolute bottom-full mb-1 text-[10px] font-bold text-slate-500">MÍN</span>
<BlankCardsCounter <BlankCardsCounter
index={index} index={index}
blankCardsCount={blankCards[index].min} blankCardsCount={blankCards[index].min}
handleBlankCardChange={(idx, delta) => handleMinChange(idx, delta)} handleBlankCardChange={(idx, delta) => handleMinChange(idx, delta)}
/> />
</div>
<div className="font-bold text-slate-300 mt-1">-</div>
<div className="flex flex-col items-center relative">
<span className="absolute bottom-full mb-1 text-[10px] font-bold text-slate-500">MÁX</span>
<BlankCardsCounter
index={index}
blankCardsCount={blankCards[index].max}
handleBlankCardChange={(idx, delta) => handleMaxChange(idx, delta)}
/>
</div>
</div> </div>
{/* Guión separador para unificar visualmente el rango */} ) : (
<span className="text-slate-300 font-bold mt-3">-</span> <div className="flex flex-col items-center relative">
<div className="flex flex-col items-center"> <span className="absolute bottom-full mb-1 text-[10px] font-bold text-slate-500">CARTAS</span>
<span className="text-[10px] font-bold text-slate-500 mb-1">MÁX</span> <BlankCardsCounter
<BlankCardsCounter index={index}
index={index} blankCardsCount={blankCards[index].min}
blankCardsCount={blankCards[index].max} handleBlankCardChange={(idx, delta) => handleExactChange(idx, delta)}
handleBlankCardChange={(idx, delta) => handleMaxChange(idx, delta)} />
/>
</div> </div>
</div> )}
) : (
// MODO EXACTO
<div className="flex flex-col items-center">
<span className="text-[10px] font-bold text-slate-500 mb-1">CARTAS</span>
<BlankCardsCounter
index={index}
blankCardsCount={blankCards[index].min}
handleBlankCardChange={(idx, delta) => handleExactChange(idx, delta)}
/>
</div>
)}
{/* Botón Toggle */}
<button
onClick={() => toggleRangeMode(index)}
className="absolute -bottom-6 text-[10px] font-semibold text-blue-600 hover:text-blue-800 hover:underline transition-colors text-center w-max"
>
{blankCards[index].isRange ? "Conozco la distancia" : "¿Dudas? Rango"}
</button>
<button
onClick={() => toggleRangeMode(index)}
className="mt-4 text-[11px] font-semibold text-blue-600 hover:text-blue-800 hover:bg-blue-50 px-4 py-2 rounded-full border border-transparent hover:border-blue-200 transition-all text-center w-max cursor-pointer z-20"
>
{blankCards[index].isRange ? "Conozco la distancia" : "¿Dudas? Rango"}
</button>
</div>
</div> </div>
)} )}
</React.Fragment> </React.Fragment>
))} ))}
{/* Botón Añadir Carta */} {/* Botón Añadir Carta */}
<div className="mx-2 my-2 h-40 flex items-center"> <div className="flex flex-col items-center mx-2 relative z-20">
<button onClick={handleAddCard} className="w-32 h-40 border-4 border-dashed border-slate-300 rounded-2xl flex flex-col items-center justify-center text-slate-400 font-bold hover:bg-blue-50 hover:border-blue-400 hover:text-blue-500 transition-colors group"> <button onClick={handleAddCard} className="w-32 h-40 border-4 border-dashed border-slate-300 rounded-2xl flex flex-col items-center justify-center text-slate-400 font-bold hover:bg-blue-50 hover:border-blue-400 hover:text-blue-500 transition-colors group">
<span className="text-3xl group-hover:scale-110 transition-transform">+</span> <span className="text-3xl group-hover:scale-110 transition-transform">+</span>
</button> </button>
@@ -165,7 +172,7 @@ export default function SubscaleModal({ onClose, onSave, targetInfo }) {
</div> </div>
{/* Botones de Acción */} {/* Botones de Acción */}
<div className="mt-8 flex justify-between items-center border-t pt-6"> <div className="mt-4 flex justify-between items-center border-t pt-6 shrink-0">
<button onClick={handleDelete} className="px-6 py-3 rounded-xl font-bold text-red-500 hover:bg-red-50 transition-colors"> <button onClick={handleDelete} className="px-6 py-3 rounded-xl font-bold text-red-500 hover:bg-red-50 transition-colors">
Borrar Subescala Borrar Subescala
</button> </button>