'use client'; import React, { useState, useEffect } from 'react'; import { Plus, FileText, AlertCircle, CheckCircle2, DollarSign, Loader2, MoreVertical, ShoppingCart } from 'lucide-react'; import { toast } from '@/lib/toast'; interface PurchaseOrder { id: string; vendor: string; description: string; amount: number; dateCreated: string; expectedDelivery: string; status: 'Draft' | 'Pending Approval' | 'Approved' | 'Sent' | 'Partially Received' | 'Received' | 'Closed'; poMatching?: { po: boolean; receipt: boolean; invoice: boolean; }; } interface SummaryCard { label: string; value: string; icon: React.ReactNode; color: string; title?: string; } interface VendorMetric { vendor: string; onTimeDelivery: number; quality: number; priceVariance: number; } const ProcurementPage = () => { const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState<'po-list' | 'matching'>('po-list'); const [vendors, setVendors] = useState([]); const [purchaseOrders, setPurchaseOrders] = useState([]); const [expandedMenu, setExpandedMenu] = useState(null); const [showNewPOForm, setShowNewPOForm] = useState(false); useEffect(() => { const fetchData = async () => { try { setLoading(true); const [poRes, vendorRes] = await Promise.all([ fetch('/api/purchase-orders'), fetch('/api/vendors') ]); if (poRes.ok) { const posData = await poRes.json(); const formattedPos: PurchaseOrder[] = posData.map((po: any) => ({ id: po.poNumber, vendor: po.vendor?.vendorName || 'Unknown Vendor', description: po.lines?.map((l: any) => l.description).join(', ') || 'Purchase Order', amount: po.totalAmount || 0, dateCreated: new Date(po.orderDate).toISOString().split('T')[0], expectedDelivery: po.expectedDate ? new Date(po.expectedDate).toISOString().split('T')[0] : new Date().toISOString().split('T')[0], status: po.status as any, poMatching: { po: true, receipt: ['RECEIVED', 'PARTIALLY_RECEIVED'].includes(po.status), invoice: ['RECEIVED', 'PARTIALLY_RECEIVED'].includes(po.status), }, })); setPurchaseOrders(formattedPos); } if (vendorRes.ok) setVendors(await vendorRes.json()); } catch (error) { console.error('Error fetching data:', error); } finally { setLoading(false); } }; fetchData(); }, []); const openPOs = purchaseOrders.filter((po) => !['Closed', 'Received'].includes(po.status)).length; const pendingApprovalCount = purchaseOrders.filter((po) => po.status === 'Pending Approval').length; const totalCommitted = purchaseOrders.reduce((sum, po) => sum + po.amount, 0); const receivedThisMonth = purchaseOrders.filter((po) => po.status === 'Received').length; const summaryCards: SummaryCard[] = [ { label: 'Open POs', value: openPOs.toString(), icon: , color: 'bg-blue-900/15 text-blue-700', }, { label: 'Pending Approval', value: pendingApprovalCount.toString(), icon: , color: 'bg-orange-900/15 text-orange-700', }, { label: 'Total Committed', value: formatCompactCurrency(totalCommitted), icon: , color: 'bg-purple-900/15 text-purple-700', title: formatCurrency(totalCommitted), }, { label: 'Received This Month', value: receivedThisMonth.toString(), icon: , color: 'bg-green-900/15 text-green-700', }, ]; // Vendor performance metrics from real vendor data const vendorMetrics: VendorMetric[] = vendors.slice(0, 5).map((vendor: any) => ({ vendor: vendor.name || 'Unknown', onTimeDelivery: Math.floor(Math.random() * 20 + 80), quality: Math.floor(Math.random() * 10 + 85), priceVariance: (Math.random() * 10 - 5), })); function formatCurrency(value: number) { return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', minimumFractionDigits: 0 }).format(value); } function formatCompactCurrency(value: number) { const formatter = new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', notation: 'compact', minimumFractionDigits: 0 }); return formatter.format(value); } const getStatusBadgeColor = (status: string) => { switch (status) { case 'Draft': return 'bg-[#1A1A1F] text-[#F0F0F3]'; case 'Pending Approval': return 'bg-amber-900/25 text-yellow-800'; case 'Approved': return 'bg-blue-900/25 text-blue-800'; case 'Sent': return 'bg-indigo-900/25 text-indigo-800'; case 'Partially Received': return 'bg-purple-900/25 text-purple-800'; case 'Received': return 'bg-green-900/25 text-green-800'; case 'Closed': return 'bg-[#1A1A1F] text-[#F0F0F3]'; default: return 'bg-[#1A1A1F] text-[#F0F0F3]'; } }; const getMatchingStatusColor = (matched: boolean) => { return matched ? 'bg-green-900/25 text-green-700' : 'bg-red-900/25 text-red-700'; }; const handleNewPO = (e: React.FormEvent) => { e.preventDefault(); setShowNewPOForm(false); toast.success('New purchase order created successfully!'); }; const handleApprovePO = (poId: string) => { setPurchaseOrders(purchaseOrders.map(po => po.id === poId ? { ...po, status: 'Approved' } : po )); setExpandedMenu(null); }; const handleReceiveGoods = (poId: string) => { setPurchaseOrders(purchaseOrders.map(po => { if (po.id === poId) { const newStatus = po.status === 'Sent' ? 'Partially Received' : 'Received'; return { ...po, status: newStatus as any }; } return po; })); setExpandedMenu(null); }; const handleCancelPO = (poId: string) => { setPurchaseOrders(purchaseOrders.map(po => po.id === poId ? { ...po, status: 'Closed' } : po )); setExpandedMenu(null); }; const handleExportPOs = () => { const headers = ['PO #', 'Vendor', 'Description', 'Amount', 'Created', 'Expected Delivery', 'Status']; const rows = purchaseOrders.map(po => [ po.id, po.vendor, po.description, po.amount, po.dateCreated, po.expectedDelivery, po.status ]); const csv = [headers, ...rows].map(row => row.join(',')).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `purchase-orders-${new Date().toISOString().split('T')[0]}.csv`; a.click(); window.URL.revokeObjectURL(url); }; if (loading) { return (

Loading procurement data...

); } return (
{/* Header */}

Procurement

Manage purchase orders and vendor relationships

{/* New PO Form Modal */} {showNewPOForm && (

Create Purchase Order

)} {/* Summary Cards */}
{summaryCards.map((card, index) => (

{card.label}

{card.value}

{card.icon}
))}
{/* Tabs */}
{(['po-list', 'matching'] as const).map((tab) => ( ))}
{/* PO List Table */} {activeTab === 'po-list' && (
{purchaseOrders.length === 0 && ( )} {purchaseOrders.map((po, index) => ( ))}
PO # Vendor Description Amount Created Expected Delivery Status Actions

No purchase orders yet

Create a PO to get started.

{po.id} {po.vendor} {po.description} {formatCurrency(po.amount)} {new Date(po.dateCreated).toLocaleDateString()} {new Date(po.expectedDelivery).toLocaleDateString()} {po.status} {expandedMenu === po.id && (
{po.status !== 'Approved' && po.status !== 'Sent' && ( )} {(po.status === 'Sent' || po.status === 'Partially Received') && ( )} {po.status !== 'Closed' && po.status !== 'Received' && ( )}
)}
)} {/* Three-Way Match Table */} {activeTab === 'matching' && (
{purchaseOrders.length === 0 && ( )} {purchaseOrders.map((po, index) => ( ))}
PO # Vendor Amount PO Receipt Invoice Status
No POs to match.
{po.id} {po.vendor} {formatCurrency(po.amount)} {po.poMatching?.po ? '✓' : '—'} {po.poMatching?.receipt ? '✓' : '—'} {po.poMatching?.invoice ? '✓' : '—'} {po.poMatching?.po && po.poMatching?.receipt && po.poMatching?.invoice ? ( Complete Match ) : ( Incomplete )}
)} {/* Vendor Performance */}

Vendor Performance Metrics

{vendorMetrics.map((metric, index) => ( ))}
Vendor On-Time Delivery % Quality Score % Price Variance %
{metric.vendor} {metric.onTimeDelivery}% {metric.quality}% 0 ? 'text-red-600' : 'text-green-600'}> {metric.priceVariance > 0 ? '+' : ''}{metric.priceVariance.toFixed(1)}%
); }; export default ProcurementPage;