'use client'; import { useState, useEffect } from 'react'; import { formatCurrency, formatCompactCurrency } from '@/lib/utils'; import { TrendingUp, DollarSign, Percent, Calendar, Plus, Loader2, AlertCircle, CheckCircle, Zap, ChevronDown, ChevronUp, } from 'lucide-react'; interface DebtFacility { id: string; name: string; lender: string; type: 'Revolving Credit' | 'Term Loan' | 'Line of Credit' | 'Equipment Financing' | 'Other'; commitmentAmount: number; drawnAmount: number; availableAmount: number; interestRate: string; maturityDate: string; status: 'Active' | 'Inactive' | 'Closed'; draws: DebtDraw[]; } interface DebtDraw { id: string; facilityId: string; drawDate: string; amount: number; repaymentDate: string | null; status: 'Active' | 'Repaid' | 'Partial'; interestRate: string; } interface Covenant { id: string; name: string; type: string; metric: string; threshold: string | number; currentValue: string | number; status: 'Compliant' | 'Warning' | 'Breach'; lastChecked: string; headroom: string; } interface NewFacilityForm { name: string; lender: string; type: 'Revolving Credit' | 'Term Loan' | 'Line of Credit' | 'Equipment Financing' | 'Other'; commitmentAmount: string; interestRate: string; maturityDate: string; } interface NewDrawForm { facilityId: string; amount: string; date: string; interestRate: string; } interface Toast { id: string; message: string; type: 'success' | 'error' | 'info'; } export default function CapitalMarketsPage() { const [loading, setLoading] = useState(true); const [activeTab, setActiveTab] = useState('facilities'); const [showNewFacility, setShowNewFacility] = useState(false); const [showRecordDraw, setShowRecordDraw] = useState(false); const [expandedFacilityId, setExpandedFacilityId] = useState(null); const [toasts, setToasts] = useState([]); const [facilities, setFacilities] = useState([]); const [draws, setDraws] = useState([]); const [covenants, setCovenants] = useState([]); const [newFacilityForm, setNewFacilityForm] = useState({ name: '', lender: '', type: 'Revolving Credit', commitmentAmount: '', interestRate: '', maturityDate: '', }); const [newDrawForm, setNewDrawForm] = useState({ facilityId: '', amount: '', date: new Date().toISOString().split('T')[0], interestRate: '', }); const [summary, setSummary] = useState({ totalDebtOutstanding: 0, totalAvailable: 0, weightedAvgRate: 0, nextMaturityDate: null as string | null, covenantStatus: 'All Clear', }); // Toast helper const addToast = (message: string, type: 'success' | 'error' | 'info' = 'info') => { const id = Math.random().toString(36).substring(7); setToasts((prev) => [...prev, { id, message, type }]); setTimeout(() => { setToasts((prev) => prev.filter((t) => t.id !== id)); }, 4000); }; // Fetch data from API useEffect(() => { const fetchData = async () => { try { setLoading(true); const res = await fetch('/api/capital-markets'); if (!res.ok) throw new Error('Failed to fetch capital markets data'); const data = await res.json(); setFacilities(data.facilities); setDraws(data.draws); setCovenants(data.covenants); setSummary({ totalDebtOutstanding: data.summary?.totalOutstanding ?? 0, totalAvailable: data.summary?.totalAvailable ?? 0, weightedAvgRate: data.summary?.weightedAvgRate ?? 0, nextMaturityDate: data.summary?.nextMaturityDate ?? '', covenantStatus: data.summary?.covenantStatus ?? 'Unknown', }); } catch (err) { console.error('Error fetching capital markets data:', err); addToast('Failed to fetch capital markets data', 'error'); } finally { setLoading(false); } }; fetchData(); }, []); const handleAddFacility = async () => { if (!newFacilityForm.name || !newFacilityForm.lender || !newFacilityForm.commitmentAmount || !newFacilityForm.interestRate || !newFacilityForm.maturityDate) { addToast('Please fill in all required fields', 'error'); return; } try { setLoading(true); const res = await fetch('/api/capital-markets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'create-facility', facility: newFacilityForm, }), }); if (!res.ok) { const error = await res.json(); throw new Error(error.error || 'Failed to create facility'); } // Reset form and close modal setNewFacilityForm({ name: '', lender: '', type: 'Revolving Credit', commitmentAmount: '', interestRate: '', maturityDate: '', }); setShowNewFacility(false); addToast('Facility created successfully', 'success'); // Refetch data to keep in sync const refreshRes = await fetch('/api/capital-markets'); if (refreshRes.ok) { const refreshData = await refreshRes.json(); setFacilities(refreshData.facilities); setDraws(refreshData.draws); setCovenants(refreshData.covenants); setSummary({ totalDebtOutstanding: refreshData.summary.totalOutstanding, totalAvailable: refreshData.summary.totalAvailable, weightedAvgRate: refreshData.summary.weightedAvgRate, nextMaturityDate: refreshData.summary.nextMaturityDate, covenantStatus: refreshData.summary.covenantStatus, }); } } catch (err) { console.error('Error adding facility:', err); addToast(err instanceof Error ? err.message : 'Failed to create facility', 'error'); } finally { setLoading(false); } }; const handleRecordDraw = async () => { if (!newDrawForm.facilityId || !newDrawForm.amount) { addToast('Please select a facility and enter an amount', 'error'); return; } try { setLoading(true); const res = await fetch('/api/capital-markets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ action: 'record-draw', draw: newDrawForm, }), }); if (!res.ok) { const error = await res.json(); throw new Error(error.error || 'Failed to record draw'); } setNewDrawForm({ facilityId: '', amount: '', date: new Date().toISOString().split('T')[0], interestRate: '', }); setShowRecordDraw(false); addToast('Draw recorded successfully', 'success'); // Refetch data to keep in sync const refreshRes = await fetch('/api/capital-markets'); if (refreshRes.ok) { const refreshData = await refreshRes.json(); setFacilities(refreshData.facilities); setDraws(refreshData.draws); setCovenants(refreshData.covenants); setSummary({ totalDebtOutstanding: refreshData.summary.totalOutstanding, totalAvailable: refreshData.summary.totalAvailable, weightedAvgRate: refreshData.summary.weightedAvgRate, nextMaturityDate: refreshData.summary.nextMaturityDate, covenantStatus: refreshData.summary.covenantStatus, }); } } catch (err) { console.error('Error recording draw:', err); addToast(err instanceof Error ? err.message : 'Failed to record draw', 'error'); } finally { setLoading(false); } }; function formatDate(dateStr: string) { return new Date(dateStr).toLocaleDateString('en-US', { year: 'numeric', month: 'short', day: 'numeric', }); } const getCovenanStatusColor = (status: string) => { switch (status) { case 'Compliant': return 'bg-green-900/25 text-green-800 border-green-200'; case 'Warning': return 'bg-yellow-900/25 text-yellow-400 border-yellow-500/25'; case 'Breach': return 'bg-red-900/25 text-red-800 border-red-200'; default: return 'bg-[#1A1A1F] text-[#F0F0F3] border-[#2A2A32]'; } }; const getCovenanStatusIcon = (status: string) => { switch (status) { case 'Compliant': return ; case 'Warning': return ; case 'Breach': return ; default: return null; } }; const getCovenantSummaryBadge = () => { const status = summary.covenantStatus; if (status === 'In Breach') { return In Breach; } else if (status === 'At Risk') { return At Risk; } else { return All Clear; } }; if (loading && facilities.length === 0) { return (
); } return (
{/* Toast Notifications */}
{toasts.map((toast) => (
{toast.message}
))}
{/* Header */}

Capital Markets

Debt Management, Credit Facilities & Covenant Compliance

{/* Summary Cards */}

Total Debt Outstanding

{formatCompactCurrency(summary.totalDebtOutstanding)}

Across {facilities.length} facilities

Available Credit

{formatCompactCurrency(summary.totalAvailable)}

Ready to draw

Weighted Avg Rate

{summary.weightedAvgRate.toFixed(2)}%

All active facilities

Next Maturity

{summary.nextMaturityDate ? formatDate(summary.nextMaturityDate) : 'N/A'}

Upcoming refinancing

Covenant Status

{getCovenantSummaryBadge()}
{/* New Facility Form */} {showNewFacility && (

New Debt Facility

setNewFacilityForm({ ...newFacilityForm, name: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., Tech Bank Credit Line" />
setNewFacilityForm({ ...newFacilityForm, lender: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., JP Morgan Chase" />
setNewFacilityForm({ ...newFacilityForm, commitmentAmount: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="0" />
setNewFacilityForm({ ...newFacilityForm, interestRate: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" placeholder="e.g., 5.5% or SOFR+200bps" />
setNewFacilityForm({ ...newFacilityForm, maturityDate: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500" />
)} {/* Record Draw Form */} {showRecordDraw && (

Record Draw

setNewDrawForm({ ...newDrawForm, amount: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500" placeholder="0" />
setNewDrawForm({ ...newDrawForm, date: e.target.value })} className="w-full px-3 py-2 border border-[#3A3A45] rounded-lg focus:outline-none focus:ring-2 focus:ring-green-500" />
)} {/* Tabs */}
{[ { id: 'facilities', label: 'Debt Facilities' }, { id: 'draws', label: 'Active Draws' }, { id: 'covenants', label: 'Covenant Compliance' }, ].map((tab) => ( ))}
{/* Tab Content */}
{/* Facilities Tab */} {activeTab === 'facilities' && (

Debt Facilities

{facilities.map((facility) => (
setExpandedFacilityId( expandedFacilityId === facility.id ? null : facility.id ) } >
{expandedFacilityId === facility.id ? ( ) : ( )}

{facility.name}

{facility.lender} • {facility.type}

Drawn Amount

{formatCurrency(facility.drawnAmount)}

Available

{formatCurrency(facility.availableAmount)}

Rate

{facility.interestRate}

Maturity

{formatDate(facility.maturityDate)}

{facility.status}
{/* Expanded Details */} {expandedFacilityId === facility.id && (

Commitment Amount

{formatCurrency(facility.commitmentAmount)}

Drawn

{((facility.drawnAmount / facility.commitmentAmount) * 100).toFixed(1)}%

Available Capacity

{((facility.availableAmount / facility.commitmentAmount) * 100).toFixed(1)}%

{facility.draws.length > 0 && (

Draws on This Facility

{facility.draws.map((draw) => (

{formatDate(draw.drawDate)}

{draw.interestRate}

{formatCurrency(draw.amount)}

{draw.status}
))}
)}
)}
))}
)} {/* Draws Tab */} {activeTab === 'draws' && (

Active Draws

{draws.length === 0 ? (

No active draws. Record your first draw using the button at the top.

) : (
{draws.map((draw) => { const facility = facilities.find((f) => f.id === draw.facilityId); return ( ); })}
Facility Draw Date Amount Interest Rate Status
{facility?.name} {formatDate(draw.drawDate)} {formatCurrency(draw.amount)} {draw.interestRate} {draw.status}
)}
)} {/* Covenants Tab */} {activeTab === 'covenants' && (

Covenant Compliance Dashboard

{covenants.map((covenant) => (
{getCovenanStatusIcon(covenant.status)}

{covenant.name}

{covenant.type} Covenant

{covenant.status}

Metric

{covenant.metric}

Threshold

{covenant.threshold}

Current Value

{covenant.currentValue}

Headroom

{covenant.headroom}

Last checked: {formatDate(covenant.lastChecked)}

))}
)}
); }