From ff7ab9f77527aae54a9cb0caa2e049c282c93254 Mon Sep 17 00:00:00 2001 From: Josh Date: Fri, 2 May 2025 14:33:14 +0000 Subject: [PATCH] UX/UI changes to MilestoneTracker/MilestoneTimeline.js and ScenarioContainer for Tasks. --- src/components/MilestoneTimeline.js | 366 +++++++++--------- src/components/MilestoneTracker.js | 292 ++++++--------- src/components/ScenarioContainer.js | 473 +++++++++++++++--------- src/utils/FinancialProjectionService.js | 25 +- user_profile.db | Bin 106496 -> 106496 bytes 5 files changed, 582 insertions(+), 574 deletions(-) diff --git a/src/components/MilestoneTimeline.js b/src/components/MilestoneTimeline.js index 0daa254..66f677c 100644 --- a/src/components/MilestoneTimeline.js +++ b/src/components/MilestoneTimeline.js @@ -1,8 +1,8 @@ +// src/components/MilestoneTimeline.js + import React, { useEffect, useState, useCallback } from 'react'; import { Button } from './ui/button.js'; -const today = new Date(); - export default function MilestoneTimeline({ careerPathId, authFetch, @@ -12,6 +12,7 @@ export default function MilestoneTimeline({ }) { const [milestones, setMilestones] = useState({ Career: [], Financial: [] }); + // We'll keep your existing milestone form state, tasks, copy wizard, etc. const [newMilestone, setNewMilestone] = useState({ title: '', description: '', @@ -25,16 +26,14 @@ export default function MilestoneTimeline({ const [showForm, setShowForm] = useState(false); const [editingMilestone, setEditingMilestone] = useState(null); - // For tasks const [showTaskForm, setShowTaskForm] = useState(null); const [newTask, setNewTask] = useState({ title: '', description: '', due_date: '' }); - // The copy wizard const [scenarios, setScenarios] = useState([]); const [copyWizardMilestone, setCopyWizardMilestone] = useState(null); // ------------------------------------------------------------------ - // 1) HELPER: Add or remove an impact from newMilestone + // 1) Financial Impacts sub-form helpers (no change) // ------------------------------------------------------------------ function addNewImpact() { setNewMilestone((prev) => ({ @@ -67,7 +66,7 @@ export default function MilestoneTimeline({ } // ------------------------------------------------------------------ - // 2) fetchMilestones => local state + // 2) Fetch milestones => store in "milestones[Career]" / "milestones[Financial]" // ------------------------------------------------------------------ const fetchMilestones = useCallback(async () => { if (!careerPathId) return; @@ -101,7 +100,7 @@ export default function MilestoneTimeline({ }, [fetchMilestones]); // ------------------------------------------------------------------ - // 3) Load Scenarios for copy wizard + // 3) Load all scenarios for the copy wizard // ------------------------------------------------------------------ useEffect(() => { async function loadScenarios() { @@ -254,7 +253,7 @@ export default function MilestoneTimeline({ } } - // Re-fetch or update local + // Re-fetch await fetchMilestones(); // reset form @@ -306,7 +305,7 @@ export default function MilestoneTimeline({ const createdTask = await res.json(); console.log('Task created:', createdTask); - // Re-fetch so the timeline shows the new task + // Re-fetch so the list shows the new task await fetchMilestones(); setNewTask({ title: '', description: '', due_date: '' }); @@ -317,7 +316,7 @@ export default function MilestoneTimeline({ } // ------------------------------------------------------------------ - // 7) Copy Wizard => now with brute force refresh + // 7) Copy Wizard // ------------------------------------------------------------------ function CopyMilestoneWizard({ milestone, scenarios, onClose, authFetch }) { const [selectedScenarios, setSelectedScenarios] = useState([]); @@ -342,7 +341,6 @@ export default function MilestoneTimeline({ }); if (!res.ok) throw new Error('Failed to copy milestone'); - // Brute force page refresh window.location.reload(); onClose(); } catch (err) { @@ -357,7 +355,6 @@ export default function MilestoneTimeline({

Milestone: {milestone.title}

