import React, { useState, useEffect } from 'react'; import { Package, Plus, Search, ArrowDown, Edit, Trash2, History, Printer, ChevronDown, ChevronRight, AlertCircle } from 'lucide-react'; import { stockService, type StockItem, type StockMovement } from '../services/stockService'; import { StockModal } from '../components/modals/StockModal'; import { StockOutModal } from '../components/modals/StockOutModal'; import { StockHistoryModal } from '../components/modals/StockHistoryModal'; import { StockInventoryReport } from '../components/reports/StockInventoryReport'; import { DiluentListModal } from '../components/modals/DiluentListModal'; import { useAuth } from '../context/useAuth'; import { useOrganization } from '@clerk/clerk-react'; import { useSystemSettings } from '../context/SystemSettingsContext'; export const StockDashboard: React.FC = () => { // ... rest of component const { isAdmin } = useAuth(); const { organization } = useOrganization(); const { settings } = useSystemSettings(); const [items, setItems] = useState([]); const [loading, setLoading] = useState(true); const [searchTerm, setSearchTerm] = useState(''); const [showAddModal, setShowAddModal] = useState(false); const [showOutModal, setShowOutModal] = useState(false); const [showHistoryModal, setShowHistoryModal] = useState(false); const [selectedItem, setSelectedItem] = useState(null); const [isPrintingInventory, setIsPrintingInventory] = useState(false); const [allMovements, setAllMovements] = useState>(new Map()); const [activeTab, setActiveTab] = useState<'PAINT' | 'THINNER'>('PAINT'); const [showDiluentModal, setShowDiluentModal] = useState(false); const logoUrl = settings?.appLogoUrl || organization?.imageUrl; const fetchItems = async () => { setLoading(true); try { const data = await stockService.getAll(); setItems(data); } catch (error) { console.error('Error fetching stock:', error); } finally { setLoading(false); } }; useEffect(() => { fetchItems(); }, []); const handleEdit = (item: StockItem) => { setSelectedItem(item); setShowAddModal(true); }; const handleOut = (item: StockItem) => { setSelectedItem(item); setShowOutModal(true); }; const handleHistory = (item: StockItem) => { setSelectedItem(item); setShowHistoryModal(true); }; const handleDelete = async (id: string) => { if (confirm('Tem certeza que deseja excluir este lote? Todo o histórico será perdido.')) { try { await stockService.delete(id); fetchItems(); } catch (error) { console.error('Error deleting item:', error); alert('Erro ao excluir item.'); } } }; const handlePrintInventory = async () => { try { setIsPrintingInventory(true); // Buscar movimentações para todos os itens const movementsMap = new Map(); await Promise.all( items.map(async (item) => { try { const movements = await stockService.getMovements(item._id!); movementsMap.set(item._id!, movements); } catch (error) { console.error(`Error fetching movements for ${item._id}:`, error); movementsMap.set(item._id!, []); } }) ); setAllMovements(movementsMap); // Aguardar renderização e imprimir setTimeout(() => { window.print(); setIsPrintingInventory(false); }, 500); } catch (error) { console.error('Error generating inventory report:', error); alert('Erro ao gerar relatório de inventário.'); setIsPrintingInventory(false); } }; const filteredItems = items.filter(item => { const searchLower = searchTerm.toLowerCase(); // Handle type checking carefully. If type is missing, assume PAINT. const type = (typeof item.dataSheetId === 'object' ? item.dataSheetId.type : '') || 'PAINT'; const isThinner = type === 'THINNER' || type === 'DILUENTE'; // Tab Filter if (activeTab === 'THINNER' && !isThinner) return false; if (activeTab === 'PAINT' && isThinner) return false; const productName = typeof item.dataSheetId === 'object' ? item.dataSheetId.name : ''; const manufacturer = typeof item.dataSheetId === 'object' ? item.dataSheetId.manufacturer : ''; return ( item.rrNumber.toLowerCase().includes(searchLower) || item.batchNumber.toLowerCase().includes(searchLower) || productName.toLowerCase().includes(searchLower) || manufacturer.toLowerCase().includes(searchLower) ); }); const groupedItems = React.useMemo(() => { const groups = new Map(); filteredItems.forEach(item => { const productName = typeof item.dataSheetId === 'object' ? item.dataSheetId.name : 'Unknown'; const manufacturer = typeof item.dataSheetId === 'object' ? item.dataSheetId.manufacturer : ''; const key = `${item.dataSheetId._id || item.dataSheetId}-${item.color}`; if (!groups.has(key)) { groups.set(key, { items: [], totalQty: 0, minStock: item.minStock || 0, unit: item.unit, productName, color: item.color || '-', manufacturer }); } const group = groups.get(key)!; group.items.push(item); group.totalQty += item.quantity; // Ensure we take the max minStock found if they differ (though backend enforces consistency) if (item.minStock && item.minStock > group.minStock) { group.minStock = item.minStock; } }); return Array.from(groups.values()); }, [filteredItems]); const [expandedGroups, setExpandedGroups] = useState>(new Set()); const toggleGroup = (key: string) => { const newExpanded = new Set(expandedGroups); if (newExpanded.has(key)) { newExpanded.delete(key); } else { newExpanded.add(key); } setExpandedGroups(newExpanded); }; return (

Gestão de Estoque

Controle de Tintas e Diluentes

{isAdmin() && activeTab === 'THINNER' && ( )} {isAdmin() && ( )}
{/* Tabs */}
{/* Filters */}
setSearchTerm(e.target.value)} className="w-full pl-12 pr-4 py-3 bg-surface border border-border/40 rounded-xl text-text-main placeholder:text-text-muted focus:outline-none focus:ring-2 focus:ring-primary/20 focus:border-primary transition-all" />
{/* Table */}
{loading ? (
Carregando...
) : filteredItems.length === 0 ? (
Nenhum item encontrado.
) : (
{activeTab === 'PAINT' && ( )} {groupedItems.map((group, index) => { const groupKey = `${group.productName}-${group.color}-${index}`; const isExpanded = expandedGroups.has(groupKey); const isLowStock = group.minStock > 0 && group.totalQty < group.minStock; return ( {/* Group Row */} toggleGroup(groupKey)} > {activeTab === 'PAINT' && ( )} {/* Expanded Item Rows */} {isExpanded && group.items.map(item => { const isExpired = item.expirationDate && new Date(item.expirationDate) < new Date(); // Check individual item min stock for legacy reasons? No, rely on group. return ( {/* Indentation */} {activeTab === 'PAINT' && ( )} ); })} ); })}
Produto / GrupoCorQuantidade Total Lotes Status
{isExpanded ? : }
{group.productName} {group.manufacturer}
{group.color} {isLowStock && } {group.totalQty.toFixed(1)} {group.unit} {group.minStock > 0 && ( Mín: {group.minStock} {group.unit} )} {group.items.length} lote(s) {/* Actions if needed for group? */}
RR: {item.rrNumber}
{activeTab === 'THINNER' && (
Lote: {item.batchNumber}
)}
Lote: {item.batchNumber} {item.quantity} {item.unit} {activeTab === 'PAINT' ? ( item.expirationDate ? ( Val: {new Date(item.expirationDate).toLocaleDateString()} ) : '-' ) : ( - )} {isAdmin() && ( <> )}
)}
{showAddModal && ( { setShowAddModal(false); setSelectedItem(null); }} onSuccess={() => { fetchItems(); setShowAddModal(false); setSelectedItem(null); }} initialData={selectedItem || undefined} initialType={activeTab} /> )} {showOutModal && selectedItem && ( { setShowOutModal(false); setSelectedItem(null); }} onSuccess={() => { fetchItems(); setShowOutModal(false); setSelectedItem(null); }} item={selectedItem} /> )} {showHistoryModal && selectedItem && ( { setShowHistoryModal(false); setSelectedItem(null); }} item={selectedItem} onUpdate={fetchItems} /> )} {isPrintingInventory && ( )} {showDiluentModal && ( setShowDiluentModal(false)} /> )}
); };