import React, { useState } from 'react'; import { Package, AlertTriangle, Clock, Archive, Thermometer, Plus, Edit, Trash2, CheckCircle, X, Save } from 'lucide-react'; import { EditViewModal } from '../../ui/EditViewModal/EditViewModal'; import { IngredientResponse, StockResponse, StockUpdate } from '../../../api/types/inventory'; import { formatters } from '../../ui/Stats/StatsPresets'; import { statusColors } from '../../../styles/colors'; import { Button } from '../../ui/Button'; interface BatchModalProps { isOpen: boolean; onClose: () => void; ingredient: IngredientResponse; batches: StockResponse[]; loading?: boolean; onAddBatch?: () => void; onEditBatch?: (batchId: string, updateData: StockUpdate) => Promise; onMarkAsWaste?: (batchId: string) => Promise; } /** * BatchModal - Card-based batch management modal * Mobile-friendly design with edit and waste marking functionality */ export const BatchModal: React.FC = ({ isOpen, onClose, ingredient, batches = [], loading = false, onAddBatch, onEditBatch, onMarkAsWaste }) => { const [editingBatch, setEditingBatch] = useState(null); const [editData, setEditData] = useState({}); const [isSubmitting, setIsSubmitting] = useState(false); // Get batch status based on expiration and availability const getBatchStatus = (batch: StockResponse) => { if (!batch.is_available) { return { label: 'No Disponible', color: statusColors.cancelled.primary, icon: X, isCritical: true }; } if (batch.is_expired) { return { label: 'Vencido', color: statusColors.expired.primary, icon: AlertTriangle, isCritical: true }; } if (!batch.expiration_date) { return { label: 'Sin Vencimiento', color: statusColors.other.primary, icon: Archive, isCritical: false }; } const today = new Date(); const expirationDate = new Date(batch.expiration_date); const daysUntilExpiry = Math.ceil((expirationDate.getTime() - today.getTime()) / (1000 * 60 * 60 * 24)); if (daysUntilExpiry <= 0) { return { label: 'Vencido', color: statusColors.expired.primary, icon: AlertTriangle, isCritical: true }; } else if (daysUntilExpiry <= 3) { return { label: 'Vence Pronto', color: statusColors.low.primary, icon: AlertTriangle, isCritical: true }; } else if (daysUntilExpiry <= 7) { return { label: 'Por Vencer', color: statusColors.pending.primary, icon: Clock, isCritical: false }; } else { return { label: 'Fresco', color: statusColors.completed.primary, icon: CheckCircle, isCritical: false }; } }; const handleEditStart = (batch: StockResponse) => { setEditingBatch(batch.id); setEditData({ current_quantity: batch.current_quantity, expiration_date: batch.expiration_date, storage_location: batch.storage_location || '', requires_refrigeration: batch.requires_refrigeration, requires_freezing: batch.requires_freezing, storage_temperature_min: batch.storage_temperature_min, storage_temperature_max: batch.storage_temperature_max, storage_humidity_max: batch.storage_humidity_max, shelf_life_days: batch.shelf_life_days, storage_instructions: batch.storage_instructions || '' }); }; const handleEditCancel = () => { setEditingBatch(null); setEditData({}); }; const handleEditSave = async (batchId: string) => { if (!onEditBatch) return; setIsSubmitting(true); try { await onEditBatch(batchId, editData); setEditingBatch(null); setEditData({}); } catch (error) { console.error('Error updating batch:', error); } finally { setIsSubmitting(false); } }; const handleMarkAsWaste = async (batchId: string) => { if (!onMarkAsWaste) return; const confirmed = window.confirm('¿Está seguro que desea marcar este lote como desperdicio? Esta acción no se puede deshacer.'); if (!confirmed) return; setIsSubmitting(true); try { await onMarkAsWaste(batchId); } catch (error) { console.error('Error marking batch as waste:', error); } finally { setIsSubmitting(false); } }; const statusConfig = { color: statusColors.inProgress.primary, text: `${batches.length} lotes`, icon: Package }; // Create card-based batch list const batchCards = batches.length > 0 ? (
{batches.map((batch) => { const status = getBatchStatus(batch); const StatusIcon = status.icon; const isEditing = editingBatch === batch.id; return (
{/* Header */}

Lote #{batch.batch_number || 'Sin número'}

{status.label}
{!isEditing && ( <> {status.isCritical && batch.is_available && ( )} )} {isEditing && ( <> )}
{/* Content */}
{/* Basic Info */}
{isEditing ? ( setEditData(prev => ({ ...prev, current_quantity: Number(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-blue-500" /> ) : (
{batch.current_quantity} {ingredient.unit_of_measure}
)}
{formatters.currency(Number(batch.total_cost || 0))}
{/* Dates */}
{isEditing ? ( setEditData(prev => ({ ...prev, expiration_date: 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-blue-500" /> ) : (
{batch.expiration_date ? new Date(batch.expiration_date).toLocaleDateString('es-ES') : 'Sin vencimiento' }
)}
{isEditing ? ( setEditData(prev => ({ ...prev, storage_location: e.target.value }))} placeholder="Ubicación del lote" 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-blue-500" /> ) : (
{batch.storage_location || 'No especificada'}
)}
{/* Storage Requirements */}
Almacenamiento
Tipo: {batch.requires_refrigeration ? 'Refrigeración' : batch.requires_freezing ? 'Congelación' : 'Ambiente'}
{(batch.storage_temperature_min || batch.storage_temperature_max) && (
Temp: {batch.storage_temperature_min || '-'}°C a {batch.storage_temperature_max || '-'}°C
)} {batch.storage_humidity_max && (
Humedad: ≤{batch.storage_humidity_max}%
)} {batch.shelf_life_days && (
Vida útil: {batch.shelf_life_days} días
)}
{batch.storage_instructions && (
"{batch.storage_instructions}"
)}
); })}
) : (

No hay lotes registrados

Los lotes se crean automáticamente al agregar stock

{onAddBatch && ( )}
); const sections = [ { title: 'Lotes de Stock', icon: Package, fields: [ { label: '', value: batchCards, span: 2 as const } ] } ]; const actions = []; if (onAddBatch && batches.length > 0) { actions.push({ label: 'Agregar Lote', icon: Plus, variant: 'primary' as const, onClick: onAddBatch }); } return ( ); }; export default BatchModal;