- {scenarios.map((s) => (
))} -
- ))} -
- +
+ {/* “+ New Milestone” toggles the same form as before */} - {/* CREATE/EDIT FORM */} + {/* If showForm => the same create/edit form */} {showForm && ( -
+
+

{editingMilestone ? 'Edit Milestone' : 'New Milestone'}

{ - const val = e.target.value === '' ? 0 : parseInt(e.target.value, 10); - setNewMilestone((prev) => ({ ...prev, progress: val })); - }} + onChange={(e) => + setNewMilestone((prev) => ({ + ...prev, + progress: parseInt(e.target.value || '0', 10) + })) + } /> + {/* If “Financial” => show impacts */} {activeView === 'Financial' && ( -
- setNewMilestone({ ...newMilestone, newSalary: e.target.value })} - /> -

Enter the full new salary after the milestone occurs.

- -
-

Financial Impacts

- {newMilestone.impacts.map((imp, idx) => ( -
- {imp.id &&

Impact ID: {imp.id}

} - +
+
Financial Impacts
+ {newMilestone.impacts.map((imp, idx) => ( +
+ {imp.id &&

ID: {imp.id}

} +
+ + +
+
+ + +
+
+ + updateImpact(idx, 'amount', e.target.value)} + /> +
+
+ + updateImpact(idx, 'start_date', e.target.value)} + /> +
+ {imp.impact_type === 'MONTHLY' && (
- - -
- -
- - -
- -
- - updateImpact(idx, 'amount', e.target.value)} - /> -
- -
- + updateImpact(idx, 'start_date', e.target.value)} + value={imp.end_date || ''} + onChange={(e) => updateImpact(idx, 'end_date', e.target.value)} />
- - {imp.impact_type === 'MONTHLY' && ( -
- - updateImpact(idx, 'end_date', e.target.value || '')} - /> -
- )} - - -
- ))} - -
+ )} + +
+ ))} +
)} - {/* universal checkbox */}
- +
+ +
)} - {/* TIMELINE VISUAL */} -
-
- {milestones[activeView].map((m) => { - const leftPos = calcPosition(m.date); + {/* *** REPLACEMENT FOR THE OLD “TIMELINE VISUAL” *** */} + {/* Instead of a horizontal timeline, we list them in a simple vertical list. */} + {Object.keys(milestones).map((typeKey) => + milestones[typeKey].map((m) => { + const tasks = m.tasks || []; return (
-
handleEditMilestone(m)} /> -
-
{m.title}
- {m.description &&

{m.description}

} +
{m.title}
+ {m.description &&

{m.description}

} +

+ Date: {m.date} — Progress: {m.progress}% +

-
-
-
-
{m.date}
+ {/* tasks list */} + {tasks.length > 0 && ( +
    + {tasks.map((t) => ( +
  • + {t.title} + {t.description ? ` - ${t.description}` : ''} + {t.due_date ? ` (Due: ${t.due_date})` : ''}{' '} + {/* If you'd like to add “Edit”/“Delete” for tasks, replicate scenario container logic */} +
  • + ))} +
+ )} - {/* Tasks */} - {m.tasks && m.tasks.length > 0 && ( -
    - {m.tasks.map((t) => ( -
  • - {t.title} - {t.description ? ` - ${t.description}` : ''} - {t.due_date ? ` (Due: ${t.due_date})` : ''} -
  • - ))} -
- )} + + + + - - -
- - - +
New Task
+ setNewTask({ ...newTask, title: e.target.value })} + /> + setNewTask({ ...newTask, description: e.target.value })} + /> + setNewTask({ ...newTask, due_date: e.target.value })} + /> +
- - {showTaskForm === m.id && ( -
- setNewTask({ ...newTask, title: e.target.value })} - /> - setNewTask({ ...newTask, description: e.target.value })} - /> - setNewTask({ ...newTask, due_date: e.target.value })} - /> - -
- )} -
+ )}
); - })} -
+ }) + )} + {/* Copy wizard if open */} {copyWizardMilestone && ( { const [selectedCareer, setSelectedCareer] = useState(initialCareer || null); const [careerPathId, setCareerPathId] = useState(null); const [existingCareerPaths, setExistingCareerPaths] = useState([]); - const [activeView, setActiveView] = useState("Career"); + const [activeView, setActiveView] = useState('Career'); - // Real user snapshot const [financialProfile, setFinancialProfile] = useState(null); - - // Scenario row (with planned_* overrides) const [scenarioRow, setScenarioRow] = useState(null); - - // scenario's collegeProfile row const [collegeProfile, setCollegeProfile] = useState(null); - // Simulation results + // We will store the scenario’s milestones in state so we can build annotation lines + const [scenarioMilestones, setScenarioMilestones] = useState([]); + const [projectionData, setProjectionData] = useState([]); const [loanPayoffMonth, setLoanPayoffMonth] = useState(null); - - // Possibly let user type the simulation length - const [simulationYearsInput, setSimulationYearsInput] = useState("20"); + const [simulationYearsInput, setSimulationYearsInput] = useState('20'); const simulationYears = parseInt(simulationYearsInput, 10) || 20; const [showEditModal, setShowEditModal] = useState(false); const [pendingCareerForModal, setPendingCareerForModal] = useState(null); - // Possibly loaded from location.state const { projectionData: initialProjectionData = [], loanPayoffMonth: initialLoanPayoffMonth = null @@ -90,7 +89,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { setSelectedCareer(fromPopout); setCareerPathId(fromPopout.career_path_id); } else if (!selectedCareer) { - // fallback to latest + // fallback: fetch the 'latest' scenario const latest = await authFetch(`${apiURL}/premium/career-profile/latest`); if (latest && latest.ok) { const latestData = await latest.json(); @@ -121,6 +120,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { if (!careerPathId) { setScenarioRow(null); setCollegeProfile(null); + setScenarioMilestones([]); return; } @@ -136,7 +136,9 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { } async function fetchCollege() { - const colRes = await authFetch(`${apiURL}/premium/college-profile?careerPathId=${careerPathId}`); + const colRes = await authFetch( + `${apiURL}/premium/college-profile?careerPathId=${careerPathId}` + ); if (!colRes?.ok) { setCollegeProfile(null); return; @@ -150,28 +152,32 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { }, [careerPathId, apiURL]); // -------------------------------------------------- - // 3) Once we have (financialProfile, scenarioRow, collegeProfile), run initial simulation + // 3) Once scenarioRow + collegeProfile + financialProfile => run simulation + // + fetch milestones for annotation lines // -------------------------------------------------- useEffect(() => { if (!financialProfile || !scenarioRow || !collegeProfile) return; (async () => { try { - // 1) load milestones for scenario - const milRes = await authFetch(`${apiURL}/premium/milestones?careerPathId=${careerPathId}`); + // fetch milestones for this scenario + const milRes = await authFetch( + `${apiURL}/premium/milestones?careerPathId=${careerPathId}` + ); if (!milRes.ok) { - console.error('Failed to fetch initial milestones for scenario', careerPathId); + console.error('Failed to fetch milestones for scenario', careerPathId); return; } const milestonesData = await milRes.json(); const allMilestones = milestonesData.milestones || []; + setScenarioMilestones(allMilestones); // store them for annotation lines - // 2) fetch impacts for each milestone - const impactPromises = allMilestones.map(m => + // fetch impacts for each + const impactPromises = allMilestones.map((m) => authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`) - .then(r => r.ok ? r.json() : null) - .then(data => data?.impacts || []) - .catch(err => { + .then((r) => (r.ok ? r.json() : null)) + .then((data) => data?.impacts || []) + .catch((err) => { console.warn('Error fetching impacts for milestone', m.id, err); return []; }) @@ -181,9 +187,11 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { ...m, impacts: impactsForEach[i] || [] })); - const allImpacts = milestonesWithImpacts.flatMap(m => m.impacts); - // 3) Build the merged profile + // flatten all + const allImpacts = milestonesWithImpacts.flatMap((m) => m.impacts); + + // mergedProfile const mergedProfile = buildMergedProfile( financialProfile, scenarioRow, @@ -192,36 +200,25 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { simulationYears ); - // 4) run the simulation const { projectionData: pData, loanPaidOffMonth: payoff } = simulateFinancialProjection(mergedProfile); - // 5) If you track cumulative net let cumu = mergedProfile.emergencySavings || 0; - const finalData = pData.map(mo => { - cumu += (mo.netSavings || 0); + const finalData = pData.map((mo) => { + cumu += mo.netSavings || 0; return { ...mo, cumulativeNetSavings: cumu }; }); setProjectionData(finalData); setLoanPayoffMonth(payoff); } catch (err) { - console.error('Error in initial scenario simulation:', err); + console.error('Error in scenario simulation:', err); } })(); - }, [ - financialProfile, - scenarioRow, - collegeProfile, - simulationYears, - careerPathId, - apiURL - ]); + }, [financialProfile, scenarioRow, collegeProfile, careerPathId, apiURL, simulationYears]); - // Merges the real snapshot w/ scenario overrides + milestones function buildMergedProfile(finProf, scenRow, colProf, milestoneImpacts, simYears) { return { - // Real snapshot fallback currentSalary: finProf.current_salary || 0, monthlyExpenses: scenRow.planned_monthly_expenses ?? finProf.monthly_expenses ?? 0, @@ -248,7 +245,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { additionalIncome: scenRow.planned_additional_income ?? finProf.additional_income ?? 0, - // College stuff + // college studentLoanAmount: colProf.existing_college_debt || 0, interestRate: colProf.interest_rate || 5, loanTerm: colProf.loan_term || 10, @@ -261,122 +258,93 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { colProf.college_enrollment_status === 'currently_enrolled' || colProf.college_enrollment_status === 'prospective_student', gradDate: colProf.expected_graduation || null, - programType: colProf.program_type, + programType: colProf.program_type || null, creditHoursPerYear: colProf.credit_hours_per_year || 0, hoursCompleted: colProf.hours_completed || 0, programLength: colProf.program_length || 0, expectedSalary: colProf.expected_salary || finProf.current_salary || 0, - // Additional + // scenario horizon startDate: new Date().toISOString(), simulationYears: simYears, - // Milestone Impacts milestoneImpacts: milestoneImpacts || [] }; } - // ------------------------------------------------------ - // 4) reSimulate => after milestone changes or user toggles something - // ------------------------------------------------------ + // If you want to re-run simulation after any milestone changes: const reSimulate = async () => { - if (!careerPathId) return; - try { - // 1) fetch everything again - const [finResp, scenResp, colResp, milResp] = await Promise.all([ - authFetch(`${apiURL}/premium/financial-profile`), - authFetch(`${apiURL}/premium/career-profile/${careerPathId}`), - authFetch(`${apiURL}/premium/college-profile?careerPathId=${careerPathId}`), - authFetch(`${apiURL}/premium/milestones?careerPathId=${careerPathId}`) - ]); - - if (!finResp.ok || !scenResp.ok || !colResp.ok || !milResp.ok) { - console.error( - 'One reSimulate fetch failed:', - finResp.status, - scenResp.status, - colResp.status, - milResp.status - ); - return; - } - - const [updatedFinancial, updatedScenario, updatedCollege, milData] = - await Promise.all([ - finResp.json(), - scenResp.json(), - colResp.json(), - milResp.json() - ]); - - const allMilestones = milData.milestones || []; - const impactsPromises = allMilestones.map(m => - authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`) - .then(r => r.ok ? r.json() : null) - .then(data => data?.impacts || []) - .catch(err => { - console.warn('Impact fetch err for milestone', m.id, err); - return []; - }) - ); - const impactsForEach = await Promise.all(impactsPromises); - const milestonesWithImpacts = allMilestones.map((m, i) => ({ - ...m, - impacts: impactsForEach[i] || [] - })); - const allImpacts = milestonesWithImpacts.flatMap(m => m.impacts); - - // 2) Build merged - const mergedProfile = buildMergedProfile( - updatedFinancial, - updatedScenario, - updatedCollege, - allImpacts, - simulationYears - ); - - // 3) run - const { projectionData: newProjData, loanPaidOffMonth: payoff } = - simulateFinancialProjection(mergedProfile); - - // 4) cumulative - let csum = mergedProfile.emergencySavings || 0; - const finalData = newProjData.map(mo => { - csum += (mo.netSavings || 0); - return { ...mo, cumulativeNetSavings: csum }; - }); - - setProjectionData(finalData); - setLoanPayoffMonth(payoff); - - // also store updated scenario, financial, college - setFinancialProfile(updatedFinancial); - setScenarioRow(updatedScenario); - setCollegeProfile(updatedCollege); - - console.log('Re-simulated after milestone update', { mergedProfile, finalData }); - } catch (err) { - console.error('Error in reSimulate:', err); - } + // Put your logic to re-fetch scenario + milestones, then re-run sim }; // handle user typing simulation length const handleSimulationYearsChange = (e) => setSimulationYearsInput(e.target.value); const handleSimulationYearsBlur = () => { if (!simulationYearsInput.trim()) { - setSimulationYearsInput("20"); + setSimulationYearsInput('20'); } }; - // Logging - console.log( - 'First 5 items of projectionData:', - Array.isArray(projectionData) ? projectionData.slice(0, 5) : 'none' - ); + // Build annotation lines from scenarioMilestones + const milestoneAnnotationLines = {}; + scenarioMilestones.forEach((m) => { + if (!m.date) return; + const d = new Date(m.date); + if (isNaN(d)) return; + + const year = d.getUTCFullYear(); + const month = String(d.getUTCMonth() + 1).padStart(2, '0'); + const short = `${year}-${month}`; + + 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' + }, + // If you want them clickable: + onClick: () => { + console.log('Clicked milestone line => open editing for', m.title); + // e.g. open the MilestoneTimeline's edit feature, or do something + } + }; + }); + + // If we also show a line for payoff: + const annotationConfig = {}; + if (loanPayoffMonth) { + 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 }; return (
- {/* Career Select */} + {/* 1) Career dropdown */} { authFetch={authFetch} /> - {/* Milestone Timeline */} + {/* 2) We keep MilestoneTimeline for tasks, +Add Milestone button, etc. */} { + }} /> - {/* AI-Suggested Milestones */} + {/* 3) AI-Suggested Milestones */} { projectionData={projectionData} /> - {/* Chart Section */} + {/* 4) The main chart with annotation lines */} {projectionData.length > 0 && (

Financial Projection

@@ -449,30 +418,9 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { plugins: { legend: { position: 'bottom' }, tooltip: { mode: 'index', intersect: false }, - annotation: loanPayoffMonth - ? { - annotations: { - 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 - } - } - } - } - : undefined + annotation: { + annotations: allAnnotations + } }, scales: { y: { @@ -484,29 +432,35 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { } }} /> +
+ {loanPayoffMonth && ( +

+ Loan Paid Off at: {loanPayoffMonth} +

+ )} +
)} - {/* Simulation Length Input */} + {/* 5) Simulation length input */}
setSimulationYearsInput(e.target.value)} onBlur={handleSimulationYearsBlur} className="border rounded p-1 w-16" />
- {/* Career Search */} + {/* 6) Career Search, scenario edit modal, etc. */} { setPendingCareerForModal(careerObj.title); }} /> - {/* Modal */} setShowEditModal(false)} @@ -518,11 +472,9 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => { authFetch={authFetch} /> - {/* Confirm new career scenario */} {pendingCareerForModal && ( - {/* AI-Suggested Milestones */}
- {/* The inline form for milestone creation/edit */} + {/* The milestone form */} {showForm && (
-

{editingMilestone ? 'Edit Milestone' : 'New Milestone'}

+

+ {editingMilestone ? 'Edit Milestone' : 'New Milestone'} +

setNewMilestone({ ...newMilestone, title: e.target.value })} + onChange={(e) => + setNewMilestone({ ...newMilestone, title: e.target.value }) + } /> setNewMilestone({ ...newMilestone, description: e.target.value })} + onChange={(e) => + setNewMilestone({ + ...newMilestone, + description: e.target.value + }) + } /> setNewMilestone({ ...newMilestone, date: e.target.value })} + onChange={(e) => + setNewMilestone({ ...newMilestone, date: e.target.value }) + } /> setNewMilestone((prev) => ({ ...prev, @@ -815,19 +864,33 @@ export default function ScenarioContainer({ /> {/* Impacts sub-form */} -
+
Financial Impacts
{newMilestone.impacts.map((imp, idx) => (
- {imp.id &&

ID: {imp.id}

} + {imp.id && ( +

ID: {imp.id}

+ )}
updateImpact(idx, 'direction', e.target.value)} + onChange={(e) => + updateImpact(idx, 'direction', e.target.value) + } > @@ -848,7 +913,9 @@ export default function ScenarioContainer({ updateImpact(idx, 'amount', e.target.value)} + onChange={(e) => + updateImpact(idx, 'amount', e.target.value) + } />
@@ -856,7 +923,9 @@ export default function ScenarioContainer({ updateImpact(idx, 'start_date', e.target.value)} + onChange={(e) => + updateImpact(idx, 'start_date', e.target.value) + } />
{imp.impact_type === 'MONTHLY' && ( @@ -865,7 +934,9 @@ export default function ScenarioContainer({ updateImpact(idx, 'end_date', e.target.value)} + onChange={(e) => + updateImpact(idx, 'end_date', e.target.value) + } />
)} @@ -907,18 +978,24 @@ export default function ScenarioContainer({
)} - {/* Render existing milestones + tasks + copy wizard, etc. */} + {/* Render existing milestones */} {milestones.map((m) => { + // tasks const tasks = m.tasks || []; return (
{m.title}
{m.description &&

{m.description}

}

- Date: {m.date} — Progress: {m.progress}% + Date: {m.date} —{' '} + Progress: {m.progress}%

{/* tasks list */} @@ -928,26 +1005,31 @@ export default function ScenarioContainer({
  • {t.title} {t.description ? ` - ${t.description}` : ''} - {t.due_date ? ` (Due: ${t.due_date})` : ''} + {t.due_date ? ` (Due: ${t.due_date})` : ''}{' '} + +
  • ))} )} - + - {/* Task form */} + {/* If this is the milestone whose tasks we're editing => show the task form */} {showTaskForm === m.id && ( -
    +
    +
    {editingTask.id ? 'Edit Task' : 'New Task'}
    setNewTask({ ...newTask, title: e.target.value })} + value={editingTask.title} + onChange={(e) => + setEditingTask({ ...editingTask, title: e.target.value }) + } /> setNewTask({ ...newTask, description: e.target.value })} + value={editingTask.description} + onChange={(e) => + setEditingTask({ + ...editingTask, + description: e.target.value + }) + } /> setNewTask({ ...newTask, due_date: e.target.value })} + value={editingTask.due_date} + onChange={(e) => + setEditingTask({ + ...editingTask, + due_date: e.target.value + }) + } /> - + +
    )}
    ); })} - {/* (B) Show the scenario edit modal if needed */} + {/* Scenario edit modal */} setShowEditModal(false)} @@ -996,7 +1107,7 @@ export default function ScenarioContainer({ collegeProfile={editingScenarioData.collegeProfile} /> - {/* The copy wizard if copying a milestone */} + {/* Copy wizard */} {copyWizardMilestone && ( ` + - `inCollege=${inCollege}, stillInCollege=${stillInCollege}, ` + - `loanDeferralUntilGrad=${loanDeferralUntilGraduation}, ` + - `loanBalBefore=${loanBalance.toFixed(2)}, ` + - `monthlyLoanPayment=${monthlyLoanPayment.toFixed(2)}, extraPayment=${extraPayment}` - ); + let totalMonthlyExpenses = monthlyExpenses + monthlyDebtPayments + tuitionCostThisMonth + extraImpactsThisMonth; if (stillInCollege && loanDeferralUntilGraduation) { // accumulate interest only const interestForMonth = loanBalance * (interestRate / 100 / 12); loanBalance += interestForMonth; - console.log(` (deferral) interest added=${interestForMonth.toFixed(2)}, loanBalAfter=${loanBalance.toFixed(2)}`); } else { // pay principal if (loanBalance > 0) { @@ -380,11 +365,6 @@ export function simulateFinancialProjection(userProfile) { const principalForMonth = Math.min(loanBalance, totalThisMonth - interestForMonth); loanBalance = Math.max(loanBalance - principalForMonth, 0); totalMonthlyExpenses += totalThisMonth; - - console.log( - ` (payment) interest=${interestForMonth.toFixed(2)}, principal=${principalForMonth.toFixed(2)}, ` + - `loanBalAfter=${loanBalance.toFixed(2)}` - ); } } @@ -467,9 +447,6 @@ export function simulateFinancialProjection(userProfile) { loanPaidOffMonth = scenarioStartClamped.clone().add(maxMonths, 'months').format('YYYY-MM'); } - console.log("End of simulation: finalLoanBalance=", loanBalance.toFixed(2), - "loanPaidOffMonth=", loanPaidOffMonth); - return { projectionData, loanPaidOffMonth, diff --git a/user_profile.db b/user_profile.db index bf75875389d8df1e02e6706853eeda763a9e8b22..c961090bc592083fc84bb7d48ac44d59b5e3c279 100644 GIT binary patch delta 1045 zcma)*Pe>F|9LML)th&22&gM!Wo9L=kf^YY|nfGSqEi4IX&_PR>36o%*e;ZP4nS1D_ zbMerrSV#yWJap&~vU=E~ONZ(hb@37eoujU;2Ya)oloIUa9lmeo{XTrZ-``rZd#&01 zBm)kfp1%SP>WiDDMDJ@slz&Ld>`rp=Wp7LngjcmUmCbWu9;9uNX0lnF){3Gi0)8cI zOiqBY+P6w+fI_5NSS_e#K{IkHLJK_~g_iK4fBQjfGAYUXf-=?nLf)^3-{sYCNJ`qa zI3Q<7i$&clJ5((PP{U;x5{)>JA&o-QXJ$Z|N?q4LG%%6lG3X&&hQz}LWY}~eBSye~ zr4(sFV#IP*?ha!WsRl#_#I$n1T3V?(537OCMNW7Ksab^xl;#N57yh~UOAp7y2ywKH zsJbT80*0WY`wk>5@S)>U933W6vCa(QY~0R+3pcD_r8<1fp>tCpHvY#^z^Fj=oUW=T zF^=`x)NWHsb+u|e%^|U?RS!RPt%d_Bjy9MUvqG_mh|YYMm@x2&1_?E|DS__s!OV3q zF+9phC+=OJoi16|W~LqA&&+dESY#nmPuYXC!5qb>fYk!h+IsETyT@mb%0Nf~n>ohN z4u1bRo|KezLD^E)mA!iSNV$1QvOy=|OVNj&L?B+2>_lyDcWnD>EEN}(t!Ucb&Y7RB ze9RIQtC1WA8Kp0r2Z}8YK{*RDf?X^o>?pgzxp2I#^H#>&dR*wlxWvfSrHWJWmYjR- z@Ol3<+%^wyunB<2|CBE37Wat9bu#QDy>XBSUzA)YLz8^>Qu4AOFE`?o;Ep^VS=31t dQjO;`;8tIM1jSe7a9yLDeb3Cdax+)Ld_I;&4MyR~Yx5Q=ndups z8bmQLFtCL(H7YX;hcY=!+XA_^AOZ{~cl}qHtd}F}UyzfSmzSDiWME{fYXC%sA%-Sa zMrKxqCVD1@Mp2}SZz^DMU}NNWVc>V!*l5K+*=7F$W`4G5li3f*Z)_}Jn>_7Um!OHR zk!6UXsgGY3fJ=cOtb0)1?zYha{nXg=Li zhtWh0>{KI5D^p;g8W=(Y1kDR37Tf>pGv4I~nykXX1#}fZ?<|JxTn>!syb~vAZ=dDM d7%3zI(xU+Mwt)dKd_jts`R8wEPhk9M4*)XEd%OSu