Made modal float with scroll.
This commit is contained in:
parent
733dba46a8
commit
d48f33572a
@ -118,6 +118,7 @@ app.get('/api/premium/career-profile/:careerPathId', authenticatePremiumUser, as
|
||||
// server3.js
|
||||
app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res) => {
|
||||
const {
|
||||
scenario_title,
|
||||
career_name,
|
||||
status,
|
||||
start_date,
|
||||
@ -125,7 +126,6 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
college_enrollment_status,
|
||||
currently_working,
|
||||
|
||||
// NEW planned columns
|
||||
planned_monthly_expenses,
|
||||
planned_monthly_debt_payments,
|
||||
planned_monthly_retirement_contribution,
|
||||
@ -149,6 +149,7 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
INSERT INTO career_paths (
|
||||
id,
|
||||
user_id,
|
||||
scenario_title,
|
||||
career_name,
|
||||
status,
|
||||
start_date,
|
||||
@ -190,6 +191,7 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
`, [
|
||||
newCareerPathId,
|
||||
req.userId,
|
||||
scenario_title || null,
|
||||
career_name,
|
||||
status || 'planned',
|
||||
start_date || now,
|
||||
@ -229,6 +231,103 @@ app.post('/api/premium/career-profile', authenticatePremiumUser, async (req, res
|
||||
}
|
||||
});
|
||||
|
||||
// server3.js (or your premium server file)
|
||||
|
||||
// Delete a career path (scenario) by ID
|
||||
app.delete('/api/premium/career-profile/:careerPathId', authenticatePremiumUser, async (req, res) => {
|
||||
const { careerPathId } = req.params;
|
||||
|
||||
try {
|
||||
// 1) Confirm that this career_path belongs to the user
|
||||
const existing = await db.get(
|
||||
`
|
||||
SELECT id
|
||||
FROM career_paths
|
||||
WHERE id = ?
|
||||
AND user_id = ?
|
||||
`,
|
||||
[careerPathId, req.userId]
|
||||
);
|
||||
|
||||
if (!existing) {
|
||||
return res.status(404).json({ error: 'Career path not found or not yours.' });
|
||||
}
|
||||
|
||||
// 2) Optionally delete the college_profile for this scenario
|
||||
// (If you always keep 1-to-1 relationship: careerPathId => college_profile)
|
||||
await db.run(
|
||||
`
|
||||
DELETE FROM college_profiles
|
||||
WHERE user_id = ?
|
||||
AND career_path_id = ?
|
||||
`,
|
||||
[req.userId, careerPathId]
|
||||
);
|
||||
|
||||
// 3) Optionally delete scenario’s milestones
|
||||
// (and any associated tasks, impacts, etc.)
|
||||
// If you store tasks in tasks table, and impacts in milestone_impacts table:
|
||||
|
||||
// First find scenario milestones
|
||||
const scenarioMilestones = await db.all(
|
||||
`
|
||||
SELECT id
|
||||
FROM milestones
|
||||
WHERE user_id = ?
|
||||
AND career_path_id = ?
|
||||
`,
|
||||
[req.userId, careerPathId]
|
||||
);
|
||||
const milestoneIds = scenarioMilestones.map((m) => m.id);
|
||||
|
||||
if (milestoneIds.length > 0) {
|
||||
// Delete tasks for these milestones
|
||||
const placeholders = milestoneIds.map(() => '?').join(',');
|
||||
await db.run(
|
||||
`
|
||||
DELETE FROM tasks
|
||||
WHERE milestone_id IN (${placeholders})
|
||||
`,
|
||||
milestoneIds
|
||||
);
|
||||
|
||||
// Delete impacts for these milestones
|
||||
await db.run(
|
||||
`
|
||||
DELETE FROM milestone_impacts
|
||||
WHERE milestone_id IN (${placeholders})
|
||||
`,
|
||||
milestoneIds
|
||||
);
|
||||
|
||||
// Finally delete the milestones themselves
|
||||
await db.run(
|
||||
`
|
||||
DELETE FROM milestones
|
||||
WHERE id IN (${placeholders})
|
||||
`,
|
||||
milestoneIds
|
||||
);
|
||||
}
|
||||
|
||||
// 4) Finally delete the career_path row
|
||||
await db.run(
|
||||
`
|
||||
DELETE FROM career_paths
|
||||
WHERE user_id = ?
|
||||
AND id = ?
|
||||
`,
|
||||
[req.userId, careerPathId]
|
||||
);
|
||||
|
||||
res.json({ message: 'Career path and related data successfully deleted.' });
|
||||
} catch (error) {
|
||||
console.error('Error deleting career path:', error);
|
||||
res.status(500).json({ error: 'Failed to delete career path.' });
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Milestone ENDPOINTS
|
||||
------------------------------------------------------------------ */
|
||||
|
18
src/components/MultiScenarioView.css
Normal file
18
src/components/MultiScenarioView.css
Normal file
@ -0,0 +1,18 @@
|
||||
.modal-backdrop {
|
||||
position: fixed;
|
||||
top:0; left:0;
|
||||
width:100vw; height:100vh;
|
||||
background: rgba(0,0,0,0.5);
|
||||
z-index: 9999;
|
||||
}
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%,-50%);
|
||||
background: #fff;
|
||||
width: 600px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
@ -2,33 +2,43 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import authFetch from '../utils/authFetch.js';
|
||||
import ScenarioContainer from './ScenarioContainer.js';
|
||||
import ScenarioEditModal from './ScenarioEditModal.js'; // The floating modal
|
||||
|
||||
export default function MultiScenarioView() {
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(null);
|
||||
|
||||
// The user’s single overall financial profile
|
||||
const [financialProfile, setFinancialProfile] = useState(null);
|
||||
const [scenarios, setScenarios] = useState([]); // each scenario is a row in career_paths
|
||||
|
||||
// All scenario rows (from career_paths)
|
||||
const [scenarios, setScenarios] = useState([]);
|
||||
|
||||
// The scenario we’re currently editing in a top-level modal:
|
||||
const [editingScenario, setEditingScenario] = useState(null);
|
||||
// The collegeProfile we load for that scenario (passed to edit modal)
|
||||
const [editingCollegeProfile, setEditingCollegeProfile] = useState(null);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadData() {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
// A) fetch financial profile
|
||||
let finRes = await authFetch('/api/premium/financial-profile');
|
||||
if (!finRes.ok) throw new Error(`FIN profile error: ${finRes.status}`);
|
||||
let finData = await finRes.json();
|
||||
// 1) Fetch the user’s overall financial profile
|
||||
const finRes = await authFetch('/api/premium/financial-profile');
|
||||
if (!finRes.ok) throw new Error(`FinancialProfile error: ${finRes.status}`);
|
||||
const finData = await finRes.json();
|
||||
|
||||
// B) fetch all career_paths (scenarios)
|
||||
let scenRes = await authFetch('/api/premium/career-profile/all');
|
||||
// 2) Fetch all scenarios (career_paths)
|
||||
const scenRes = await authFetch('/api/premium/career-profile/all');
|
||||
if (!scenRes.ok) throw new Error(`Scenarios error: ${scenRes.status}`);
|
||||
let scenData = await scenRes.json();
|
||||
const scenData = await scenRes.json();
|
||||
|
||||
setFinancialProfile(finData);
|
||||
setScenarios(scenData.careerPaths || []);
|
||||
} catch (err) {
|
||||
console.error('Error loading premium data:', err);
|
||||
setError(err.message || 'Failed to load data');
|
||||
console.error('Error loading data in MultiScenarioView:', err);
|
||||
setError(err.message || 'Failed to load scenarios/financial');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
@ -36,7 +46,9 @@ export default function MultiScenarioView() {
|
||||
loadData();
|
||||
}, []);
|
||||
|
||||
// “Add Scenario” => create a brand new row in career_paths
|
||||
// ---------------------------
|
||||
// Add a new scenario
|
||||
// ---------------------------
|
||||
async function handleAddScenario() {
|
||||
try {
|
||||
const body = {
|
||||
@ -54,9 +66,9 @@ export default function MultiScenarioView() {
|
||||
if (!res.ok) throw new Error(`Add scenario error: ${res.status}`);
|
||||
const data = await res.json();
|
||||
|
||||
// Insert the new row into local state
|
||||
const newRow = {
|
||||
id: data.career_path_id,
|
||||
user_id: null,
|
||||
career_name: body.career_name,
|
||||
status: body.status,
|
||||
start_date: body.start_date,
|
||||
@ -67,11 +79,13 @@ export default function MultiScenarioView() {
|
||||
setScenarios((prev) => [...prev, newRow]);
|
||||
} catch (err) {
|
||||
console.error('Failed adding scenario:', err);
|
||||
alert('Could not add scenario');
|
||||
alert(err.message || 'Could not add scenario');
|
||||
}
|
||||
}
|
||||
|
||||
// “Clone” => create a new row in career_paths with copied fields
|
||||
// ---------------------------
|
||||
// Clone scenario
|
||||
// ---------------------------
|
||||
async function handleCloneScenario(sourceScenario) {
|
||||
try {
|
||||
const body = {
|
||||
@ -90,8 +104,9 @@ 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;
|
||||
const newRow = {
|
||||
id: data.career_path_id,
|
||||
id: newScenarioId,
|
||||
career_name: body.career_name,
|
||||
status: body.status,
|
||||
start_date: body.start_date,
|
||||
@ -102,23 +117,70 @@ export default function MultiScenarioView() {
|
||||
setScenarios((prev) => [...prev, newRow]);
|
||||
} catch (err) {
|
||||
console.error('Failed cloning scenario:', err);
|
||||
alert('Could not clone scenario');
|
||||
alert(err.message || 'Could not clone scenario');
|
||||
}
|
||||
}
|
||||
|
||||
// “Remove” => possibly remove from DB or just local
|
||||
// ---------------------------
|
||||
// Delete scenario
|
||||
// ---------------------------
|
||||
async function handleRemoveScenario(scenarioId) {
|
||||
// confirm
|
||||
const confirmDel = window.confirm(
|
||||
'Delete this scenario (and associated collegeProfile/milestones)?'
|
||||
);
|
||||
if (!confirmDel) return;
|
||||
|
||||
try {
|
||||
const res = await authFetch(`/api/premium/career-profile/${scenarioId}`, {
|
||||
method: 'DELETE'
|
||||
});
|
||||
if (!res.ok) throw new Error(`Delete scenario error: ${res.status}`);
|
||||
// remove from local
|
||||
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');
|
||||
console.error('Delete scenario error:', err);
|
||||
alert(err.message || 'Could not delete scenario');
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------
|
||||
// User clicks "Edit" in ScenarioContainer => we fetch that scenario’s collegeProfile
|
||||
// set editingScenario / editingCollegeProfile => modal
|
||||
// ---------------------------
|
||||
async function handleEditScenario(scenarioObj) {
|
||||
if (!scenarioObj?.id) return;
|
||||
try {
|
||||
// fetch the collegeProfile
|
||||
const colResp = await authFetch(`/api/premium/college-profile?careerPathId=${scenarioObj.id}`);
|
||||
let colData = {};
|
||||
if (colResp.ok) {
|
||||
colData = await colResp.json();
|
||||
}
|
||||
setEditingScenario(scenarioObj);
|
||||
setEditingCollegeProfile(colData);
|
||||
} catch (err) {
|
||||
console.error('Error loading collegeProfile for editing:', err);
|
||||
setEditingScenario(scenarioObj);
|
||||
setEditingCollegeProfile({});
|
||||
}
|
||||
}
|
||||
|
||||
// Called by <ScenarioEditModal> on close => we optionally update local scenario
|
||||
function handleModalClose(updatedScenario, updatedCollege) {
|
||||
if (updatedScenario) {
|
||||
setScenarios(prev =>
|
||||
prev.map((s) => (s.id === updatedScenario.id ? { ...s, ...updatedScenario } : s))
|
||||
);
|
||||
}
|
||||
// We might not store the updatedCollege in local state unless we want to re-simulate immediately
|
||||
// For now, do nothing or re-fetch if needed
|
||||
setEditingScenario(null);
|
||||
setEditingCollegeProfile(null);
|
||||
}
|
||||
|
||||
if (loading) return <p>Loading scenarios...</p>;
|
||||
if (error) return <p className="text-red-600">Error: {error}</p>;
|
||||
if (error) return <p style={{ color: 'red' }}>Error: {error}</p>;
|
||||
|
||||
return (
|
||||
<div className="multi-scenario-view" style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
|
||||
@ -126,15 +188,26 @@ export default function MultiScenarioView() {
|
||||
<ScenarioContainer
|
||||
key={scen.id}
|
||||
scenario={scen}
|
||||
financialProfile={financialProfile} // shared for all
|
||||
financialProfile={financialProfile}
|
||||
onClone={() => handleCloneScenario(scen)}
|
||||
onRemove={() => handleRemoveScenario(scen.id)}
|
||||
onEdit={() => handleEditScenario(scen)} // new callback
|
||||
/>
|
||||
))}
|
||||
|
||||
<div style={{ alignSelf: 'flex-start' }}>
|
||||
<button onClick={handleAddScenario}>+ Add Scenario</button>
|
||||
</div>
|
||||
|
||||
{/* The floating modal at the bottom => only if editingScenario != null */}
|
||||
{editingScenario && (
|
||||
<ScenarioEditModal
|
||||
show={true}
|
||||
scenario={editingScenario}
|
||||
collegeProfile={editingCollegeProfile}
|
||||
onClose={handleModalClose}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -2,160 +2,151 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Line } from 'react-chartjs-2';
|
||||
import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
|
||||
import ScenarioEditModal from './ScenarioEditModal.js';
|
||||
import MilestoneTimeline from './MilestoneTimeline.js';
|
||||
import AISuggestedMilestones from './AISuggestedMilestones.js';
|
||||
import authFetch from '../utils/authFetch.js';
|
||||
|
||||
export default function ScenarioContainer({
|
||||
scenario, // from career_paths row
|
||||
financialProfile, // single row, shared across user
|
||||
scenario,
|
||||
financialProfile,
|
||||
onRemove,
|
||||
onClone,
|
||||
onRemove
|
||||
onEdit // <-- new callback to open the floating modal
|
||||
}) {
|
||||
const [localScenario, setLocalScenario] = useState(scenario);
|
||||
const [collegeProfile, setCollegeProfile] = useState(null);
|
||||
|
||||
const [projectionData, setProjectionData] = useState([]);
|
||||
const [loanPaidOffMonth, setLoanPaidOffMonth] = useState(null);
|
||||
|
||||
const [editOpen, setEditOpen] = useState(false);
|
||||
// An input for sim length
|
||||
const [simulationYearsInput, setSimulationYearsInput] = useState('20');
|
||||
|
||||
// Re-sync if parent updates scenario
|
||||
useEffect(() => {
|
||||
setLocalScenario(scenario);
|
||||
}, [scenario]);
|
||||
|
||||
// 1) Fetch the college profile for this scenario
|
||||
useEffect(() => {
|
||||
if (!localScenario?.id) return;
|
||||
if (!scenario?.id) {
|
||||
setCollegeProfile(null);
|
||||
return;
|
||||
}
|
||||
async function loadCollegeProfile() {
|
||||
try {
|
||||
const res = await authFetch(`/api/premium/college-profile?careerPathId=${localScenario.id}`);
|
||||
const url = `/api/premium/college-profile?careerPathId=${scenario.id}`;
|
||||
const res = await authFetch(url);
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setCollegeProfile(data);
|
||||
} else {
|
||||
console.warn('No college profile found or error:', res.status);
|
||||
setCollegeProfile({});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Failed fetching college profile:', err);
|
||||
console.error('Error loading collegeProfile:', err);
|
||||
}
|
||||
}
|
||||
loadCollegeProfile();
|
||||
}, [localScenario]);
|
||||
}, [scenario]);
|
||||
|
||||
// 2) Whenever we have financialProfile + collegeProfile => run the simulation
|
||||
useEffect(() => {
|
||||
if (!financialProfile || !collegeProfile) return;
|
||||
if (!financialProfile || !collegeProfile || !scenario?.id) return;
|
||||
|
||||
// Merge them into the userProfile object for the simulator:
|
||||
// Merge the user’s base financial profile + scenario overwrites + college profile
|
||||
const mergedProfile = {
|
||||
currentSalary: financialProfile.current_salary || 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 (scenario-based)
|
||||
monthlyExpenses:
|
||||
scenario.planned_monthly_expenses ?? financialProfile.monthly_expenses ?? 0,
|
||||
monthlyDebtPayments:
|
||||
scenario.planned_monthly_debt_payments ?? financialProfile.monthly_debt_payments ?? 0,
|
||||
// ...
|
||||
studentLoanAmount: collegeProfile.existing_college_debt || 0,
|
||||
interestRate: collegeProfile.interest_rate || 5,
|
||||
loanTerm: collegeProfile.loan_term || 10,
|
||||
loanDeferralUntilGraduation: !!collegeProfile.loan_deferral_until_graduation,
|
||||
academicCalendar: collegeProfile.academic_calendar || 'semester',
|
||||
annualFinancialAid: collegeProfile.annual_financial_aid || 0,
|
||||
calculatedTuition: collegeProfile.tuition || 0,
|
||||
extraPayment: collegeProfile.extra_payment || 0,
|
||||
gradDate: collegeProfile.expected_graduation || null,
|
||||
programType: collegeProfile.program_type || '',
|
||||
creditHoursPerYear: collegeProfile.credit_hours_per_year || 0,
|
||||
hoursCompleted: collegeProfile.hours_completed || 0,
|
||||
programLength: collegeProfile.program_length || 0,
|
||||
inCollege:
|
||||
collegeProfile.college_enrollment_status === 'currently_enrolled' ||
|
||||
collegeProfile.college_enrollment_status === 'prospective_student',
|
||||
expectedSalary: collegeProfile.expected_salary || financialProfile.current_salary || 0,
|
||||
|
||||
// milestoneImpacts is fetched & merged in MilestoneTimeline, not here
|
||||
// ...
|
||||
simulationYears: parseInt(simulationYearsInput, 10) || 20,
|
||||
milestoneImpacts: []
|
||||
};
|
||||
|
||||
// 3) run the simulation
|
||||
const { projectionData, loanPaidOffMonth } = simulateFinancialProjection(mergedProfile);
|
||||
setProjectionData(projectionData);
|
||||
setLoanPaidOffMonth(loanPaidOffMonth);
|
||||
}, [financialProfile, collegeProfile]);
|
||||
}, [financialProfile, collegeProfile, scenario, simulationYearsInput]);
|
||||
|
||||
function handleDeleteScenario() {
|
||||
// let the parent actually do the DB deletion
|
||||
onRemove(scenario.id);
|
||||
}
|
||||
|
||||
function handleSimulationYearsBlur() {
|
||||
if (simulationYearsInput.trim() === '') {
|
||||
setSimulationYearsInput('20');
|
||||
}
|
||||
}
|
||||
|
||||
// chart data
|
||||
const labels = projectionData.map(p => p.month);
|
||||
const netSavData = projectionData.map(p => p.cumulativeNetSavings || 0);
|
||||
const retData = projectionData.map(p => p.retirementSavings || 0);
|
||||
const loanData = projectionData.map(p => p.loanBalance || 0);
|
||||
|
||||
const chartData = {
|
||||
labels,
|
||||
datasets: [
|
||||
{ label: 'Net Savings', data: netSavData, borderColor: 'blue', fill: false },
|
||||
{ label: 'Retirement', data: retData, borderColor: 'green', fill: false },
|
||||
{ label: 'Loan', data: loanData, borderColor: 'red', fill: false }
|
||||
]
|
||||
};
|
||||
|
||||
return (
|
||||
<div style={{ width: '420px', border: '1px solid #ccc', padding: '1rem' }}>
|
||||
<h3>{localScenario.career_name || 'Untitled Scenario'}</h3>
|
||||
<p>Status: {localScenario.status}</p>
|
||||
<h4>{scenario.scenario_title || scenario.career_name || 'Untitled Scenario'}</h4>
|
||||
|
||||
<Line
|
||||
data={{
|
||||
labels: projectionData.map((p) => p.month),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Net Savings',
|
||||
data: projectionData.map((p) => p.cumulativeNetSavings || 0),
|
||||
borderColor: 'blue',
|
||||
fill: false
|
||||
}
|
||||
]
|
||||
}}
|
||||
options={{ responsive: true }}
|
||||
/>
|
||||
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<strong>Loan Paid Off:</strong> {loanPaidOffMonth || 'N/A'} <br />
|
||||
<strong>Final Retirement:</strong>{' '}
|
||||
{projectionData[projectionData.length - 1]?.retirementSavings?.toFixed(0) || 0}
|
||||
<div style={{ margin: '0.5rem 0' }}>
|
||||
<label>Simulation Length (yrs): </label>
|
||||
<input
|
||||
type="text"
|
||||
style={{ width: '3rem' }}
|
||||
value={simulationYearsInput}
|
||||
onChange={e => setSimulationYearsInput(e.target.value)}
|
||||
onBlur={handleSimulationYearsBlur}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* The timeline that fetches scenario/universal milestones for display */}
|
||||
<MilestoneTimeline
|
||||
careerPathId={localScenario.id}
|
||||
authFetch={authFetch}
|
||||
activeView="Financial"
|
||||
setActiveView={() => {}}
|
||||
onMilestoneUpdated={() => {
|
||||
// might do scenario changes if you want
|
||||
}}
|
||||
/>
|
||||
|
||||
<AISuggestedMilestones
|
||||
career={localScenario.career_name}
|
||||
careerPathId={localScenario.id}
|
||||
authFetch={authFetch}
|
||||
activeView="Financial"
|
||||
projectionData={projectionData}
|
||||
/>
|
||||
<Line data={chartData} options={{ responsive: true }} />
|
||||
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<button onClick={() => setEditOpen(true)}>Edit</button>
|
||||
<button onClick={onClone} style={{ marginLeft: '0.5rem' }}>
|
||||
Clone
|
||||
</button>
|
||||
<button onClick={onRemove} style={{ marginLeft: '0.5rem', color: 'red' }}>
|
||||
Remove
|
||||
</button>
|
||||
<strong>Loan Paid Off:</strong> {loanPaidOffMonth || 'N/A'}
|
||||
{projectionData.length > 0 && (
|
||||
<>
|
||||
<br />
|
||||
<strong>Final Retirement:</strong> {projectionData[projectionData.length - 1].retirementSavings.toFixed(0)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* If you do scenario-level editing for planned fields, show scenario edit modal */}
|
||||
{editOpen && (
|
||||
<ScenarioEditModal
|
||||
show={editOpen}
|
||||
onClose={() => setEditOpen(false)}
|
||||
scenario={localScenario}
|
||||
setScenario={setLocalScenario}
|
||||
apiURL="/api"
|
||||
{scenario?.id && (
|
||||
<MilestoneTimeline
|
||||
careerPathId={scenario.id}
|
||||
authFetch={authFetch}
|
||||
activeView="Financial"
|
||||
setActiveView={() => {}}
|
||||
onMilestoneUpdated={() => {}}
|
||||
/>
|
||||
)}
|
||||
{scenario?.id && (
|
||||
<AISuggestedMilestones
|
||||
career={scenario.career_name || scenario.scenario_title || ''}
|
||||
careerPathId={scenario.id}
|
||||
authFetch={authFetch}
|
||||
activeView="Financial"
|
||||
projectionData={projectionData}
|
||||
/>
|
||||
)}
|
||||
|
||||
<div style={{ marginTop: '0.5rem' }}>
|
||||
<button onClick={() => onEdit(scenario)}>Edit</button>
|
||||
<button onClick={() => onClone(scenario)} style={{ marginLeft: '0.5rem' }}>
|
||||
Clone
|
||||
</button>
|
||||
<button onClick={handleDeleteScenario} style={{ marginLeft: '0.5rem', color: 'red' }}>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user