8000 Phase 3-04: dashboard FIRE goal progress module by adeebahmed · Pull Request #63 · adeebahmed/Libertas · GitHub
[go: up one dir, main page]

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading 8000
Diff view
Diff view
75 changes: 75 additions & 0 deletions frontend/src/components/GoalProgress.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { useMemo } from 'react'
import type { RetirementPlan } from '../types'

function usd(n: number) {
return new Intl.NumberFormat('en-US', { style: 'currency', currency: 'USD', maximumFractionDigits: 0 }).format(n)
}

export default function GoalProgress({
plan,
fireType,
}: {
plan: RetirementPlan | null
fireType: string
}) {
const progress = useMemo(() => {
if (!plan || !plan.target) return 0
return Math.max(0, Math.min(100, (plan.current_balance / plan.target) * 100))
}, [plan])

if (!plan) return null

const nudges: string[] = []
if (plan.needed_monthly_contribution && plan.needed_monthly_contribution > plan.monthly_contribution) {
const delta = plan.needed_monthly_contribution - plan.monthly_contribution
nudges.push(`Increase contribution by ${usd(delta)}/mo to stay on timeline.`)
}
if (plan.on_track?.on_track) {
nudges.push('You are currently on track. Keep contribution consistency high.')
}
if (plan.years_to_target && plan.years_to_target <= 10) {
nudges.push(`At current pace, target in about ${plan.years_to_target} years.`)
}
if (!nudges.length) {
nudges.push('Set monthly contribution in Settings to generate stronger trajectory guidance.')
}

return (
<div className="card mb-24">
<div className="flex-between mb-12">
<div>
<div className="section-label mb-4">FIRE Goal Progress</div>
<div style={{ color: 'var(--text-2)', fontSize: 13 }}>
Mode: <strong style={{ textTransform: 'uppercase' }}>{fireType || 'regular'}</strong>
</div>
</div>
<div className="num" style={{ fontSize: 18 }}>{progress.toFixed(1)}%</div>
</div>

<div style={{ height: 8, borderRadius: 999, background: 'var(--bg-elevated)', marginBottom: 10 }}>
<div style={{ width: `${progress}%`, height: '100%', borderRadius: 999, background: 'var(--blue)' }} />
</div>

<div className="grid-3 mb-12">
<div>
<div className="section-label mb-4">Current</div>
<div className="num-mid">{usd(plan.current_balance)}</div>
</div>
<div>
<div className="section-label mb-4">Target</div>
<div className="num-mid">{usd(plan.target)}</div>
</div>
<div>
<div className="section-label mb-4">Timeline</div>
<div className="num-mid">{plan.years_to_target ?? '100+'} years</div>
</div>
</div>

<ul style={{ margin: 0, paddingLeft: 18 }}>
{nudges.map((n, i) => (
<li key={i} style={{ marginBottom: 5, color: 'var(--text-2)' }}>{n}</li>
))}
</ul>
</div>
)
}
7 changes: 6 additions & 1 deletion frontend/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useEffect, useMemo, useState } from 'react'
import { useNavigate } from 'react-router-dom'
import { useApi } from '../hooks/useApi'
import { api } from '../api/client'
import type { Account, BalanceSnapshot, Insight, NetWorth } from '../types'
import type { Account, BalanceSnapshot, Insight, NetWorth, RetirementPlan } from '../types'
import {
Area,
AreaChart,
Expand All @@ -14,6 +14,7 @@ import {
XAxis,
YAxis,
} from 'recharts'
import GoalProgress from '../components/GoalProgress'

const PIE_COLORS = ['#3b82f6', '#34d399', '#d4a840', '#a78bfa', '#22d3ee', '#f87171', '#60a5fa']
const RANGE_OPTIONS = ['1M', '3M', '6M', 'YTD', '1Y', 'ALL'] as const
Expand Down Expand Up @@ -85,6 +86,8 @@ export default function Dashboard() {
const { data: history } = useApi<BalanceSnapshot[]>(() => api.get(`/snapshots/net-worth?range=${range}`), [range])
const { data: accounts } = useApi<Account[]>(() => api.get('/accounts'), [])
const { data: insights } = useApi<Insight[]>(() => api.get('/insights'), [])
const { data: retirementPlan } = useApi<RetirementPlan>(() => api.get('/retirement/plan'), [])
const { data: settings } = useApi<Record<string, unknown>>(() => api.get('/settings'), [])

const groupedAccounts = useMemo(() => {
const source = [...(accounts ?? [])].sort((a, b) => Math.abs(b.balance) - Math.abs(a.balance))
Expand Down Expand Up @@ -169,6 +172,8 @@ export default function Dashboard() {
</div>
</div>

<GoalProgress plan={retirementPlan} fireType={String(settings?.fire_type ?? 'regular')} />

<div className="dashboard-top-grid" style={{ display: 'grid', gridTemplateColumns: '1fr 320px', gap: 20, marginBottom: 24 }}>
<div className="card">
<div className="flex-between mb-16" style={{ alignItems: 'center' }}>
Expand Down
8 changes: 8 additions & 0 deletions frontend/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,14 @@ export interface RetirementPlan {
}
}

export interface GoalProgressSnapshot {
fire_type: string
progress_pct: number
current_balance: number
target_balance: number
years_to_target: number | null
}

export interface TaxEstimate {
income_w2: number
income_1099: number
Expand Down
0