Improve the frontend 2
This commit is contained in:
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user