import React, { useState, useEffect } from 'react'; import { useLocation } from 'react-router-dom'; import { Line, Bar } from 'react-chartjs-2'; import { Chart as ChartJS, LineElement, BarElement, CategoryScale, LinearScale, Filler, PointElement, Tooltip, Legend } from 'chart.js'; import annotationPlugin from 'chartjs-plugin-annotation'; import authFetch from '../utils/authFetch.js'; import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js'; import parseFloatOrZero from '../utils/ParseFloatorZero.js'; import { getFullStateName } from '../utils/stateUtils.js'; import { Button } from './ui/button.js'; import CareerSelectDropdown from './CareerSelectDropdown.js'; import MilestoneTimeline from './MilestoneTimeline.js'; import ScenarioEditModal from './ScenarioEditModal.js'; import './CareerRoadmap.css'; import './MilestoneTimeline.css'; // -------------- // Register ChartJS Plugins // -------------- ChartJS.register( LineElement, BarElement, CategoryScale, LinearScale, Filler, PointElement, Tooltip, Legend, annotationPlugin ); // -------------- // Helper Functions // -------------- function stripSocCode(fullSoc) { if (!fullSoc) return ''; return fullSoc.split('.')[0]; } function getRelativePosition(userSal, p10, p90) { if (!p10 || !p90) return 0; if (userSal < p10) return 0; if (userSal > p90) return 1; return (userSal - p10) / (p90 - p10); } // A simple gauge for the user’s salary vs. percentiles function SalaryGauge({ userSalary, percentileRow, prefix = 'regional' }) { if (!percentileRow) return null; const p10 = parseFloatOrZero(percentileRow[`${prefix}_PCT10`], 0); const p90 = parseFloatOrZero(percentileRow[`${prefix}_PCT90`], 0); const median = parseFloatOrZero(percentileRow[`${prefix}_MEDIAN`], 0); if (!p10 || !p90 || p10 >= p90) { return null; } const userFrac = getRelativePosition(userSalary, p10, p90) * 100; const medianFrac = getRelativePosition(median, p10, p90) * 100; return (
${p10.toLocaleString()} ${p90.toLocaleString()}
{/* Median Marker */}
Median ${median.toLocaleString()}
{/* User Salary Marker */}
${userSalary.toLocaleString()}
); } function EconomicProjectionsBar({ data }) { if (!data) return null; const { area, baseYear, projectedYear, base, projection, change, annualOpenings, occupationName } = data; if (!area || !base || !projection) { return

No data for {area || 'this region'}.

; } const barData = { labels: [`${occupationName || 'Career'}: ${area}`], datasets: [ { label: `Jobs in ${baseYear}`, data: [base], backgroundColor: 'rgba(75,192,192,0.6)' }, { label: `Jobs in ${projectedYear}`, data: [projection], backgroundColor: 'rgba(255,99,132,0.6)' } ] }; const barOptions = { responsive: true, plugins: { legend: { position: 'bottom' }, tooltip: { callbacks: { label: (ctx) => `${ctx.dataset.label}: ${ctx.parsed.y.toLocaleString()}` } } }, scales: { y: { beginAtZero: true, ticks: { callback: (val) => val.toLocaleString() } } } }; return (

{area}

Change: {change?.toLocaleString() ?? 0} jobs

Annual Openings: {annualOpenings?.toLocaleString() ?? 0}

); } function getYearsInCareer(startDateString) { if (!startDateString) return null; const start = new Date(startDateString); if (isNaN(start)) return null; const now = new Date(); const diffMs = now - start; const diffYears = diffMs / (1000 * 60 * 60 * 24 * 365.25); if (diffYears < 1) { return '<1'; } return Math.floor(diffYears).toString(); } /** * parseAiJson * If ChatGPT returns a fenced code block like: * ```json * [ { ... }, ... ] * ``` * we extract that JSON. Otherwise, we parse the raw string. */ function parseAiJson(rawText) { const fencedRegex = /```json\s*([\s\S]*?)\s*```/i; const match = rawText.match(fencedRegex); if (match) { const jsonStr = match[1].trim(); const arr = JSON.parse(jsonStr); // Add an "id" for each milestone arr.forEach((m) => { m.id = crypto.randomUUID(); }); return arr; } else { // fallback if no fences const arr = JSON.parse(rawText); arr.forEach((m) => { m.id = crypto.randomUUID(); }); return arr; } } export default function CareerRoadmap({ selectedCareer: initialCareer }) { const location = useLocation(); const apiURL = process.env.REACT_APP_API_URL; const [interestStrategy, setInterestStrategy] = useState('FLAT'); // 'NONE' | 'FLAT' | 'MONTE_CARLO' const [flatAnnualRate, setFlatAnnualRate] = useState(0.06); // 6% default const [randomRangeMin, setRandomRangeMin] = useState(-0.02); // -3% monthly const [randomRangeMax, setRandomRangeMax] = useState(0.02); // 8% monthly // Basic states const [userProfile, setUserProfile] = useState(null); const [financialProfile, setFinancialProfile] = useState(null); const [masterCareerRatings, setMasterCareerRatings] = useState([]); const [existingCareerProfiles, setExistingCareerProfiles] = useState([]); const [selectedCareer, setSelectedCareer] = useState(initialCareer || null); const [careerProfileId, setCareerProfileId] = useState(null); const [scenarioRow, setScenarioRow] = useState(null); const [collegeProfile, setCollegeProfile] = useState(null); const [strippedSocCode, setStrippedSocCode] = useState(null); const [salaryData, setSalaryData] = useState(null); const [economicProjections, setEconomicProjections] = useState(null); // Milestones & Projection const [scenarioMilestones, setScenarioMilestones] = useState([]); const [projectionData, setProjectionData] = useState([]); const [loanPayoffMonth, setLoanPayoffMonth] = useState(null); // Config const [simulationYearsInput, setSimulationYearsInput] = useState('20'); const simulationYears = parseInt(simulationYearsInput, 10) || 20; const [showEditModal, setShowEditModal] = useState(false); // AI const [aiLoading, setAiLoading] = useState(false); const [recommendations, setRecommendations] = useState([]); // parsed array const [selectedIds, setSelectedIds] = useState([]); // which rec IDs are checked const [lastClickTime, setLastClickTime] = useState(null); const RATE_LIMIT_SECONDS = 15; // adjust as needed const [buttonDisabled, setButtonDisabled] = useState(false); const { projectionData: initProjData = [], loanPayoffMonth: initLoanMonth = null } = location.state || {}; // 1) Fetch user + financial useEffect(() => { async function fetchUser() { try { const r = await authFetch('/api/user-profile'); if (r.ok) setUserProfile(await r.json()); } catch (err) { console.error('Error user-profile =>', err); } } async function fetchFin() { try { const r = await authFetch(`${apiURL}/premium/financial-profile`); if (r.ok) setFinancialProfile(await r.json()); } catch (err) { console.error('Error financial =>', err); } } fetchUser(); fetchFin(); }, [apiURL]); const userSalary = parseFloatOrZero(financialProfile?.current_salary, 0); const userArea = userProfile?.area || 'U.S.'; const userState = getFullStateName(userProfile?.state || '') || 'United States'; useEffect(() => { let timer; if (buttonDisabled) { timer = setTimeout(() => setButtonDisabled(false), RATE_LIMIT_SECONDS * 1000); } return () => clearTimeout(timer); }, [buttonDisabled]); useEffect(() => { const storedRecs = localStorage.getItem('aiRecommendations'); if (storedRecs) { try { const arr = JSON.parse(storedRecs); arr.forEach((m) => { if (!m.id) { m.id = crypto.randomUUID(); } }); setRecommendations(arr); } catch (err) { console.error('Error parsing stored AI recs =>', err); } } }, []); useEffect(() => { if (recommendations.length > 0) { localStorage.setItem('aiRecommendations', JSON.stringify(recommendations)); } else { // if it's empty, we can remove from localStorage if you want localStorage.removeItem('aiRecommendations'); } }, [recommendations]); // 2) load local JSON => masterCareerRatings useEffect(() => { fetch('/careers_with_ratings.json') .then((res) => { if (!res.ok) throw new Error('Failed to load local career data'); return res.json(); }) .then((data) => setMasterCareerRatings(data)) .catch((err) => console.error('Error loading local career data =>', err)); }, []); // 3) fetch user’s career-profiles useEffect(() => { async function fetchProfiles() { const r = await authFetch(`${apiURL}/premium/career-profile/all`); if (!r || !r.ok) return; const d = await r.json(); setExistingCareerProfiles(d.careerProfiles); const fromPopout = location.state?.selectedCareer; if (fromPopout) { setSelectedCareer(fromPopout); setCareerProfileId(fromPopout.career_profile_id); } else { const stored = localStorage.getItem('lastSelectedCareerProfileId'); if (stored) { const match = d.careerProfiles.find((p) => p.id === stored); if (match) { setSelectedCareer(match); setCareerProfileId(stored); return; } } // fallback => latest const lr = await authFetch(`${apiURL}/premium/career-profile/latest`); if (lr && lr.ok) { const ld = await lr.json(); if (ld?.id) { setSelectedCareer(ld); setCareerProfileId(ld.id); } } } } fetchProfiles(); }, [apiURL, location.state]); // 4) scenarioRow + college useEffect(() => { if (!careerProfileId) { setScenarioRow(null); setCollegeProfile(null); setScenarioMilestones([]); return; } localStorage.setItem('lastSelectedCareerProfileId', careerProfileId); async function fetchScenario() { const s = await authFetch(`${apiURL}/premium/career-profile/${careerProfileId}`); if (s.ok) setScenarioRow(await s.json()); } async function fetchCollege() { const c = await authFetch(`${apiURL}/premium/college-profile?careerProfileId=${careerProfileId}`); if (c.ok) setCollegeProfile(await c.json()); } fetchScenario(); fetchCollege(); }, [careerProfileId, apiURL]); // 5) from scenarioRow => find the full SOC => strip useEffect(() => { if (!scenarioRow?.career_name || !masterCareerRatings.length) { setStrippedSocCode(null); return; } const lower = scenarioRow.career_name.trim().toLowerCase(); const found = masterCareerRatings.find( (obj) => obj.title?.trim().toLowerCase() === lower ); if (!found) { console.warn('No matching SOC =>', scenarioRow.career_name); setStrippedSocCode(null); return; } setStrippedSocCode(stripSocCode(found.soc_code)); }, [scenarioRow, masterCareerRatings]); // 6) Salary useEffect(() => { if (!strippedSocCode) { setSalaryData(null); return; } (async () => { try { const qs = new URLSearchParams({ socCode: strippedSocCode, area: userArea }).toString(); const url = `${apiURL}/salary?${qs}`; const r = await fetch(url); if (!r.ok) { console.error('[Salary fetch non-200 =>]', r.status); setSalaryData(null); return; } const dd = await r.json(); setSalaryData(dd); } catch (err) { console.error('[Salary fetch error]', err); setSalaryData(null); } })(); }, [strippedSocCode, userArea, apiURL]); // 7) Econ useEffect(() => { if (!strippedSocCode || !userState) { setEconomicProjections(null); return; } (async () => { const qs = new URLSearchParams({ state: userState }).toString(); const econUrl = `${apiURL}/projections/${strippedSocCode}?${qs}`; try { const r = await authFetch(econUrl); if (!r.ok) { console.error('[Econ fetch non-200 =>]', r.status); setEconomicProjections(null); return; } const econData = await r.json(); setEconomicProjections(econData); } catch (err) { console.error('[Econ fetch error]', err); setEconomicProjections(null); } })(); }, [strippedSocCode, userState, apiURL]); // 8) Build financial projection async function buildProjection() { try { const milUrl = `${apiURL}/premium/milestones?careerProfileId=${careerProfileId}`; const mr = await authFetch(milUrl); if (!mr.ok) { console.error('Failed to fetch milestones =>', mr.status); return; } const md = await mr.json(); const allMilestones = md.milestones || []; setScenarioMilestones(allMilestones); // fetch impacts const imPromises = allMilestones.map((m) => authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`) .then((r) => (r.ok ? r.json() : null)) .then((dd) => dd?.impacts || []) .catch((e) => { console.warn('Error fetching impacts =>', e); return []; }) ); const impactsForEach = await Promise.all(imPromises); const allImpacts = allMilestones .map((m, i) => ({ ...m, impacts: impactsForEach[i] || [] })) .flatMap((m) => m.impacts); const f = financialProfile; const financialBase = { currentSalary: parseFloatOrZero(f.current_salary, 0), additionalIncome: parseFloatOrZero(f.additional_income, 0), monthlyExpenses: parseFloatOrZero(f.monthly_expenses, 0), monthlyDebtPayments: parseFloatOrZero(f.monthly_debt_payments, 0), retirementSavings: parseFloatOrZero(f.retirement_savings, 0), emergencySavings: parseFloatOrZero(f.emergency_fund, 0), retirementContribution: parseFloatOrZero(f.retirement_contribution, 0), emergencyContribution: parseFloatOrZero(f.emergency_contribution, 0), extraCashEmergencyPct: parseFloatOrZero(f.extra_cash_emergency_pct, 50), extraCashRetirementPct: parseFloatOrZero(f.extra_cash_retirement_pct, 50) }; function parseScenarioOverride(overrideVal, fallbackVal) { if (overrideVal === null) { return fallbackVal; } return parseFloatOrZero(overrideVal, fallbackVal); } const s = scenarioRow; const scenarioOverrides = { monthlyExpenses: parseScenarioOverride( s.planned_monthly_expenses, financialBase.monthlyExpenses ), monthlyDebtPayments: parseScenarioOverride( s.planned_monthly_debt_payments, financialBase.monthlyDebtPayments ), monthlyRetirementContribution: parseScenarioOverride( s.planned_monthly_retirement_contribution, financialBase.retirementContribution ), monthlyEmergencyContribution: parseScenarioOverride( s.planned_monthly_emergency_contribution, financialBase.emergencyContribution ), surplusEmergencyAllocation: parseScenarioOverride( s.planned_surplus_emergency_pct, financialBase.extraCashEmergencyPct ), surplusRetirementAllocation: parseScenarioOverride( s.planned_surplus_retirement_pct, financialBase.extraCashRetirementPct ), additionalIncome: parseScenarioOverride( s.planned_additional_income, financialBase.additionalIncome ) }; const c = collegeProfile; const collegeData = { studentLoanAmount: parseFloatOrZero(c.existing_college_debt, 0), interestRate: parseFloatOrZero(c.interest_rate, 5), loanTerm: parseFloatOrZero(c.loan_term, 10), loanDeferralUntilGraduation: !!c.loan_deferral_until_graduation, academicCalendar: c.academic_calendar || 'monthly', annualFinancialAid: parseFloatOrZero(c.annual_financial_aid, 0), calculatedTuition: parseFloatOrZero(c.tuition, 0), extraPayment: parseFloatOrZero(c.extra_payment, 0), inCollege: c.college_enrollment_status === 'currently_enrolled' || c.college_enrollment_status === 'prospective_student', gradDate: c.expected_graduation || null, programType: c.program_type || null, creditHoursPerYear: parseFloatOrZero(c.credit_hours_per_year, 0), hoursCompleted: parseFloatOrZero(c.hours_completed, 0), programLength: parseFloatOrZero(c.program_length, 0), expectedSalary: parseFloatOrZero(c.expected_salary) || parseFloatOrZero(f.current_salary, 0) }; const mergedProfile = { currentSalary: financialBase.currentSalary, monthlyExpenses: scenarioOverrides.monthlyExpenses, monthlyDebtPayments: scenarioOverrides.monthlyDebtPayments, retirementSavings: financialBase.retirementSavings, emergencySavings: financialBase.emergencySavings, monthlyRetirementContribution: scenarioOverrides.monthlyRetirementContribution, monthlyEmergencyContribution: scenarioOverrides.monthlyEmergencyContribution, surplusEmergencyAllocation: scenarioOverrides.surplusEmergencyAllocation, surplusRetirementAllocation: scenarioOverrides.surplusRetirementAllocation, additionalIncome: scenarioOverrides.additionalIncome, // college studentLoanAmount: collegeData.studentLoanAmount, interestRate: collegeData.interestRate, loanTerm: collegeData.loanTerm, loanDeferralUntilGraduation: collegeData.loanDeferralUntilGraduation, academicCalendar: collegeData.academicCalendar, annualFinancialAid: collegeData.annualFinancialAid, calculatedTuition: collegeData.calculatedTuition, extraPayment: collegeData.extraPayment, inCollege: collegeData.inCollege, gradDate: collegeData.gradDate, programType: collegeData.programType, creditHoursPerYear: collegeData.creditHoursPerYear, hoursCompleted: collegeData.hoursCompleted, programLength: collegeData.programLength, expectedSalary: collegeData.expectedSalary, startDate: new Date().toISOString().slice(0, 10), simulationYears, milestoneImpacts: allImpacts, interestStrategy, flatAnnualRate, monthlyReturnSamples: [], // or keep an array if you have historical data randomRangeMin, randomRangeMax }; const { projectionData: pData, loanPaidOffMonth } = simulateFinancialProjection(mergedProfile); let cumu = mergedProfile.emergencySavings || 0; const finalData = pData.map((mo) => { cumu += mo.netSavings || 0; return { ...mo, cumulativeNetSavings: cumu }; }); setProjectionData(finalData); setLoanPayoffMonth(loanPaidOffMonth); } catch (err) { console.error('Error in scenario simulation =>', err); } } useEffect(() => { if (!financialProfile || !scenarioRow || !collegeProfile) return; buildProjection(); }, [financialProfile, scenarioRow, collegeProfile, careerProfileId, apiURL, simulationYears, interestStrategy, flatAnnualRate, randomRangeMin, randomRangeMax]); // Build chart datasets / annotations const milestoneAnnotationLines = {}; scenarioMilestones.forEach((m) => { if (!m.date) return; const d = new Date(m.date); if (isNaN(d)) return; const yyyy = d.getUTCFullYear(); const mm = String(d.getUTCMonth() + 1).padStart(2, '0'); const short = `${yyyy}-${mm}`; if (!projectionData.some((p) => p.month === short)) return; milestoneAnnotationLines[`milestone_${m.id}`] = { type: 'line', xMin: short, xMax: short, borderColor: 'orange', borderWidth: 2, label: { display: true, content: m.title || 'Milestone', color: 'orange', position: 'end' } }; }); const [clickCount, setClickCount] = useState(() => { const storedCount = localStorage.getItem('aiClickCount'); const storedDate = localStorage.getItem('aiClickDate'); const today = new Date().toISOString().slice(0, 10).slice(0, 10); if (storedDate !== today) { localStorage.setItem('aiClickDate', today); localStorage.setItem('aiClickCount', '0'); return 0; } return parseInt(storedCount || '0', 10); }); const DAILY_CLICK_LIMIT = 10; // example limit per day const hasStudentLoan = projectionData.some((p) => p.loanBalance > 0); const annotationConfig = {}; if (loanPayoffMonth && hasStudentLoan) { annotationConfig.loanPaidOffLine = { type: 'line', xMin: loanPayoffMonth, xMax: loanPayoffMonth, borderColor: 'rgba(255, 206, 86, 1)', borderWidth: 2, borderDash: [6, 6], label: { display: true, content: 'Loan Paid Off', position: 'end', backgroundColor: 'rgba(255, 206, 86, 0.8)', color: '#000', font: { size: 12 }, rotation: 0, yAdjust: -10 } }; } const allAnnotations = { ...milestoneAnnotationLines, ...annotationConfig }; const emergencyData = { label: 'Emergency Savings', data: projectionData.map((p) => p.emergencySavings), borderColor: 'rgba(255, 159, 64, 1)', backgroundColor: 'rgba(255, 159, 64, 0.2)', tension: 0.4, fill: true }; const retirementData = { label: 'Retirement Savings', data: projectionData.map((p) => p.retirementSavings), borderColor: 'rgba(75, 192, 192, 1)', backgroundColor: 'rgba(75, 192, 192, 0.2)', tension: 0.4, fill: true }; const totalSavingsData = { label: 'Total Savings', data: projectionData.map((p) => p.totalSavings), borderColor: 'rgba(54, 162, 235, 1)', backgroundColor: 'rgba(54, 162, 235, 0.2)', tension: 0.4, fill: true }; const loanBalanceData = { label: 'Loan Balance', data: projectionData.map((p) => p.loanBalance), borderColor: 'rgba(255, 99, 132, 1)', backgroundColor: 'rgba(255, 99, 132, 0.2)', tension: 0.4, fill: { target: 'origin', above: 'rgba(255,99,132,0.3)', below: 'transparent' } }; const chartDatasets = [emergencyData, retirementData]; if (hasStudentLoan) chartDatasets.push(loanBalanceData); chartDatasets.push(totalSavingsData); const yearsInCareer = getYearsInCareer(scenarioRow?.start_date); // -- AI Handler -- async function handleAiClick() { if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { alert('You have reached the daily limit for suggestions.'); return; } if (aiLoading || clickCount >= DAILY_CLICK_LIMIT) { alert('You have reached your daily limit of AI-generated recommendations. Please check back tomorrow.'); return; } setAiLoading(true); setSelectedIds([]); const oldRecTitles = recommendations.map(r => r.title.trim()).filter(Boolean); const acceptedTitles = scenarioMilestones.map(m => (m.title || '').trim()).filter(Boolean); const allToAvoid = [...oldRecTitles, ...acceptedTitles]; try { const payload = { userProfile, scenarioRow, financialProfile, collegeProfile, previouslyUsedTitles: allToAvoid }; const res = await authFetch('/api/premium/ai/next-steps', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!res.ok) throw new Error('AI request failed'); const data = await res.json(); const rawText = data.recommendations || ''; const arr = parseAiJson(rawText); setRecommendations(arr); localStorage.setItem('aiRecommendations', JSON.stringify(arr)); // Update click count setClickCount(prev => { const newCount = prev + 1; localStorage.setItem('aiClickCount', newCount); return newCount; }); } catch (err) { console.error('Error fetching AI next steps =>', err); } finally { setAiLoading(false); } } function handleToggle(recId) { setSelectedIds((prev) => { if (prev.includes(recId)) { return prev.filter((x) => x !== recId); } else { return [...prev, recId]; } }); } async function handleCreateSelectedMilestones() { if (!careerProfileId) return; const confirm = window.confirm('Create the selected AI suggestions as milestones?'); if (!confirm) return; const selectedRecs = recommendations.filter((r) => selectedIds.includes(r.id)); if (!selectedRecs.length) return; // Use the AI-suggested date: const payload = selectedRecs.map((rec) => ({ title: rec.title, description: rec.description || '', date: rec.date, // <-- use AI's date, not today's date career_profile_id: careerProfileId })); try { const r = await authFetch('/api/premium/milestone', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ milestones: payload }) }); if (!r.ok) throw new Error('Failed to create new milestones'); // re-run projection to see them in the chart await buildProjection(); // optionally clear alert('Milestones created successfully!'); setSelectedIds([]); } catch (err) { console.error('Error creating milestones =>', err); alert('Error saving new AI milestones.'); } } function handleSimulationYearsChange(e) { setSimulationYearsInput(e.target.value); } function handleSimulationYearsBlur() { if (!simulationYearsInput.trim()) setSimulationYearsInput('20'); } const buttonLabel = recommendations.length > 0 ? 'New Suggestions' : 'What Should I Do Next?'; return (

Where Am I Now?

{/* 1) Career */}

Current Career:{' '} {scenarioRow?.career_name || '(Select a career)'}

{yearsInCareer && (

Time in this career: {yearsInCareer}{' '} {yearsInCareer === '<1' ? 'year' : 'years'}

)}
{/* 2) Salary Benchmarks */}
{salaryData?.regional && (

Regional Salary Data ({userArea || 'U.S.'})

10th percentile:{' '} {salaryData.regional.regional_PCT10 ? `$${parseFloat(salaryData.regional.regional_PCT10).toLocaleString()}` : 'N/A'}

Median:{' '} {salaryData.regional.regional_MEDIAN ? `$${parseFloat(salaryData.regional.regional_MEDIAN).toLocaleString()}` : 'N/A'}

90th percentile:{' '} {salaryData.regional.regional_PCT90 ? `$${parseFloat(salaryData.regional.regional_PCT90).toLocaleString()}` : 'N/A'}

)} {salaryData?.national && (

National Salary Data

10th percentile:{' '} {salaryData.national.national_PCT10 ? `$${parseFloat(salaryData.national.national_PCT10).toLocaleString()}` : 'N/A'}

Median:{' '} {salaryData.national.national_MEDIAN ? `$${parseFloat(salaryData.national.national_MEDIAN).toLocaleString()}` : 'N/A'}

90th percentile:{' '} {salaryData.national.national_PCT90 ? `$${parseFloat(salaryData.national.national_PCT90).toLocaleString()}` : 'N/A'}

)}
{/* 3) Economic Projections */}
{economicProjections?.state && ( )} {economicProjections?.national && ( )}
{!economicProjections?.state && !economicProjections?.national && (

No economic data found.

)} {/* 4) Career Goals */}

Your Career Goals

{scenarioRow?.career_goals || 'No career goals entered yet.'}

{/* 5) Financial Projection */}

Financial Projection

{projectionData.length > 0 ? ( <> p.month), datasets: chartDatasets }} options={{ responsive: true, plugins: { legend: { position: 'bottom' }, tooltip: { mode: 'index', intersect: false }, annotation: { annotations: allAnnotations } }, scales: { y: { beginAtZero: false, ticks: { callback: (val) => `$${val.toLocaleString()}` } } } }} /> {loanPayoffMonth && hasStudentLoan && (

Loan Paid Off at:{' '} {loanPayoffMonth}

)} ) : (

No financial projection data found.

)}
{/* 6) Simulation length + Edit scenario */}
{ setShowEditModal(false); window.location.reload(); }} scenario={scenarioRow} financialProfile={financialProfile} setFinancialProfile={setFinancialProfile} collegeProfile={collegeProfile} setCollegeProfile={setCollegeProfile} apiURL={apiURL} authFetch={authFetch} /> {/* (E1) Interest Strategy */} {/* (E2) If FLAT => show the annual rate */} {interestStrategy === 'FLAT' && (
setFlatAnnualRate(parseFloatOrZero(e.target.value, 0.06))} className="border rounded p-1 w-20" />
)} {/* (E3) If MONTE_CARLO => show the random range */} {interestStrategy === 'MONTE_CARLO' && (
setRandomRangeMin(parseFloatOrZero(e.target.value, -0.02))} className="border rounded p-1 w-20 mr-2" /> setRandomRangeMax(parseFloatOrZero(e.target.value, 0.02))} className="border rounded p-1 w-20" />
)} {/* 7) AI Next Steps */}
{aiLoading &&

Generating your next steps…

} {/* If we have structured recs, show checkboxes */} {recommendations.length > 0 && (

Select the Advice You Want to Keep

    {recommendations.map((m) => (
  • handleToggle(m.id)} />
    {m.title} {m.date}

    {m.description}

  • ))}
{selectedIds.length > 0 && ( )}
)}
); }