import { useState, useEffect } from 'react'; import { Title, Text, Stack, Card, SimpleGrid, Group, Button, Table, Badge, Loader, Center, Alert, ThemeIcon, Divider, Accordion, Paper, Tabs, Collapse, ActionIcon, } from '@mantine/core'; import { IconBulb, IconCash, IconBuildingBank, IconChartAreaLine, IconAlertTriangle, IconSparkles, IconRefresh, IconCoin, IconPigMoney, IconChevronDown, IconChevronUp, } from '@tabler/icons-react'; import { useQuery, useMutation } from '@tanstack/react-query'; import { notifications } from '@mantine/notifications'; import api from '../../services/api'; // ── Types ── interface FinancialSummary { operating_cash: number; reserve_cash: number; operating_investments: number; reserve_investments: number; total_operating: number; total_reserve: number; total_all: number; } interface FinancialSnapshot { summary: FinancialSummary; investment_accounts: Array<{ id: string; name: string; institution: string; investment_type: string; fund_type: string; principal: string; interest_rate: string; maturity_date: string | null; current_value: string; }>; } interface MarketRate { bank_name: string; apy: string; min_deposit: string | null; term: string; term_months: number | null; rate_type: string; fetched_at: string; } interface MarketRatesResponse { cd: MarketRate[]; money_market: MarketRate[]; high_yield_savings: MarketRate[]; } interface Recommendation { type: string; priority: 'high' | 'medium' | 'low'; title: string; summary: string; details: string; fund_type: string; suggested_amount?: number; suggested_term?: string; suggested_rate?: number; bank_name?: string; rationale: string; } interface AIResponse { recommendations: Recommendation[]; overall_assessment: string; risk_notes: string[]; } interface SavedRecommendation { id: string; recommendations: Recommendation[]; overall_assessment: string; risk_notes: string[]; response_time_ms: number; created_at: string; } // ── Helpers ── const fmt = (v: number) => v.toLocaleString('en-US', { style: 'currency', currency: 'USD' }); const priorityColors: Record = { high: 'red', medium: 'yellow', low: 'blue', }; const typeIcons: Record = { cd_ladder: IconChartAreaLine, new_investment: IconBuildingBank, reallocation: IconRefresh, maturity_action: IconCash, liquidity_warning: IconAlertTriangle, general: IconBulb, }; const typeLabels: Record = { cd_ladder: 'CD Ladder', new_investment: 'New Investment', reallocation: 'Reallocation', maturity_action: 'Maturity Action', liquidity_warning: 'Liquidity', general: 'General', }; // ── Rate Table Component ── function RateTable({ rates, showTerm }: { rates: MarketRate[]; showTerm: boolean }) { if (rates.length === 0) { return ( No rates available. Run the market rate fetcher to populate data. ); } return ( Bank APY {showTerm && Term} Min Deposit {rates.map((r, i) => ( {r.bank_name} {parseFloat(r.apy).toFixed(2)}% {showTerm && {r.term}} {r.min_deposit ? `$${parseFloat(r.min_deposit).toLocaleString()}` : '-'} ))}
); } // ── Recommendations Display Component ── function RecommendationsDisplay({ aiResult, lastUpdated }: { aiResult: AIResponse; lastUpdated?: string }) { return ( {/* Last Updated timestamp */} {lastUpdated && ( Last updated: {new Date(lastUpdated).toLocaleString()} )} {/* Overall Assessment */} {aiResult.overall_assessment} {/* Risk Notes */} {aiResult.risk_notes && aiResult.risk_notes.length > 0 && ( } > {aiResult.risk_notes.map((note, i) => ( {note} ))} )} {/* Recommendation Cards */} {aiResult.recommendations.length > 0 ? ( {aiResult.recommendations.map((rec, i) => { const Icon = typeIcons[rec.type] || IconBulb; return (
{rec.title} {rec.priority} {typeLabels[rec.type] || rec.type} {rec.fund_type} {rec.summary}
{rec.suggested_amount != null && ( {fmt(rec.suggested_amount)} )}
{rec.details} {(rec.suggested_term || rec.suggested_rate != null || rec.bank_name) && ( {rec.suggested_term && (
Suggested Term {rec.suggested_term}
)} {rec.suggested_rate != null && (
Target Rate {rec.suggested_rate}% APY
)} {rec.bank_name && (
Bank {rec.bank_name}
)}
)} {rec.rationale}
); })}
) : ( No specific recommendations at this time. )}
); } // ── Main Component ── export function InvestmentPlanningPage() { const [aiResult, setAiResult] = useState(null); const [lastUpdated, setLastUpdated] = useState(null); const [ratesExpanded, setRatesExpanded] = useState(true); // Load financial snapshot on mount const { data: snapshot, isLoading: snapshotLoading } = useQuery({ queryKey: ['investment-planning-snapshot'], queryFn: async () => { const { data } = await api.get('/investment-planning/snapshot'); return data; }, }); // Load market rates (all types) on mount const { data: marketRates, isLoading: ratesLoading } = useQuery({ queryKey: ['investment-planning-market-rates'], queryFn: async () => { const { data } = await api.get('/investment-planning/market-rates'); return data; }, }); // Load saved recommendation on mount const { data: savedRec } = useQuery({ queryKey: ['investment-planning-saved-recommendation'], queryFn: async () => { const { data } = await api.get('/investment-planning/saved-recommendation'); return data; }, }); // Populate AI results from saved recommendation on load useEffect(() => { if (savedRec && !aiResult) { setAiResult({ recommendations: savedRec.recommendations, overall_assessment: savedRec.overall_assessment, risk_notes: savedRec.risk_notes, }); setLastUpdated(savedRec.created_at); } }, [savedRec]); // eslint-disable-line react-hooks/exhaustive-deps // AI recommendation (on-demand) const aiMutation = useMutation({ mutationFn: async () => { const { data } = await api.post('/investment-planning/recommendations', {}, { timeout: 300000 }); return data as AIResponse; }, onSuccess: (data) => { setAiResult(data); setLastUpdated(new Date().toISOString()); if (data.recommendations.length > 0) { notifications.show({ message: `Generated ${data.recommendations.length} investment recommendations`, color: 'green', }); } }, onError: (err: any) => { notifications.show({ message: err.response?.data?.message || 'Failed to get AI recommendations', color: 'red', }); }, }); if (snapshotLoading) { return (
); } const s = snapshot?.summary; // Determine the latest fetched_at timestamp across all rate types const allRatesList = [ ...(marketRates?.cd || []), ...(marketRates?.money_market || []), ...(marketRates?.high_yield_savings || []), ]; const latestFetchedAt = allRatesList.length > 0 ? allRatesList.reduce((latest, r) => new Date(r.fetched_at) > new Date(latest.fetched_at) ? r : latest, ).fetched_at : null; const totalRateCount = (marketRates?.cd?.length || 0) + (marketRates?.money_market?.length || 0) + (marketRates?.high_yield_savings?.length || 0); return ( {/* Page Header */}
Investment Planning Account overview, market rates, and AI-powered investment recommendations
{/* ── Section 1: Financial Snapshot Cards ── */} {s && ( Operating Cash {fmt(s.operating_cash)} Investments: {fmt(s.operating_investments)} Reserve Cash {fmt(s.reserve_cash)} Investments: {fmt(s.reserve_investments)} Total All Funds {fmt(s.total_all)} Operating: {fmt(s.total_operating)} | Reserve: {fmt(s.total_reserve)} Total Invested {fmt(s.operating_investments + s.reserve_investments)} Earning interest across all accounts )} {/* ── Section 2: Current Investments Table ── */} {snapshot?.investment_accounts && snapshot.investment_accounts.length > 0 && ( Current Investments Name Institution Type Fund Principal Rate Maturity {snapshot.investment_accounts.map((inv) => ( {inv.name} {inv.institution || '-'} {inv.investment_type} {inv.fund_type} {fmt(parseFloat(inv.principal))} {parseFloat(inv.interest_rate || '0').toFixed(2)}% {inv.maturity_date ? new Date(inv.maturity_date).toLocaleDateString() : '-'} ))}
)} {/* ── Section 3: Today's Market Rates (Collapsible with Tabs) ── */} Today's Market Rates {totalRateCount > 0 && ( {totalRateCount} rates )} {latestFetchedAt && ( Last fetched: {new Date(latestFetchedAt).toLocaleString()} )} setRatesExpanded((v) => !v)} title={ratesExpanded ? 'Collapse rates' : 'Expand rates'} > {ratesExpanded ? : } {ratesLoading ? (
) : ( CDs {(marketRates?.cd?.length || 0) > 0 && ( {marketRates?.cd?.length} )} Money Market {(marketRates?.money_market?.length || 0) > 0 && ( {marketRates?.money_market?.length} )} High Yield Savings {(marketRates?.high_yield_savings?.length || 0) > 0 && ( {marketRates?.high_yield_savings?.length} )} )}
{/* ── Section 4: AI Investment Recommendations ── */}
AI Investment Recommendations Powered by AI analysis of your complete financial picture
{/* Loading State */} {aiMutation.isPending && (
Analyzing your financial data and market rates... This may take a few minutes for complex tenant data
)} {/* Results */} {aiResult && !aiMutation.isPending && ( )} {/* Empty State */} {!aiResult && !aiMutation.isPending && ( AI-Powered Investment Analysis Click "Get AI Recommendations" to analyze your accounts, cash flow, budget, and capital projects against current market rates. The AI will suggest specific investment moves to maximize interest income while maintaining adequate liquidity. )}
); }