"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 { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; 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, Boxes, Download, Calendar } from "lucide-react"; import { clientFetch } from "@/lib/api"; import { toast } from "sonner"; import { DatePicker, DateRangePicker, DateRangeDisplay, MonthPicker } from "@/components/ui/date-picker"; import { DateRange } from "react-day-picker"; import { addDays, startOfDay, endOfDay, format, isSameDay } from "date-fns"; interface StockData { currentStock: number; stockTracking: boolean; lowStockThreshold?: number; } type ReportType = 'daily' | 'weekly' | 'monthly' | 'custom'; 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); // Export state const [exportDate, setExportDate] = useState(new Date().toISOString().split('T')[0]); const [exportDateRange, setExportDateRange] = useState({ from: startOfDay(addDays(new Date(), -6)), to: endOfDay(new Date()) }); const [selectedMonth, setSelectedMonth] = useState(new Date()); const [reportType, setReportType] = useState('daily'); const [isExporting, setIsExporting] = useState(false); useEffect(() => { const authToken = document.cookie .split("; ") .find((row) => row.startsWith("Authorization=")) ?.split("=")[1]; if (!authToken) { router.push("/login"); return; } const fetchDataAsync = async () => { try { const response = await clientFetch('api/products'); const fetchedProducts = response || []; 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; try { const newStockValue = stockValues[product._id] || 0; const stockData: StockData = { currentStock: newStockValue, stockTracking: product.stockTracking || false, lowStockThreshold: product.lowStockThreshold }; await clientFetch(`api/stock/${product._id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(stockData) }); // 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; try { // Toggle the stock tracking status const newTrackingStatus = !product.stockTracking; // For enabling tracking, we need to ensure there's a stock value const stockData: StockData = { stockTracking: newTrackingStatus, currentStock: product.currentStock || 0, lowStockThreshold: product.lowStockThreshold || 10, }; // Update stock tracking status await clientFetch(`api/stock/${product._id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(stockData) }); // 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') => { setBulkAction(action); setIsConfirmDialogOpen(true); }; const executeBulkAction = async () => { if (!bulkAction) return; try { const productsToUpdate = products.filter(p => selectedProducts.includes(p._id || '')); await Promise.all(productsToUpdate.map(async (product) => { if (!product._id) return; const stockData: StockData = { stockTracking: bulkAction === 'enable', currentStock: product.currentStock || 0, lowStockThreshold: product.lowStockThreshold || 10, }; await clientFetch(`api/stock/${product._id}`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(stockData) }); })); // Update local state setProducts(products.map(p => { if (selectedProducts.includes(p._id || '')) { return { ...p, stockTracking: bulkAction === 'enable', }; } return p; })); setSelectedProducts([]); toast.success(`Stock tracking ${bulkAction}d for selected products`); } catch (error) { console.error(`Error ${bulkAction}ing stock tracking:`, error); toast.error(`Failed to ${bulkAction} stock tracking`); } setIsConfirmDialogOpen(false); setBulkAction(null); }; const toggleSelectProduct = (productId: string) => { setSelectedProducts(prev => prev.includes(productId) ? prev.filter(id => id !== productId) : [...prev, productId] ); }; const toggleSelectAll = () => { setSelectedProducts(prev => prev.length === products.length ? [] : products.map(p => p._id || '') ); }; const handleExportStock = async () => { setIsExporting(true); try { let response; let filename; switch (reportType) { case 'daily': response = await clientFetch(`/api/analytics/daily-stock-report?date=${exportDate}`); filename = `daily-stock-report-${exportDate}.csv`; break; case 'weekly': if (!exportDateRange?.from) { toast.error('Please select a date range for weekly report'); return; } const weekStart = format(exportDateRange.from, 'yyyy-MM-dd'); response = await clientFetch(`/api/analytics/weekly-stock-report?weekStart=${weekStart}`); filename = `weekly-stock-report-${weekStart}.csv`; break; case 'monthly': const year = selectedMonth.getFullYear(); const month = selectedMonth.getMonth() + 1; response = await clientFetch(`/api/analytics/monthly-stock-report?year=${year}&month=${month}`); filename = `monthly-stock-report-${year}-${month.toString().padStart(2, '0')}.csv`; break; case 'custom': if (!exportDateRange?.from || !exportDateRange?.to) { toast.error('Please select a date range for custom report'); return; } const startDate = format(exportDateRange.from, 'yyyy-MM-dd'); const endDate = format(exportDateRange.to, 'yyyy-MM-dd'); response = await clientFetch(`/api/analytics/daily-stock-report?startDate=${startDate}&endDate=${endDate}`); filename = `custom-stock-report-${startDate}-to-${endDate}.csv`; break; default: toast.error('Invalid report type'); return; } if (!response || !response.products) { throw new Error('No data received from server'); } // Convert data to CSV format const csvHeaders = [ 'Product Name', 'Quantity Sold', 'Total Revenue (£)', 'Average Price (£)', 'Order Count', 'Current Stock', 'Stock Status', 'Unit Type' ]; const csvData = [ csvHeaders.join(','), ...response.products.map((product: any) => [ `"${product.productName.replace(/"/g, '""')}"`, // Escape quotes in product names product.quantitySold, product.totalRevenue.toFixed(2), product.averagePrice.toFixed(2), product.orderCount, product.currentStock || 0, `"${product.stockStatus}"`, `"${product.unitType || 'N/A'}"` ].join(',')) ].join('\n'); // Create and download the CSV file const blob = new Blob([csvData], { type: 'text/csv;charset=utf-8;' }); const link = document.createElement('a'); const url = URL.createObjectURL(blob); link.setAttribute('href', url); link.setAttribute('download', filename); link.style.visibility = 'hidden'; document.body.appendChild(link); link.click(); document.body.removeChild(link); const periodText = reportType === 'daily' ? exportDate : reportType === 'weekly' ? `week starting ${format(exportDateRange?.from || new Date(), 'MMM dd')}` : reportType === 'monthly' ? `${response.monthName || 'current month'}` : `${format(exportDateRange?.from || new Date(), 'MMM dd')} to ${format(exportDateRange?.to || new Date(), 'MMM dd')}`; toast.success(`${reportType.charAt(0).toUpperCase() + reportType.slice(1)} stock report for ${periodText} exported successfully`); } catch (error) { console.error('Error exporting stock report:', error); toast.error('Failed to export stock report'); } finally { setIsExporting(false); } }; const getStockStatus = (product: Product) => { if (!product.stockTracking) return 'Not tracked'; if (product.currentStock === undefined) return 'Unknown'; if (product.currentStock <= 0) return 'Out of stock'; if (product.lowStockThreshold && product.currentStock <= product.lowStockThreshold) return 'Low stock'; return 'In stock'; }; const filteredProducts = products.filter(product => { if (!searchTerm) return true; const searchLower = searchTerm.toLowerCase(); return ( product.name.toLowerCase().includes(searchLower) || product.description.toLowerCase().includes(searchLower) || getStockStatus(product).toLowerCase().includes(searchLower) ); }); return (

Stock Management

setSearchTerm(e.target.value)} /> {/* Report Type Selector */} setReportType('daily')}> Daily Report setReportType('weekly')}> Weekly Report setReportType('monthly')}> Monthly Report setReportType('custom')}> Custom Range {/* Date Selection based on report type */} {reportType === 'daily' && ( setExportDate(date ? date.toISOString().split('T')[0] : '')} placeholder="Select export date" className="w-auto" /> )} {(reportType === 'weekly' || reportType === 'custom') && ( )} {reportType === 'monthly' && ( setSelectedMonth(date || new Date())} placeholder="Select month" className="w-auto" /> )} {selectedProducts.length > 0 && ( handleBulkAction('enable')}> Enable Stock Tracking handleBulkAction('disable')}> Disable Stock Tracking )}
Product Stock Status Current Stock Track Stock Actions {loading ? ( Loading products... ) : filteredProducts.length === 0 ? ( No products found ) : ( filteredProducts.map((product) => ( toggleSelectProduct(product._id || '')} className="rounded border-gray-300" /> {product.name} {getStockStatus(product)} {editingStock[product._id || ''] ? (
handleStockChange(product._id || '', parseInt(e.target.value) || 0)} className="w-24" />
) : ( {product.currentStock || 0} )}
handleToggleStockTracking(product)} /> {!editingStock[product._id || ''] && ( )}
)) )}
Confirm Bulk Action Are you sure you want to {bulkAction} stock tracking for {selectedProducts.length} selected products? setBulkAction(null)}>Cancel Continue
); }