"use client"; import { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Layout from "@/components/layout/layout"; import { Button } from "@/components/ui/button"; import { Table, TableHeader, TableRow, TableHead, TableBody, TableCell } from "@/components/ui/table"; import { Input } from "@/components/ui/input"; import { Switch } from "@/components/ui/switch"; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; import { AlertDialog, AlertDialogAction, AlertDialogCancel, AlertDialogContent, AlertDialogDescription, AlertDialogFooter, AlertDialogHeader, AlertDialogTitle, AlertDialogTrigger, } from "@/components/ui/alert-dialog"; import { Product } from "@/models/products"; import { Package, RefreshCw, ChevronDown, CheckSquare, XSquare } from "lucide-react"; import { fetchProductData, updateProductStock, saveProductData } from "@/lib/api"; import { toast } from "sonner"; export default function StockManagementPage() { const router = useRouter(); const [products, setProducts] = useState([]); const [loading, setLoading] = useState(true); const [editingStock, setEditingStock] = useState>({}); const [stockValues, setStockValues] = useState>({}); const [searchTerm, setSearchTerm] = useState(""); const [selectedProducts, setSelectedProducts] = useState([]); const [isConfirmDialogOpen, setIsConfirmDialogOpen] = useState(false); const [bulkAction, setBulkAction] = useState<'enable' | 'disable' | null>(null); useEffect(() => { const authToken = document.cookie .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; if (!authToken) { router.push("/login"); return; } const fetchDataAsync = async () => { try { const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}/products`; const fetchedProducts = await fetchProductData(apiUrl, authToken); setProducts(fetchedProducts); // Initialize stock values const initialStockValues: Record = {}; fetchedProducts.forEach((product: Product) => { if (product._id) { initialStockValues[product._id] = product.currentStock || 0; } }); setStockValues(initialStockValues); setLoading(false); } catch (error) { console.error("Error fetching products:", error); setLoading(false); } }; fetchDataAsync(); }, [router]); const handleEditStock = (productId: string) => { setEditingStock({ ...editingStock, [productId]: true, }); }; const handleSaveStock = async (product: Product) => { if (!product._id) return; const authToken = document.cookie .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; if (!authToken) { router.push("/login"); return; } try { const newStockValue = stockValues[product._id] || 0; await updateProductStock( product._id, { currentStock: newStockValue, stockTracking: product.stockTracking }, authToken ); // Update local products state setProducts(products.map(p => { if (p._id === product._id) { return { ...p, currentStock: newStockValue }; } return p; })); setEditingStock({ ...editingStock, [product._id]: false, }); toast.success("Stock updated successfully"); } catch (error) { console.error("Error updating stock:", error); toast.error("Failed to update stock"); } }; const handleStockChange = (productId: string, value: number) => { setStockValues({ ...stockValues, [productId]: value, }); }; const handleToggleStockTracking = async (product: Product) => { if (!product._id) return; const authToken = document.cookie .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; if (!authToken) { router.push("/login"); return; } try { const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}/products/${product._id}`; // Toggle the stock tracking status const newTrackingStatus = !product.stockTracking; // For enabling tracking, we need to ensure there's a stock value const stockData = { stockTracking: newTrackingStatus, currentStock: product.currentStock || 0, lowStockThreshold: product.lowStockThreshold || 10, }; // Update product with new tracking status await saveProductData( apiUrl, stockData, authToken, "PUT" ); // Also update stock API to ensure consistency await updateProductStock( product._id, stockData, authToken ); // Update local state setProducts(products.map(p => { if (p._id === product._id) { return { ...p, stockTracking: newTrackingStatus, currentStock: stockData.currentStock, lowStockThreshold: stockData.lowStockThreshold, }; } return p; })); toast.success(`Stock tracking ${newTrackingStatus ? 'enabled' : 'disabled'} for ${product.name}`); } catch (error) { console.error("Error toggling stock tracking:", error); toast.error(`Failed to ${product.stockTracking ? 'disable' : 'enable'} stock tracking`); } }; const handleBulkAction = async (action: 'enable' | 'disable') => { if (selectedProducts.length === 0) { toast.error("No products selected"); return; } setBulkAction(action); setIsConfirmDialogOpen(true); }; const executeBulkAction = async () => { const authToken = document.cookie .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; if (!authToken || !bulkAction) { setIsConfirmDialogOpen(false); return; } setLoading(true); try { const isEnabling = bulkAction === 'enable'; const updatePromises = selectedProducts.map(async (productId) => { const product = products.find(p => p._id === productId); if (!product) return null; const apiUrl = `${process.env.NEXT_PUBLIC_API_URL}/products/${productId}`; const stockData = { stockTracking: isEnabling, currentStock: product.currentStock || 0, lowStockThreshold: product.lowStockThreshold || 10, }; // Update product with new tracking status await saveProductData( apiUrl, stockData, authToken, "PUT" ); // Also update stock API to ensure consistency await updateProductStock( productId, stockData, authToken ); return productId; }); await Promise.all(updatePromises); // Update local state setProducts(products.map(p => { if (selectedProducts.includes(p._id as string)) { return { ...p, stockTracking: isEnabling, }; } return p; })); setSelectedProducts([]); toast.success(`Stock tracking ${isEnabling ? 'enabled' : 'disabled'} for ${selectedProducts.length} products`); } catch (error) { console.error(`Error ${bulkAction}ing stock tracking:`, error); toast.error(`Failed to ${bulkAction} stock tracking`); } finally { setLoading(false); setIsConfirmDialogOpen(false); } }; const toggleSelectProduct = (productId: string) => { setSelectedProducts(prev => prev.includes(productId) ? prev.filter(id => id !== productId) : [...prev, productId] ); }; const toggleSelectAll = () => { if (selectedProducts.length === filteredProducts.length) { setSelectedProducts([]); } else { setSelectedProducts(filteredProducts.map(p => p._id as string)); } }; const getStockStatus = (product: Product) => { if (!product.stockTracking) return "Not Tracked"; if (!product.currentStock || product.currentStock <= 0) { return "Out of Stock"; } else if (product.lowStockThreshold && product.currentStock <= product.lowStockThreshold) { return "Low Stock"; } else { return "In Stock"; } }; const filteredProducts = products.filter(product => { const matchesSearch = !searchTerm || product.name.toLowerCase().includes(searchTerm.toLowerCase()); return matchesSearch; }); const statsData = { total: products.length, inStock: products.filter(p => p.stockTracking && p.currentStock && p.currentStock > 0 && (!p.lowStockThreshold || p.currentStock > p.lowStockThreshold)).length, lowStock: products.filter(p => p.stockTracking && p.lowStockThreshold && p.currentStock && p.currentStock <= p.lowStockThreshold && p.currentStock > 0).length, outOfStock: products.filter(p => p.stockTracking && (!p.currentStock || p.currentStock <= 0)).length, notTracked: products.filter(p => p.stockTracking === false).length }; return (

Stock Management

{statsData.total}
Total Products
{statsData.inStock}
In Stock
{statsData.lowStock}
Low Stock
{statsData.outOfStock}
Out of Stock
{statsData.notTracked}
Not Tracked

Inventory List

{selectedProducts.length > 0 && ( handleBulkAction('enable')}> Enable Tracking handleBulkAction('disable')}> Disable Tracking )}
setSearchTerm(e.target.value)} className="w-64" />
0 && selectedProducts.length === filteredProducts.length} onChange={toggleSelectAll} /> Product Unit Status Current Stock Low Stock Threshold Track Stock Actions {loading ? ( Loading... ) : filteredProducts.length > 0 ? ( filteredProducts.map((product) => ( toggleSelectProduct(product._id as string)} /> {product.name} {product.unitType} {getStockStatus(product)} {editingStock[product._id as string] ? ( handleStockChange(product._id as string, parseFloat(e.target.value))} className="max-w-[100px]" /> ) : ( product.stockTracking ? `${product.currentStock || 0} ${product.unitType}` : "N/A" )} {product.stockTracking ? `${product.lowStockThreshold || 0} ${product.unitType}` : "N/A"} handleToggleStockTracking(product)} /> {product.stockTracking && ( editingStock[product._id as string] ? ( ) : ( ) )} )) ) : ( No products found. )}
{bulkAction === 'enable' ? 'Enable' : 'Disable'} Stock Tracking Are you sure you want to {bulkAction === 'enable' ? 'enable' : 'disable'} stock tracking for {selectedProducts.length} selected products? {bulkAction === 'disable' && ' This will remove any stock tracking for these products.'} {bulkAction === 'enable' && ' This will enable stock tracking with initial values.'} Cancel Confirm
); }