Inventory Management
Track stock, sales, and alerts.
Inventory Movement Report
Global audit log of all stock adjustments across all branches.
import React, { useState, useEffect } from 'react'; import DataTable from '../../../Components/DataTable'; import Toast from '../Components/Toast'; import { Package, Plus, AlertTriangle, Search, Filter, Building, Eye, History, ShoppingCart, ArrowUpRight, X, TrendingUp, MoreVertical, FileText, Settings, RefreshCw, BarChart3 } from 'lucide-react'; import AddProductModal from './AddProductModal'; import AdjustStockModal from './AdjustStockModal'; import ProductDetailsModal from './ProductDetailsModal'; import NewSaleModal from './NewSaleModal'; export default function InventoryIndex() { const [activeTab, setActiveTab] = useState('Stock Control'); const [products, setProducts] = useState([]); const [movements, setMovements] = useState([]); const [branches, setBranches] = useState([]); const [categories, setCategories] = useState([]); const [loading, setLoading] = useState(true); const [movementsLoading, setMovementsLoading] = useState(false); const [toast, setToast] = useState(null); // Modals const [isAddModalOpen, setIsAddModalOpen] = useState(false); const [isAdjustModalOpen, setIsAdjustModalOpen] = useState(false); const [isDetailsModalOpen, setIsDetailsModalOpen] = useState(false); const [isSaleModalOpen, setIsSaleModalOpen] = useState(false); const [selectedProduct, setSelectedProduct] = useState(null); // Filters const [filterBranch, setFilterBranch] = useState(window.__APP_DATA__?.role === 'receptionist' ? window.__APP_DATA__?.user?.branch_id : ''); const [filterStatus, setFilterStatus] = useState(''); const [startDate, setStartDate] = useState(''); const [endDate, setEndDate] = useState(''); const [searchTerm, setSearchTerm] = useState(''); const isReceptionist = window.__APP_DATA__?.role === 'receptionist'; const fetchMasters = async () => { try { // Fetch branches const bRes = await fetch('/api/branches'); if (bRes.ok) { setBranches(await bRes.json()); } else { console.error('Failed to fetch branches'); } // Fetch categories from the Master API to ensure 1:1 match const cRes = await fetch('/api/masters/product'); if (cRes.ok) { const cats = await cRes.json(); // Filter for Active only if needed, or show all setCategories(cats.filter(c => c.status === 'Active')); } else { console.error('Failed to fetch product categories from master API'); } } catch (error) { console.error('Master data fetch exception:', error); } }; const fetchProducts = async () => { setLoading(true); try { const url = `/api/inventory/products?branch_id=${filterBranch}&status=${filterStatus}`; const res = await fetch(url); setProducts(await res.json()); } finally { setLoading(false); } }; // Fetch Sales moved to Global Reports const fetchMovements = async () => { setMovementsLoading(true); try { let url = `/api/inventory/movements?branch_id=${filterBranch}`; if (startDate) url += `&start_date=${startDate}`; if (endDate) url += `&end_date=${endDate}`; const res = await fetch(url); setMovements(await res.json()); } catch (error) { console.error('Error fetching movements:', error); } finally { setMovementsLoading(false); } }; useEffect(() => { const init = async () => { setLoading(true); await Promise.all([fetchProducts(), fetchMasters()]); setLoading(false); }; init(); }, []); useEffect(() => { fetchProducts(); }, [filterBranch, filterStatus, startDate, endDate]); useEffect(() => { if (activeTab === 'Reports & Alerts') { fetchMovements(); } }, [activeTab, filterBranch, startDate, endDate]); const lowStockCount = products.filter(p => parseFloat(p.current_stock) <= parseFloat(p.reorder_level)).length; const stockColumns = [ { header: 'Branch', render: (row) => {row.branch?.name} }, { header: 'Product Info', render: (row) => (
Track stock, sales, and alerts.
Global audit log of all stock adjustments across all branches.