Spaces:
Sleeping
Sleeping
| import React, { useState, useEffect } from 'react'; | |
| import { db } from '../../firebase/config'; | |
| import { ref, onValue, push, set, remove, update } from 'firebase/database'; | |
| import { Plus, Trash2, Edit3, AlertTriangle, X, Save } from 'lucide-react'; | |
| export default function InventoryControl() { | |
| const [items, setItems] = useState([]); | |
| const [formData, setFormData] = useState({ name: '', quantity: '', unit: '', minStock: '' }); | |
| const [editingItem, setEditingItem] = useState(null); | |
| const [isModalOpen, setIsModalOpen] = useState(false); | |
| useEffect(() => { | |
| const inventoryRef = ref(db, 'inventory'); | |
| onValue(inventoryRef, (snapshot) => { | |
| const data = snapshot.val(); | |
| if (data) { | |
| const productList = Object.keys(data).map(key => ({ id: key, ...data[key] })); | |
| setItems(productList); | |
| } else { | |
| setItems([]); | |
| } | |
| }); | |
| }, []); | |
| const handleAddItem = async (e) => { | |
| e.preventDefault(); | |
| if (!formData.name || !formData.quantity) return; | |
| const inventoryRef = ref(db, 'inventory'); | |
| const newItemRef = push(inventoryRef); | |
| await set(newItemRef, { | |
| name: formData.name, | |
| quantity: Number(formData.quantity), | |
| unit: formData.unit || 'Und', | |
| minStock: Number(formData.minStock) || 10 | |
| }); | |
| setFormData({ name: '', quantity: '', unit: '', minStock: '' }); | |
| }; | |
| const handleDelete = async (id) => { | |
| if(window.confirm('¿Seguro que deseas eliminar este insumo?')){ | |
| await remove(ref(db, `inventory/${id}`)); | |
| } | |
| }; | |
| const openEdit = (item) => { | |
| setEditingItem({ ...item }); | |
| setIsModalOpen(true); | |
| }; | |
| const handleSaveEdit = async () => { | |
| if (!editingItem.name) return; | |
| const { id, ...updates } = editingItem; | |
| await update(ref(db, `inventory/${id}`), { | |
| ...updates, | |
| quantity: Number(updates.quantity), | |
| minStock: Number(updates.minStock) | |
| }); | |
| setEditingItem(null); | |
| setIsModalOpen(false); | |
| }; | |
| const handleAddStock = async (id, currentQty) => { | |
| await set(ref(db, `inventory/${id}/quantity`), currentQty + 10); | |
| } | |
| return ( | |
| <div className="animate-fade-in" style={{ padding: '0 1rem' }}> | |
| <h2 className="text-gradient" style={{ fontSize: '2rem', marginBottom: '1.5rem', fontWeight: '800' }}>Control de Stock</h2> | |
| <div className="glass-card" style={{ marginBottom: '2rem', padding: '1.5rem' }}> | |
| <h3 style={{ marginBottom: '1.25rem', fontSize: '1.1rem' }}>Registrar Nuevo Insumo</h3> | |
| <form onSubmit={handleAddItem} style={{ display: 'flex', gap: '1rem', alignItems: 'flex-end', flexWrap: 'wrap' }}> | |
| <div style={{ flex: 2, minWidth: '150px' }}> | |
| <label style={labelStyle}>Nombre del Insumo</label> | |
| <input type="text" value={formData.name} onChange={e => setFormData({...formData, name: e.target.value})} required style={inputStyle} placeholder="Tomate rojo" /> | |
| </div> | |
| <div style={{ flex: 1, minWidth: '80px' }}> | |
| <label style={labelStyle}>Cantidad</label> | |
| <input type="number" value={formData.quantity} onChange={e => setFormData({...formData, quantity: e.target.value})} required style={inputStyle} placeholder="50" /> | |
| </div> | |
| <div style={{ flex: 1, minWidth: '80px' }}> | |
| <label style={labelStyle}>Mínimo</label> | |
| <input type="number" value={formData.minStock} onChange={e => setFormData({...formData, minStock: e.target.value})} style={inputStyle} placeholder="10" /> | |
| </div> | |
| <div style={{ flex: 1, minWidth: '80px' }}> | |
| <label style={labelStyle}>Unidad</label> | |
| <input type="text" value={formData.unit} onChange={e => setFormData({...formData, unit: e.target.value})} style={inputStyle} placeholder="Kg, Lt, Und" /> | |
| </div> | |
| <button type="submit" className="btn-primary" style={{ padding: '0.8rem 1.5rem' }}> | |
| <Plus size={20} /> | |
| </button> | |
| </form> | |
| </div> | |
| <div className="glass-panel" style={{ overflow: 'hidden' }}> | |
| <table style={{ width: '100%', borderCollapse: 'collapse', textAlign: 'left' }}> | |
| <thead> | |
| <tr style={{ borderBottom: '1px solid var(--border-subtle)', background: 'rgba(255,255,255,0.02)' }}> | |
| <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Insumo</th> | |
| <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Stock Actual</th> | |
| <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500' }}>Estado</th> | |
| <th style={{ padding: '1.25rem', color: 'var(--text-muted)', fontWeight: '500', textAlign: 'right' }}>Acciones</th> | |
| </tr> | |
| </thead> | |
| <tbody> | |
| {items.length === 0 ? ( | |
| <tr><td colSpan="4" style={{ padding: '2rem', textAlign: 'center', color: 'var(--text-muted)' }}>Sin insumos registrados</td></tr> | |
| ) : ( | |
| items.map((item) => { | |
| const isLow = item.quantity <= (item.minStock || 10); | |
| return ( | |
| <tr key={item.id} style={{ borderBottom: '1px solid var(--border-subtle)', transition: 'background 0.2s' }} className="table-row-hover"> | |
| <td style={{ padding: '1.25rem', fontWeight: '600' }}>{item.name}</td> | |
| <td style={{ padding: '1.25rem', fontWeight: '700', color: isLow ? 'var(--primary)' : 'var(--text-main)', fontSize: '1.1rem' }}> | |
| {item.quantity} <span style={{ fontSize: '0.8rem', fontWeight: '400', color: 'var(--text-muted)' }}>{item.unit}</span> | |
| </td> | |
| <td style={{ padding: '1.25rem' }}> | |
| {isLow ? ( | |
| <span style={{ display: 'inline-flex', alignItems: 'center', gap: '0.4rem', color: 'var(--primary)', fontSize: '0.8rem', background: 'rgba(255,107,107,0.1)', padding: '0.25rem 0.75rem', borderRadius: '12px', fontWeight: '600' }}> | |
| <AlertTriangle size={14} /> RELLENAR | |
| </span> | |
| ) : ( | |
| <span style={{ color: 'var(--success)', fontSize: '0.8rem', fontWeight: '600', background: 'rgba(76,217,100,0.1)', padding: '0.25rem 0.75rem', borderRadius: '12px' }}>ÓPTIMO</span> | |
| )} | |
| </td> | |
| <td style={{ padding: '1.25rem', textAlign: 'right' }}> | |
| <div style={{ display: 'flex', gap: '0.5rem', justifyContent: 'flex-end' }}> | |
| <button onClick={() => handleAddStock(item.id, item.quantity)} title="Sumar 10" style={actionBtnStyle}>+10</button> | |
| <button onClick={() => openEdit(item)} title="Editar" style={actionBtnStyle}><Edit3 size={16} /></button> | |
| <button onClick={() => handleDelete(item.id)} title="Eliminar" style={{...actionBtnStyle, color: 'var(--primary)'}}><Trash2 size={16} /></button> | |
| </div> | |
| </td> | |
| </tr> | |
| ) | |
| }) | |
| )} | |
| </tbody> | |
| </table> | |
| </div> | |
| {/* Modal de Edición */} | |
| {isModalOpen && ( | |
| <div style={{ position: 'fixed', top: 0, left: 0, width: '100vw', height: '100vh', background: 'rgba(0,0,0,0.85)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, padding: '20px' }}> | |
| <div className="glass-panel animate-slide-up" style={{ maxWidth: '450px', width: '100%', padding: '2.5rem' }}> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '2rem' }}> | |
| <h2 className="text-gradient" style={{ fontSize: '1.5rem' }}>Editar Insumo</h2> | |
| <button onClick={() => setIsModalOpen(false)} style={{ background: 'none', border: 'none', color: '#fff', cursor: 'pointer' }}><X size={24} /></button> | |
| </div> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem' }}> | |
| <div> | |
| <label style={labelStyle}>Nombre del Insumo</label> | |
| <input type="text" value={editingItem.name} onChange={e => setEditingItem({...editingItem, name: e.target.value})} style={inputStyle} /> | |
| </div> | |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}> | |
| <div> | |
| <label style={labelStyle}>Stock</label> | |
| <input type="number" value={editingItem.quantity} onChange={e => setEditingItem({...editingItem, quantity: e.target.value})} style={inputStyle} /> | |
| </div> | |
| <div> | |
| <label style={labelStyle}>Min. Stock</label> | |
| <input type="number" value={editingItem.minStock || 10} onChange={e => setEditingItem({...editingItem, minStock: e.target.value})} style={inputStyle} /> | |
| </div> | |
| </div> | |
| <div> | |
| <label style={labelStyle}>Unidad</label> | |
| <input type="text" value={editingItem.unit} onChange={e => setEditingItem({...editingItem, unit: e.target.value})} style={inputStyle} /> | |
| </div> | |
| <button onClick={handleSaveEdit} className="btn-primary" style={{ height: '50px', marginTop: '1rem', display: 'flex', gap: '0.75rem', alignItems: 'center', justifyContent: 'center' }}> | |
| <Save size={18} /> Guardar Cambios | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| )} | |
| </div> | |
| ); | |
| } | |
| const inputStyle = { | |
| width: '100%', padding: '0.8rem', borderRadius: '8px', | |
| background: 'rgba(255,255,255,0.05)', border: '1px solid var(--border-subtle)', | |
| color: '#fff', outline: 'none' | |
| }; | |
| const labelStyle = { | |
| display: 'block', marginBottom: '0.5rem', fontSize: '0.85rem', color: 'var(--text-muted)' | |
| }; | |
| const actionBtnStyle = { | |
| width: '32px', height: '32px', borderRadius: '8px', border: '1px solid var(--border-subtle)', | |
| background: 'rgba(255,255,255,0.05)', color: 'var(--text-main)', cursor: 'pointer', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center', transition: 'all 0.2s', fontSize: '0.75rem', fontWeight: '700' | |
| }; | |