add: manejar errores para que no lleguen datos vacíos al endpoint
This commit is contained in:
@@ -1,33 +1,44 @@
|
|||||||
export default function CardEditor({ index, level, handleLevelChange, handleRemoveLevel, totalLevels }) {
|
export default function CardEditor({ index, level, handleLevelChange, handleRemoveLevel, totalLevels, error }) {
|
||||||
return (
|
return (
|
||||||
<div className="relative w-72 h-44 bg-white border-2 border-slate-200 rounded-xl shadow-[0_8px_30px_rgb(0,0,0,0.08)] flex flex-col items-center justify-center transition-transform hover:-translate-y-1 hover:shadow-[0_12px_40px_rgb(0,0,0,0.12)] group">
|
<div className="flex flex-col items-center">
|
||||||
|
<div className={`relative w-72 h-44 bg-white border-2 rounded-xl shadow-[0_8px_30px_rgb(0,0,0,0.08)] flex flex-col items-center justify-center transition-transform hover:-translate-y-1 hover:shadow-[0_12px_40px_rgb(0,0,0,0.12)] group ${
|
||||||
|
error ? 'border-red-400 shadow-red-100' : 'border-slate-200'
|
||||||
|
}`}>
|
||||||
|
{/* Botón para eliminar */}
|
||||||
|
{totalLevels > 2 && (
|
||||||
|
<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>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Detalles tipo naipe */}
|
||||||
|
<span className="absolute top-4 left-4 text-sm font-black text-slate-300">{index + 1}</span>
|
||||||
|
<span className="absolute bottom-4 right-4 text-sm font-black text-slate-300 rotate-180">{index + 1}</span>
|
||||||
|
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
placeholder="Escribe aquí..."
|
||||||
|
value={level}
|
||||||
|
onChange={(e) => handleLevelChange(index, e.target.value)}
|
||||||
|
className={`w-4/5 text-center text-2xl 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>
|
||||||
|
|
||||||
|
{/* Mensaje de error */}
|
||||||
|
<div className="h-6 mt-2">
|
||||||
|
{error && (
|
||||||
|
<p className="text-red-500 text-sm font-semibold animate-pulse">
|
||||||
|
Escribe una etiqueta
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Botón Eliminar */}
|
|
||||||
{totalLevels > 2 && (
|
|
||||||
<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>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{/* Detalles tipo naipe */}
|
|
||||||
<span className="absolute top-4 left-4 text-sm font-black text-slate-300">
|
|
||||||
{index + 1}
|
|
||||||
</span>
|
|
||||||
<span className="absolute bottom-4 right-4 text-sm font-black text-slate-300 rotate-180">
|
|
||||||
{index + 1}
|
|
||||||
</span>
|
|
||||||
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
placeholder="Escribe aquí..."
|
|
||||||
value={level}
|
|
||||||
onChange={(e) => handleLevelChange(index, e.target.value)}
|
|
||||||
className="w-4/5 text-center text-2xl font-bold text-slate-700 bg-transparent border-b-2 border-dashed border-slate-300 focus:border-blue-500 outline-none pb-1"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
export default function CriterionInput({ criterionName, setCriterionName }) {
|
export default function CriterionInput({ criterionName, setCriterionName, error }) {
|
||||||
return (
|
return (
|
||||||
<div className="w-full max-w-2xl bg-white p-6 rounded-2xl shadow-sm border border-slate-200 mb-12">
|
<div className="w-full max-w-2xl bg-white p-6 rounded-2xl shadow-sm border border-slate-200 mb-12">
|
||||||
<label className="block text-sm font-bold text-slate-400 uppercase tracking-widest mb-2 text-center">
|
<label className="block text-sm font-bold text-slate-400 uppercase tracking-widest mb-2 text-center">
|
||||||
@@ -9,8 +9,19 @@ export default function CriterionInput({ criterionName, setCriterionName }) {
|
|||||||
placeholder="Ej. Calidad del aceite..."
|
placeholder="Ej. Calidad del aceite..."
|
||||||
value={criterionName}
|
value={criterionName}
|
||||||
onChange={(e) => setCriterionName(e.target.value)}
|
onChange={(e) => setCriterionName(e.target.value)}
|
||||||
className="w-full text-3xl font-bold p-2 text-center text-slate-700 border-b-2 border-transparent hover:border-slate-200 focus:border-blue-500 outline-none transition-colors"
|
className={`w-full text-3xl font-bold p-2 text-center text-slate-700 border-b-2 outline-none transition-colors ${
|
||||||
|
error
|
||||||
|
? 'border-red-400 focus:border-red-500'
|
||||||
|
: 'border-transparent hover:border-slate-200 focus:border-blue-500'
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<p className="text-red-500 text-sm font-semibold text-center mt-2 animate-pulse">
|
||||||
|
El nombre del criterio es obligatorio
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -22,7 +22,7 @@ export const MainLayout = () => {
|
|||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
DoC Clásico
|
DoC Básico
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
|
||||||
<NavLink
|
<NavLink
|
||||||
|
|||||||
@@ -13,24 +13,38 @@ export default function BasicMode() {
|
|||||||
|
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [result, setResult] = useState(null);
|
const [result, setResult] = useState(null);
|
||||||
|
|
||||||
|
const [errors, setErrors] = useState({ criterion: false, levels: [] });
|
||||||
|
|
||||||
const handleCalculate = async () => {
|
const handleCalculate = async () => {
|
||||||
|
|
||||||
|
let hasError = false;
|
||||||
|
const newErrors = { criterion: false, levels: Array(levels.length).fill(false) };
|
||||||
|
|
||||||
|
if (!criterionName.trim()) {
|
||||||
|
newErrors.criterion = true;
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
levels.forEach((level, idx) => {
|
||||||
|
if (!level.trim()) {
|
||||||
|
newErrors.levels[idx] = true;
|
||||||
|
hasError = true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setErrors(newErrors);
|
||||||
|
|
||||||
|
if (hasError) return;
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
setResult(null);
|
setResult(null);
|
||||||
|
|
||||||
const firstIndex = "0";
|
|
||||||
const lastIndex = (levels.length - 1).toString();
|
|
||||||
|
|
||||||
const currentReferences = {
|
|
||||||
[firstIndex]: 0,
|
|
||||||
[lastIndex]: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const payload = {
|
const payload = {
|
||||||
criterion_name: criterionName || "Criterio sin nombre",
|
criterion_name: criterionName.trim(),
|
||||||
levels: levels,
|
levels: levels.map(l => l.trim()),
|
||||||
blank_cards: blankCards,
|
blank_cards: blankCards,
|
||||||
references: currentReferences
|
references: { "0": 0, [(levels.length - 1).toString()]: 1 }
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -43,15 +57,27 @@ export default function BasicMode() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleCriterionChange = (val) => {
|
||||||
|
setCriterionName(val);
|
||||||
|
if (errors.criterion) setErrors({ ...errors, criterion: false });
|
||||||
|
};
|
||||||
|
|
||||||
const handleLevelChange = (index, newValue) => {
|
const handleLevelChange = (index, newValue) => {
|
||||||
const newLevels = [...levels];
|
const newLevels = [...levels];
|
||||||
newLevels[index] = newValue;
|
newLevels[index] = newValue;
|
||||||
setLevels(newLevels);
|
setLevels(newLevels);
|
||||||
|
|
||||||
|
if (errors.levels[index]) {
|
||||||
|
const newErrLevels = [...errors.levels];
|
||||||
|
newErrLevels[index] = false;
|
||||||
|
setErrors({ ...errors, levels: newErrLevels });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleAddLevel = () => {
|
const handleAddLevel = () => {
|
||||||
setLevels([...levels, '']);
|
setLevels([...levels, '']);
|
||||||
setBlankCards([...blankCards, 0]);
|
setBlankCards([...blankCards, 0]);
|
||||||
|
setErrors({ ...errors, levels: [...errors.levels, false] });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRemoveLevel = (indexToRemove) => {
|
const handleRemoveLevel = (indexToRemove) => {
|
||||||
@@ -59,8 +85,12 @@ export default function BasicMode() {
|
|||||||
const newLevels = levels.filter((_, index) => index !== indexToRemove);
|
const newLevels = levels.filter((_, index) => index !== indexToRemove);
|
||||||
const blankIndexToRemove = indexToRemove === 0 ? 0 : indexToRemove - 1;
|
const blankIndexToRemove = indexToRemove === 0 ? 0 : indexToRemove - 1;
|
||||||
const newBlankCards = blankCards.filter((_, index) => index !== blankIndexToRemove);
|
const newBlankCards = blankCards.filter((_, index) => index !== blankIndexToRemove);
|
||||||
|
|
||||||
|
const newErrLevels = errors.levels.filter((_, index) => index !== indexToRemove);
|
||||||
|
|
||||||
setLevels(newLevels);
|
setLevels(newLevels);
|
||||||
setBlankCards(newBlankCards);
|
setBlankCards(newBlankCards);
|
||||||
|
setErrors({ ...errors, levels: newErrLevels });
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleBlankCardChange = (index, delta) => {
|
const handleBlankCardChange = (index, delta) => {
|
||||||
@@ -74,10 +104,11 @@ export default function BasicMode() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full flex flex-col items-center">
|
<div className="w-full flex flex-col items-center">
|
||||||
|
|
||||||
<CriterionInput
|
<CriterionInput
|
||||||
criterionName={criterionName}
|
criterionName={criterionName}
|
||||||
setCriterionName={setCriterionName}
|
setCriterionName={handleCriterionChange}
|
||||||
|
error={errors.criterion}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="w-full max-w-lg flex flex-col items-center">
|
<div className="w-full max-w-lg flex flex-col items-center">
|
||||||
@@ -90,6 +121,7 @@ export default function BasicMode() {
|
|||||||
handleLevelChange={handleLevelChange}
|
handleLevelChange={handleLevelChange}
|
||||||
handleRemoveLevel={handleRemoveLevel}
|
handleRemoveLevel={handleRemoveLevel}
|
||||||
totalLevels={levels.length}
|
totalLevels={levels.length}
|
||||||
|
error={errors.levels[index]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{index < levels.length - 1 && (
|
{index < levels.length - 1 && (
|
||||||
@@ -107,18 +139,18 @@ export default function BasicMode() {
|
|||||||
|
|
||||||
<div className="w-full max-w-lg mt-12 pt-8 border-t-2 border-slate-200 flex flex-col items-center">
|
<div className="w-full max-w-lg mt-12 pt-8 border-t-2 border-slate-200 flex flex-col items-center">
|
||||||
<button
|
<button
|
||||||
onClick={handleCalculate}
|
onClick={handleCalculate}
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
className={`w-full py-4 text-white text-xl font-bold rounded-xl shadow-lg transition-all active:scale-[0.98] ${
|
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 hover:shadow-xl'
|
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 hover:shadow-xl'
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{isLoading ? 'Calculando...' : 'Calcular Valores DoC'}
|
{isLoading ? 'Calculando...' : 'Calcular Valores DoC'}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ValueFunctionChart result={result} />
|
<ValueFunctionChart result={result} />
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user