Milestone hard refresh added.

This commit is contained in:
Josh 2025-04-24 11:53:31 +00:00
parent 23ac7260ab
commit 733dba46a8
5 changed files with 134 additions and 272 deletions

View File

@ -3,13 +3,13 @@ import React, { useEffect, useState, useCallback } from 'react';
const today = new Date();
const MilestoneTimeline = ({
export default function MilestoneTimeline({
careerPathId,
authFetch,
activeView,
setActiveView,
onMilestoneUpdated // optional callback if you want the parent to be notified of changes
}) => {
onMilestoneUpdated
}) {
const [milestones, setMilestones] = useState({ Career: [], Financial: [] });
// "new or edit" milestone form data
@ -22,8 +22,6 @@ const MilestoneTimeline = ({
impacts: [],
isUniversal: 0
});
// We'll track which existing impacts are removed so we can do a DELETE if needed
const [impactsToDelete, setImpactsToDelete] = useState([]);
const [showForm, setShowForm] = useState(false);
@ -33,17 +31,17 @@ const MilestoneTimeline = ({
const [showTaskForm, setShowTaskForm] = useState(null);
const [newTask, setNewTask] = useState({ title: '', description: '', due_date: '' });
// For the Copy wizard
// The copy wizard
const [scenarios, setScenarios] = useState([]);
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
// ------------------------------------------------------------------
// 1) Impact Helper Functions (define them first to avoid scoping errors)
// 1) HELPER FUNCTIONS (defined above usage)
// ------------------------------------------------------------------
// Insert a new blank impact into newMilestone.impacts
const addNewImpact = () => {
setNewMilestone(prev => ({
// Insert a new blank impact
function addNewImpact() {
setNewMilestone((prev) => ({
...prev,
impacts: [
...prev.impacts,
@ -56,53 +54,33 @@ const MilestoneTimeline = ({
}
]
}));
};
}
// Remove an impact from newMilestone.impacts
const removeImpact = (idx) => {
setNewMilestone(prev => {
function removeImpact(idx) {
setNewMilestone((prev) => {
const newImpacts = [...prev.impacts];
const removed = newImpacts[idx];
if (removed.id) {
// queue up for DB DELETE
setImpactsToDelete(old => [...old, removed.id]);
if (removed && removed.id) {
// queue for DB DELETE
setImpactsToDelete((old) => [...old, removed.id]);
}
newImpacts.splice(idx, 1);
return { ...prev, impacts: newImpacts };
});
};
}
// Update a specific impact property
const updateImpact = (idx, field, value) => {
setNewMilestone(prev => {
function updateImpact(idx, field, value) {
setNewMilestone((prev) => {
const newImpacts = [...prev.impacts];
newImpacts[idx] = { ...newImpacts[idx], [field]: value };
return { ...prev, impacts: newImpacts };
});
};
}
// ------------------------------------------------------------------
// 2) Load scenarios (for copy wizard)
// ------------------------------------------------------------------
useEffect(() => {
async function loadScenarios() {
try {
const res = await authFetch('/api/premium/career-profile/all');
if (res.ok) {
const data = await res.json();
setScenarios(data.careerPaths || []);
} else {
console.error('Failed to load scenarios. Status:', res.status);
}
} catch (err) {
console.error('Error loading scenarios for copy wizard:', err);
}
}
loadScenarios();
}, [authFetch]);
// ------------------------------------------------------------------
// 3) Fetch milestones for the current scenario
// 2) fetchMilestones => local state
// ------------------------------------------------------------------
const fetchMilestones = useCallback(async () => {
if (!careerPathId) return;
@ -114,12 +92,12 @@ const MilestoneTimeline = ({
}
const data = await res.json();
if (!data.milestones) {
console.warn('No milestones returned:', data);
console.warn('No milestones field in response:', data);
return;
}
const categorized = { Career: [], Financial: [] };
data.milestones.forEach(m => {
data.milestones.forEach((m) => {
if (categorized[m.milestone_type]) {
categorized[m.milestone_type].push(m);
} else {
@ -138,9 +116,27 @@ const MilestoneTimeline = ({
}, [fetchMilestones]);
// ------------------------------------------------------------------
// 4) "Edit" an existing milestone => load impacts
// 3) Load scenarios for copy wizard
// ------------------------------------------------------------------
const handleEditMilestone = async (m) => {
useEffect(() => {
async function loadScenarios() {
try {
const res = await authFetch('/api/premium/career-profile/all');
if (res.ok) {
const data = await res.json();
setScenarios(data.careerPaths || []);
}
} catch (err) {
console.error('Error loading scenarios for copy wizard:', err);
}
}
loadScenarios();
}, [authFetch]);
// ------------------------------------------------------------------
// 4) Edit Milestone => fetch impacts
// ------------------------------------------------------------------
async function handleEditMilestone(m) {
try {
setImpactsToDelete([]);
@ -158,7 +154,7 @@ const MilestoneTimeline = ({
date: m.date || '',
progress: m.progress || 0,
newSalary: m.new_salary || '',
impacts: fetchedImpacts.map(imp => ({
impacts: fetchedImpacts.map((imp) => ({
id: imp.id,
impact_type: imp.impact_type || 'ONE_TIME',
direction: imp.direction || 'subtract',
@ -171,16 +167,15 @@ const MilestoneTimeline = ({
setEditingMilestone(m);
setShowForm(true);
} catch (err) {
console.error('Error editing milestone:', err);
console.error('Error in handleEditMilestone:', err);
}
}
};
// ------------------------------------------------------------------
// 5) Save (create or update) a milestone => handle impacts if needed
// 5) Save (create/update) => handle impacts
// ------------------------------------------------------------------
const saveMilestone = async () => {
async function saveMilestone() {
if (!activeView) return;
const url = editingMilestone
@ -219,7 +214,6 @@ const MilestoneTimeline = ({
const savedMilestone = await res.json();
console.log('Milestone saved/updated:', savedMilestone);
// If financial => handle impacts
if (activeView === 'Financial') {
// 1) Delete old impacts
for (const impactId of impactsToDelete) {
@ -232,7 +226,6 @@ const MilestoneTimeline = ({
}
}
}
// 2) Insert/Update new impacts
for (let i = 0; i < newMilestone.impacts.length; i++) {
const imp = newMilestone.impacts[i];
@ -253,7 +246,7 @@ const MilestoneTimeline = ({
});
if (!impRes.ok) {
const errImp = await impRes.json();
console.error('Failed updating existing impact:', errImp);
console.error('Failed updating impact:', errImp);
}
} else {
// new => POST
@ -277,20 +270,10 @@ const MilestoneTimeline = ({
}
}
// optional local state update to avoid re-fetch
setMilestones((prev) => {
const newState = { ...prev };
if (editingMilestone) {
newState[activeView] = newState[activeView].map(m =>
m.id === editingMilestone.id ? savedMilestone : m
);
} else {
newState[activeView].push(savedMilestone);
}
return newState;
});
// Optionally re-fetch or update local
await fetchMilestones();
// reset the form
// reset form
setShowForm(false);
setEditingMilestone(null);
setNewMilestone({
@ -304,22 +287,18 @@ const MilestoneTimeline = ({
});
setImpactsToDelete([]);
// optionally re-fetch from DB
// await fetchMilestones();
if (onMilestoneUpdated) {
onMilestoneUpdated();
}
} catch (err) {
console.error('Error saving milestone:', err);
}
};
}
// ------------------------------------------------------------------
// 6) addTask => attach a new task to an existing milestone
// 6) Add Task
// ------------------------------------------------------------------
const addTask = async (milestoneId) => {
async function addTask(milestoneId) {
try {
const taskPayload = {
milestone_id: milestoneId,
@ -343,32 +322,18 @@ const MilestoneTimeline = ({
const createdTask = await res.json();
console.log('Task created:', createdTask);
// update local state
setMilestones((prev) => {
const newState = { ...prev };
['Career', 'Financial'].forEach((cat) => {
newState[cat] = newState[cat].map((m) => {
if (m.id === milestoneId) {
return {
...m,
tasks: [...(m.tasks || []), createdTask]
};
}
return m;
});
});
return newState;
});
// Re-fetch so the timeline shows the new task
await fetchMilestones();
setNewTask({ title: '', description: '', due_date: '' });
setShowTaskForm(null);
} catch (err) {
console.error('Error adding task:', err);
}
};
}
// ------------------------------------------------------------------
// 7) "Copy" wizard -> after copying => re-fetch or local update
// 7) Copy Wizard => now with brute force refresh
// ------------------------------------------------------------------
function CopyMilestoneWizard({ milestone, scenarios, onClose, authFetch }) {
const [selectedScenarios, setSelectedScenarios] = useState([]);
@ -376,13 +341,9 @@ const MilestoneTimeline = ({
if (!milestone) return null;
function toggleScenario(scenarioId) {
setSelectedScenarios(prev => {
if (prev.includes(scenarioId)) {
return prev.filter(id => id !== scenarioId);
} else {
return [...prev, scenarioId];
}
});
setSelectedScenarios((prev) =>
prev.includes(scenarioId) ? prev.filter((id) => id !== scenarioId) : [...prev, scenarioId]
);
}
async function handleCopy() {
@ -397,16 +358,10 @@ const MilestoneTimeline = ({
});
if (!res.ok) throw new Error('Failed to copy milestone');
const data = await res.json();
console.log('Copied milestone to new scenarios:', data);
// Brute force page refresh
window.location.reload();
onClose(); // close wizard
// re-fetch or update local
await fetchMilestones();
if (onMilestoneUpdated) {
onMilestoneUpdated();
}
onClose();
} catch (err) {
console.error('Error copying milestone:', err);
}
@ -418,7 +373,7 @@ const MilestoneTimeline = ({
<h3>Copy Milestone to Other Scenarios</h3>
<p>Milestone: <strong>{milestone.title}</strong></p>
{scenarios.map(s => (
{scenarios.map((s) => (
<div key={s.id}>
<label>
<input
@ -441,7 +396,7 @@ const MilestoneTimeline = ({
}
// ------------------------------------------------------------------
// 8) Delete milestone => single or all
// 8) handleDelete => also brute force refresh
// ------------------------------------------------------------------
async function handleDeleteMilestone(m) {
if (m.is_universal === 1) {
@ -458,22 +413,21 @@ const MilestoneTimeline = ({
console.error('Failed removing universal from all. Status:', delAll.status);
return;
}
// re-fetch
await fetchMilestones();
if (onMilestoneUpdated) {
onMilestoneUpdated();
}
} catch (err) {
console.error('Error deleting universal milestone from all:', err);
}
} else {
// remove from single scenario
await deleteSingleMilestone(m);
return;
}
} else {
// normal => single scenario
await deleteSingleMilestone(m);
}
// done => brute force
window.location.reload();
}
async function deleteSingleMilestone(m) {
@ -483,18 +437,13 @@ const MilestoneTimeline = ({
console.error('Failed to delete single milestone:', delRes.status);
return;
}
// re-fetch
await fetchMilestones();
if (onMilestoneUpdated) {
onMilestoneUpdated();
}
} catch (err) {
console.error('Error removing milestone from scenario:', err);
}
}
// ------------------------------------------------------------------
// 9) Positioning in the timeline
// 9) Render the timeline
// ------------------------------------------------------------------
const allMilestonesCombined = [...milestones.Career, ...milestones.Financial];
const lastDate = allMilestonesCombined.reduce((latest, m) => {
@ -502,22 +451,19 @@ const MilestoneTimeline = ({
return d > latest ? d : latest;
}, today);
const calcPosition = (dateString) => {
function calcPosition(dateString) {
const start = today.getTime();
const end = lastDate.getTime();
const dateVal = new Date(dateString).getTime();
if (end === start) return 0;
const ratio = (dateVal - start) / (end - start);
return Math.min(Math.max(ratio * 100, 0), 100);
};
}
// ------------------------------------------------------------------
// Render
// ------------------------------------------------------------------
return (
<div className="milestone-timeline">
<div className="view-selector">
{['Career', 'Financial'].map(view => (
{['Career', 'Financial'].map((view) => (
<button
key={view}
className={activeView === view ? 'active' : ''}
@ -528,11 +474,10 @@ const MilestoneTimeline = ({
))}
</div>
{/* + New Milestone button */}
<button
onClick={() => {
if (showForm) {
// Cancel
// Cancel form
setShowForm(false);
setEditingMilestone(null);
setNewMilestone({
@ -555,6 +500,7 @@ const MilestoneTimeline = ({
{showForm && (
<div className="form">
{/* Title / Desc / Date / Progress */}
<input
type="text"
placeholder="Title"
@ -571,7 +517,9 @@ const MilestoneTimeline = ({
type="date"
placeholder="Milestone Date"
value={newMilestone.date}
onChange={(e) => setNewMilestone(prev => ({ ...prev, date: e.target.value }))}
onChange={(e) =>
setNewMilestone((prev) => ({ ...prev, date: e.target.value }))
}
/>
<input
type="number"
@ -579,27 +527,26 @@ const MilestoneTimeline = ({
value={newMilestone.progress === 0 ? '' : newMilestone.progress}
onChange={(e) => {
const val = e.target.value === '' ? 0 : parseInt(e.target.value, 10);
setNewMilestone(prev => ({ ...prev, progress: val }));
setNewMilestone((prev) => ({ ...prev, progress: val }));
}}
/>
{/* If Financial => newSalary + impacts */}
{activeView === 'Financial' && (
<div>
<input
type="number"
placeholder="Full New Salary (e.g. 70000)"
placeholder="Full New Salary (e.g., 70000)"
value={newMilestone.newSalary}
onChange={(e) => setNewMilestone({ ...newMilestone, newSalary: e.target.value })}
/>
<p>Enter the full new salary (not just the increase) after the milestone occurs.</p>
<p>Enter the full new salary after the milestone occurs.</p>
<div className="impacts-section border p-2 mt-3">
<h4>Financial Impacts</h4>
{newMilestone.impacts.map((imp, idx) => (
<div key={idx} className="impact-item border p-2 my-2">
{imp.id && (
<p className="text-xs text-gray-500">Impact ID: {imp.id}</p>
)}
{imp.id && <p className="text-xs text-gray-500">Impact ID: {imp.id}</p>}
<div>
<label>Type: </label>
@ -674,7 +621,7 @@ const MilestoneTimeline = ({
type="checkbox"
checked={!!newMilestone.isUniversal}
onChange={(e) =>
setNewMilestone(prev => ({
setNewMilestone((prev) => ({
...prev,
isUniversal: e.target.checked ? 1 : 0
}))
@ -690,7 +637,7 @@ const MilestoneTimeline = ({
</div>
)}
{/* Timeline */}
{/* Actual timeline */}
<div className="milestone-timeline-container">
<div className="milestone-timeline-line" />
@ -736,7 +683,6 @@ const MilestoneTimeline = ({
{showTaskForm === m.id ? 'Cancel Task' : 'Add Task'}
</button>
{/* Edit, Copy, Delete Buttons */}
<div style={{ marginTop: '0.5rem' }}>
<button onClick={() => handleEditMilestone(m)}>Edit</button>
<button
@ -754,7 +700,7 @@ const MilestoneTimeline = ({
</div>
{showTaskForm === m.id && (
<div className="task-form">
<div className="task-form" style={{ marginTop: '0.5rem' }}>
<input
type="text"
placeholder="Task Title"
@ -781,18 +727,14 @@ const MilestoneTimeline = ({
})}
</div>
{/* CopyWizard modal if copying */}
{copyWizardMilestone && (
<CopyMilestoneWizard
milestone={copyWizardMilestone}
scenarios={scenarios}
onClose={() => setCopyWizardMilestone(null)}
authFetch={authFetch}
onMilestoneUpdated={onMilestoneUpdated}
/>
)}
</div>
);
};
export default MilestoneTimeline;
}

View File

@ -2,17 +2,13 @@
import React, { useEffect, useState } from 'react';
import authFetch from '../utils/authFetch.js';
import ScenarioContainer from './ScenarioContainer.js';
import { v4 as uuidv4 } from 'uuid';
export default function MultiScenarioView() {
const [loading, setLoading] = useState(false);
const [financialProfile, setFinancialProfile] = useState(null);
const [scenarios, setScenarios] = useState([]); // each scenario corresponds to a row in career_paths
// For error reporting
const [error, setError] = useState(null);
const [financialProfile, setFinancialProfile] = useState(null);
const [scenarios, setScenarios] = useState([]); // each scenario is a row in career_paths
// 1) On mount, fetch the users single financial profile + all career_paths.
useEffect(() => {
async function loadData() {
setLoading(true);
@ -40,10 +36,9 @@ export default function MultiScenarioView() {
loadData();
}, []);
// 2) “Add Scenario” => create a brand new row in career_paths
// “Add Scenario” => create a brand new row in career_paths
async function handleAddScenario() {
try {
// You might prompt user for a scenario name, or just default
const body = {
career_name: 'New Scenario ' + new Date().toLocaleDateString(),
status: 'planned',
@ -59,10 +54,9 @@ export default function MultiScenarioView() {
if (!res.ok) throw new Error(`Add scenario error: ${res.status}`);
const data = await res.json();
// re-fetch scenarios or just push a new scenario object
const newRow = {
id: data.career_path_id,
user_id: null, // we can skip if not needed
user_id: null,
career_name: body.career_name,
status: body.status,
start_date: body.start_date,
@ -70,18 +64,16 @@ export default function MultiScenarioView() {
college_enrollment_status: body.college_enrollment_status,
currently_working: body.currently_working
};
setScenarios(prev => [...prev, newRow]);
setScenarios((prev) => [...prev, newRow]);
} catch (err) {
console.error('Failed adding scenario:', err);
alert('Could not add scenario');
}
}
// 3) “Clone” => POST a new row in career_paths with copied fields
// “Clone” => create a new row in career_paths with copied fields
async function handleCloneScenario(sourceScenario) {
try {
// A simple approach: just create a new row with the same fields
// Then copy the existing scenario fields
const body = {
career_name: sourceScenario.career_name + ' (Copy)',
status: sourceScenario.status,
@ -98,15 +90,8 @@ export default function MultiScenarioView() {
if (!res.ok) throw new Error(`Clone scenario error: ${res.status}`);
const data = await res.json();
const newScenarioId = data.career_path_id;
// Optionally, also clone the scenarios milestones, if you want them duplicated:
// (Youd fetch all existing milestones for sourceScenario, then re-insert them for newScenario.)
// This example just leaves that out for brevity.
// Add it to local state
const newRow = {
id: newScenarioId,
id: data.career_path_id,
career_name: body.career_name,
status: body.status,
start_date: body.start_date,
@ -114,21 +99,18 @@ export default function MultiScenarioView() {
college_enrollment_status: body.college_enrollment_status,
currently_working: body.currently_working
};
setScenarios(prev => [...prev, newRow]);
setScenarios((prev) => [...prev, newRow]);
} catch (err) {
console.error('Failed cloning scenario:', err);
alert('Could not clone scenario');
}
}
// 4) “Remove” => (If you want a delete scenario)
// “Remove” => possibly remove from DB or just local
async function handleRemoveScenario(scenarioId) {
try {
// If you have a real DELETE endpoint for career_paths, use it:
// For now, well just remove from the local UI:
setScenarios(prev => prev.filter(s => s.id !== scenarioId));
// Optionally, implement an API call:
// await authFetch(`/api/premium/career-profile/${scenarioId}`, { method: 'DELETE' });
setScenarios((prev) => prev.filter((s) => s.id !== scenarioId));
// Optionally do an API call: DELETE /api/premium/career-profile/:id
} catch (err) {
console.error('Failed removing scenario:', err);
alert('Could not remove scenario');
@ -144,13 +126,9 @@ export default function MultiScenarioView() {
<ScenarioContainer
key={scen.id}
scenario={scen}
financialProfile={financialProfile} // shared for all scenarios
financialProfile={financialProfile} // shared for all
onClone={() => handleCloneScenario(scen)}
onRemove={() => handleRemoveScenario(scen.id)}
// Optionally refresh the scenario if user changes it
onScenarioUpdated={(updated) => {
setScenarios(prev => prev.map(s => s.id === scen.id ? { ...s, ...updated } : s));
}}
/>
))}

View File

@ -11,15 +11,11 @@ export default function ScenarioContainer({
scenario, // from career_paths row
financialProfile, // single row, shared across user
onClone,
onRemove,
onScenarioUpdated
onRemove
}) {
const [localScenario, setLocalScenario] = useState(scenario);
const [collegeProfile, setCollegeProfile] = useState(null);
const [milestones, setMilestones] = useState([]);
const [universalMilestones, setUniversalMilestones] = useState([]);
const [projectionData, setProjectionData] = useState([]);
const [loanPaidOffMonth, setLoanPaidOffMonth] = useState(null);
@ -35,9 +31,7 @@ export default function ScenarioContainer({
if (!localScenario?.id) return;
async function loadCollegeProfile() {
try {
const res = await authFetch(
`/api/premium/college-profile?careerPathId=${localScenario.id}`
);
const res = await authFetch(`/api/premium/college-profile?careerPathId=${localScenario.id}`);
if (res.ok) {
const data = await res.json();
setCollegeProfile(data);
@ -52,62 +46,23 @@ export default function ScenarioContainer({
loadCollegeProfile();
}, [localScenario]);
// 2) Fetch scenarios milestones (and universal)
useEffect(() => {
if (!localScenario?.id) return;
async function loadMilestones() {
try {
const [scenRes, uniRes] = await Promise.all([
authFetch(`/api/premium/milestones?careerPathId=${localScenario.id}`),
authFetch(`/api/premium/milestones?careerPathId=universal`) // if you have that route
]);
let scenarioData = scenRes.ok ? (await scenRes.json()) : { milestones: [] };
let universalData = uniRes.ok ? (await uniRes.json()) : { milestones: [] };
setMilestones(scenarioData.milestones || []);
setUniversalMilestones(universalData.milestones || []);
} catch (err) {
console.error('Failed to load milestones:', err);
}
}
loadMilestones();
}, [localScenario]);
// 3) Merge real snapshot + scenario overrides => run simulation
// 2) Whenever we have financialProfile + collegeProfile => run the simulation
useEffect(() => {
if (!financialProfile || !collegeProfile) return;
// Merge the scenario's planned overrides if not null,
// else fallback to the real snapshot in financialProfile
// Merge them into the userProfile object for the simulator:
const mergedProfile = {
currentSalary: financialProfile.current_salary || 0,
monthlyExpenses:
localScenario.planned_monthly_expenses ?? financialProfile.monthly_expenses ?? 0,
monthlyDebtPayments:
localScenario.planned_monthly_debt_payments ?? financialProfile.monthly_debt_payments ?? 0,
retirementSavings: financialProfile.retirement_savings ?? 0,
emergencySavings: financialProfile.emergency_fund ?? 0,
monthlyRetirementContribution:
localScenario.planned_monthly_retirement_contribution ??
financialProfile.retirement_contribution ??
0,
monthlyEmergencyContribution:
localScenario.planned_monthly_emergency_contribution ??
financialProfile.emergency_contribution ??
0,
surplusEmergencyAllocation:
localScenario.planned_surplus_emergency_pct ??
financialProfile.extra_cash_emergency_pct ??
50,
surplusRetirementAllocation:
localScenario.planned_surplus_retirement_pct ??
financialProfile.extra_cash_retirement_pct ??
50,
additionalIncome:
localScenario.planned_additional_income ?? financialProfile.additional_income ?? 0,
monthlyExpenses: financialProfile.monthly_expenses || 0,
monthlyDebtPayments: financialProfile.monthly_debt_payments || 0,
retirementSavings: financialProfile.retirement_savings || 0,
emergencySavings: financialProfile.emergency_fund || 0,
monthlyRetirementContribution: financialProfile.retirement_contribution || 0,
monthlyEmergencyContribution: financialProfile.emergency_contribution || 0,
surplusEmergencyAllocation: financialProfile.extra_cash_emergency_pct || 50,
surplusRetirementAllocation: financialProfile.extra_cash_retirement_pct || 50,
// College fields
// College fields (scenario-based)
studentLoanAmount: collegeProfile.existing_college_debt || 0,
interestRate: collegeProfile.interest_rate || 5,
loanTerm: collegeProfile.loan_term || 10,
@ -126,28 +81,16 @@ export default function ScenarioContainer({
collegeProfile.college_enrollment_status === 'prospective_student',
expectedSalary: collegeProfile.expected_salary || financialProfile.current_salary || 0,
// Flatten scenario + universal milestoneImpacts
milestoneImpacts: buildAllImpacts([...milestones, ...universalMilestones])
// milestoneImpacts is fetched & merged in MilestoneTimeline, not here
milestoneImpacts: []
};
const { projectionData, loanPaidOffMonth } =
simulateFinancialProjection(mergedProfile);
// 3) run the simulation
const { projectionData, loanPaidOffMonth } = simulateFinancialProjection(mergedProfile);
setProjectionData(projectionData);
setLoanPaidOffMonth(loanPaidOffMonth);
}, [financialProfile, collegeProfile, localScenario, milestones, universalMilestones]);
}, [financialProfile, collegeProfile]);
function buildAllImpacts(allMilestones) {
let impacts = [];
for (let m of allMilestones) {
if (m.impacts) {
impacts.push(...m.impacts);
}
// If new_salary logic is relevant, handle it here
}
return impacts;
}
// Edit => open modal
return (
<div style={{ width: '420px', border: '1px solid #ccc', padding: '1rem' }}>
<h3>{localScenario.career_name || 'Untitled Scenario'}</h3>
@ -170,18 +113,18 @@ export default function ScenarioContainer({
<div style={{ marginTop: '0.5rem' }}>
<strong>Loan Paid Off:</strong> {loanPaidOffMonth || 'N/A'} <br />
<strong>Retirement (final):</strong> ${
projectionData[projectionData.length - 1]?.retirementSavings?.toFixed(0) || 0
}
<strong>Final Retirement:</strong>{' '}
{projectionData[projectionData.length - 1]?.retirementSavings?.toFixed(0) || 0}
</div>
{/* The timeline that fetches scenario/universal milestones for display */}
<MilestoneTimeline
careerPathId={localScenario.id}
authFetch={authFetch}
activeView="Financial"
setActiveView={() => {}}
onMilestoneUpdated={() => {
// re-fetch or something
// might do scenario changes if you want
}}
/>
@ -203,7 +146,8 @@ export default function ScenarioContainer({
</button>
</div>
{/* Updated ScenarioEditModal that references localScenario + setLocalScenario */}
{/* If you do scenario-level editing for planned fields, show scenario edit modal */}
{editOpen && (
<ScenarioEditModal
show={editOpen}
onClose={() => setEditOpen(false)}
@ -211,6 +155,7 @@ export default function ScenarioContainer({
setScenario={setLocalScenario}
apiURL="/api"
/>
)}
</div>
);
}

View File

@ -226,9 +226,6 @@ export function simulateFinancialProjection(userProfile) {
let wasInDeferral = inCollege && loanDeferralUntilGraduation;
const graduationDateObj = gradDate ? moment(gradDate).startOf('month') : null;
console.log('simulateFinancialProjection - monthly tax approach');
console.log('scenarioStartClamped:', scenarioStartClamped.format('YYYY-MM-DD'));
/***************************************************
* 7) THE MONTHLY LOOP
***************************************************/

Binary file not shown.