Improve the frontend 2

This commit is contained in:
Urtzi Alfaro
2025-10-29 06:58:05 +01:00
parent 858d985c92
commit 36217a2729
98 changed files with 6652 additions and 4230 deletions

View File

@@ -1,10 +1,7 @@
import React, { useState } from 'react';
import { Trash2, AlertTriangle, Info, X } from 'lucide-react';
import { Modal, Button } from '../../ui';
import React from 'react';
import { BaseDeleteModal } from '../../ui';
import { IngredientResponse, DeletionSummary } from '../../../api/types/inventory';
type DeleteMode = 'soft' | 'hard';
interface DeleteIngredientModalProps {
isOpen: boolean;
onClose: () => void;
@@ -25,307 +22,62 @@ export const DeleteIngredientModal: React.FC<DeleteIngredientModalProps> = ({
onHardDelete,
isLoading = false,
}) => {
const [selectedMode, setSelectedMode] = useState<DeleteMode>('soft');
const [showConfirmation, setShowConfirmation] = useState(false);
const [confirmText, setConfirmText] = useState('');
const [deletionResult, setDeletionResult] = useState<DeletionSummary | null>(null);
const handleDeleteModeSelect = (mode: DeleteMode) => {
setSelectedMode(mode);
setShowConfirmation(true);
setConfirmText('');
};
const handleConfirmDelete = async () => {
try {
if (selectedMode === 'hard') {
const result = await onHardDelete(ingredient.id);
setDeletionResult(result);
// Close modal immediately after successful hard delete
onClose();
} else {
await onSoftDelete(ingredient.id);
// Close modal immediately after successful soft delete
onClose();
}
} catch (error) {
console.error('Error deleting ingredient:', error);
// Handle error (could show a toast or error message)
}
};
const handleClose = () => {
setShowConfirmation(false);
setSelectedMode('soft');
setConfirmText('');
setDeletionResult(null);
onClose();
};
const isConfirmDisabled =
selectedMode === 'hard' && confirmText.toUpperCase() !== 'ELIMINAR';
// Show deletion result for hard delete
if (deletionResult) {
return (
<Modal isOpen={isOpen} onClose={handleClose} size="md">
<div className="p-6">
<div className="flex items-center gap-3 mb-6">
<div className="flex-shrink-0">
<div className="w-10 h-10 rounded-full bg-green-100 dark:bg-green-900/20 flex items-center justify-center">
<svg className="w-6 h-6 text-green-600 dark:text-green-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
</svg>
</div>
</div>
<div>
<h3 className="text-lg font-semibold text-[var(--text-primary)]">
Eliminación Completada
</h3>
<p className="text-sm text-[var(--text-secondary)]">
El artículo {deletionResult.ingredient_name} ha sido eliminado permanentemente
</p>
</div>
</div>
<div className="bg-[var(--background-secondary)] rounded-lg p-4 mb-6">
<h4 className="font-medium text-[var(--text-primary)] mb-3">Resumen de eliminación:</h4>
<div className="space-y-2 text-sm text-[var(--text-secondary)]">
<div className="flex justify-between">
<span>Lotes de stock eliminados:</span>
<span className="font-medium">{deletionResult.deleted_stock_entries}</span>
</div>
<div className="flex justify-between">
<span>Movimientos eliminados:</span>
<span className="font-medium">{deletionResult.deleted_stock_movements}</span>
</div>
<div className="flex justify-between">
<span>Alertas eliminadas:</span>
<span className="font-medium">{deletionResult.deleted_stock_alerts}</span>
</div>
</div>
</div>
<div className="flex justify-end">
<Button variant="primary" onClick={handleClose}>
Entendido
</Button>
</div>
</div>
</Modal>
);
}
// Show confirmation step
if (showConfirmation) {
const isHardDelete = selectedMode === 'hard';
return (
<Modal isOpen={isOpen} onClose={handleClose} size="md">
<div className="p-6">
<div className="flex items-start gap-4 mb-6">
<div className="flex-shrink-0">
{isHardDelete ? (
<AlertTriangle className="w-8 h-8 text-red-500" />
) : (
<Info className="w-8 h-8 text-orange-500" />
)}
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold text-[var(--text-primary)] mb-2">
{isHardDelete ? 'Confirmación de Eliminación Permanente' : 'Confirmación de Desactivación'}
</h3>
<div className="mb-4">
<div className="bg-[var(--background-secondary)] p-3 rounded-lg mb-4">
<p className="font-medium text-[var(--text-primary)]">{ingredient.name}</p>
<p className="text-sm text-[var(--text-secondary)]">
Categoría: {ingredient.category} Stock actual: {ingredient.current_stock || 0}
</p>
</div>
{isHardDelete ? (
<div className="text-red-600 dark:text-red-400 mb-4">
<p className="font-medium mb-2"> Esta acción eliminará permanentemente:</p>
<ul className="text-sm space-y-1 ml-4">
<li> El artículo y toda su información</li>
<li> Todos los lotes de stock asociados</li>
<li> Todo el historial de movimientos</li>
<li> Las alertas relacionadas</li>
</ul>
<p className="font-bold mt-3 text-red-700 dark:text-red-300">
Esta acción NO se puede deshacer
</p>
</div>
) : (
<div className="text-orange-600 dark:text-orange-400 mb-4">
<p className="font-medium mb-2"> Esta acción desactivará el artículo:</p>
<ul className="text-sm space-y-1 ml-4">
<li> El artículo se marcará como inactivo</li>
<li> No aparecerá en listas activas</li>
<li> Se conserva todo el historial y stock</li>
<li> Se puede reactivar posteriormente</li>
</ul>
</div>
)}
</div>
{isHardDelete && (
<div className="mb-6">
<label className="block text-sm font-medium text-[var(--text-primary)] mb-2">
Para confirmar, escriba <span className="font-mono bg-[var(--background-secondary)] px-1 rounded text-[var(--text-primary)]">ELIMINAR</span>:
</label>
<input
type="text"
value={confirmText}
onChange={(e) => setConfirmText(e.target.value)}
className="w-full px-3 py-2 border border-[var(--border-color)] bg-[var(--background-primary)] text-[var(--text-primary)] rounded-md focus:outline-none focus:ring-2 focus:ring-red-500 focus:border-red-500 placeholder:text-[var(--text-tertiary)]"
placeholder="Escriba ELIMINAR"
autoComplete="off"
/>
</div>
)}
</div>
</div>
<div className="flex gap-3 justify-end">
<Button
variant="outline"
onClick={() => setShowConfirmation(false)}
disabled={isLoading}
>
Volver
</Button>
<Button
variant={isHardDelete ? 'danger' : 'warning'}
onClick={handleConfirmDelete}
disabled={isConfirmDisabled || isLoading}
isLoading={isLoading}
>
{isHardDelete ? 'Eliminar Permanentemente' : 'Desactivar Artículo'}
</Button>
</div>
</div>
</Modal>
);
}
// Initial mode selection
return (
<Modal isOpen={isOpen} onClose={handleClose} size="lg">
<div className="p-6">
<div className="mb-6">
<h2 className="text-xl font-semibold text-[var(--text-primary)]">
Eliminar Artículo
</h2>
</div>
<div className="mb-6">
<div className="bg-[var(--background-secondary)] p-4 rounded-lg">
<p className="font-medium text-[var(--text-primary)]">{ingredient.name}</p>
<p className="text-sm text-[var(--text-secondary)]">
Categoría: {ingredient.category} Stock actual: {ingredient.current_stock || 0}
</p>
</div>
</div>
<div className="mb-6">
<p className="text-[var(--text-primary)] mb-4">
Elija el tipo de eliminación que desea realizar:
</p>
<div className="space-y-4">
{/* Soft Delete Option */}
<div
className={`border-2 rounded-lg p-4 cursor-pointer transition-colors ${
selectedMode === 'soft'
? 'border-orange-500 bg-orange-50 dark:bg-orange-900/10'
: 'border-[var(--border-color)] hover:border-orange-300'
}`}
onClick={() => setSelectedMode('soft')}
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-1">
<div className={`w-4 h-4 rounded-full border-2 ${
selectedMode === 'soft'
? 'border-orange-500 bg-orange-500'
: 'border-[var(--border-color)]'
}`}>
{selectedMode === 'soft' && (
<div className="w-2 h-2 bg-white rounded-full m-0.5" />
)}
</div>
</div>
<div>
<h3 className="font-medium text-[var(--text-primary)] mb-1">
Desactivar (Recomendado)
</h3>
<p className="text-sm text-[var(--text-secondary)]">
El artículo se marca como inactivo pero conserva todo su historial.
Ideal para artículos temporalmente fuera del catálogo.
</p>
<div className="mt-2 text-xs text-orange-600 dark:text-orange-400">
Reversible Conserva historial Conserva stock
</div>
</div>
</div>
</div>
{/* Hard Delete Option */}
<div
className={`border-2 rounded-lg p-4 cursor-pointer transition-colors ${
selectedMode === 'hard'
? 'border-red-500 bg-red-50 dark:bg-red-900/10'
: 'border-[var(--border-color)] hover:border-red-300'
}`}
onClick={() => setSelectedMode('hard')}
>
<div className="flex items-start gap-3">
<div className="flex-shrink-0 mt-1">
<div className={`w-4 h-4 rounded-full border-2 ${
selectedMode === 'hard'
? 'border-red-500 bg-red-500'
: 'border-[var(--border-color)]'
}`}>
{selectedMode === 'hard' && (
<div className="w-2 h-2 bg-white rounded-full m-0.5" />
)}
</div>
</div>
<div>
<h3 className="font-medium text-[var(--text-primary)] mb-1 flex items-center gap-2">
Eliminar Permanentemente
<AlertTriangle className="w-4 h-4 text-red-500" />
</h3>
<p className="text-sm text-[var(--text-secondary)]">
Elimina completamente el artículo y todos sus datos asociados.
Use solo para datos erróneos o pruebas.
</p>
<div className="mt-2 text-xs text-red-600 dark:text-red-400">
No reversible Elimina historial Elimina stock
</div>
</div>
</div>
</div>
</div>
</div>
<div className="flex gap-3 justify-end">
<Button variant="outline" onClick={handleClose}>
Cancelar
</Button>
<Button
variant={selectedMode === 'hard' ? 'danger' : 'warning'}
onClick={() => handleDeleteModeSelect(selectedMode)}
>
<Trash2 className="w-4 h-4 mr-2" />
Continuar
</Button>
</div>
</div>
</Modal>
<BaseDeleteModal<IngredientResponse, DeletionSummary>
isOpen={isOpen}
onClose={onClose}
entity={ingredient}
onSoftDelete={onSoftDelete}
onHardDelete={onHardDelete}
isLoading={isLoading}
title="Eliminar Artículo"
getEntityId={(ing) => ing.id}
getEntityDisplay={(ing) => ({
primaryText: ing.name,
secondaryText: `Categoría: ${ing.category} • Stock actual: ${ing.current_stock || 0}`,
})}
softDeleteOption={{
title: 'Desactivar (Recomendado)',
description: 'El artículo se marca como inactivo pero conserva todo su historial. Ideal para artículos temporalmente fuera del catálogo.',
benefits: '✓ Reversible • ✓ Conserva historial • ✓ Conserva stock',
}}
hardDeleteOption={{
title: 'Eliminar Permanentemente',
description: 'Elimina completamente el artículo y todos sus datos asociados. Use solo para datos erróneos o pruebas.',
benefits: '⚠️ No reversible • ⚠️ Elimina historial • ⚠️ Elimina stock',
enabled: true,
}}
softDeleteWarning={{
title: ' Esta acción desactivará el artículo:',
items: [
'El artículo se marcará como inactivo',
'No aparecerá en listas activas',
'Se conserva todo el historial y stock',
'Se puede reactivar posteriormente',
],
}}
hardDeleteWarning={{
title: '⚠️ Esta acción eliminará permanentemente:',
items: [
'El artículo y toda su información',
'Todos los lotes de stock asociados',
'Todo el historial de movimientos',
'Las alertas relacionadas',
],
footer: 'Esta acción NO se puede deshacer',
}}
requireConfirmText={true}
confirmText="ELIMINAR"
showSuccessScreen={false}
showDeletionSummary={true}
deletionSummaryTitle="Eliminación Completada"
formatDeletionSummary={(summary) => ({
'Lotes de stock eliminados': summary.deleted_stock_entries,
'Movimientos eliminados': summary.deleted_stock_movements,
'Alertas eliminadas': summary.deleted_stock_alerts,
})}
/>
);
};
export default DeleteIngredientModal;
export default DeleteIngredientModal;