Fixed Clone Scenario

This commit is contained in:
Josh 2025-04-29 15:54:23 +00:00
parent e58507411a
commit 2a268a0c6e
2 changed files with 162 additions and 50 deletions

View File

@ -1,11 +1,17 @@
// src/components/MultiScenarioView.js
import React, { useEffect, useState } from 'react';
import authFetch from '../utils/authFetch.js';
import ScenarioContainer from './ScenarioContainer.js';
// This component loads the user's global financial profile
// plus a list of all scenarios, and renders one <ScenarioContainer>
// for each. It also has the "Add Scenario" and "Clone/Remove" logic.
/**
* MultiScenarioView
* -----------------
* - Loads the users global financialProfile
* - Loads all scenarios from `career_paths`
* - Renders a <ScenarioContainer> for each scenario
* - Handles "Add Scenario", "Clone Scenario" (including college_profile), "Remove Scenario"
*/
export default function MultiScenarioView() {
const [loading, setLoading] = useState(false);
const [error, setError] = useState(null);
@ -13,16 +19,20 @@ export default function MultiScenarioView() {
// The users single overall financial profile
const [financialProfile, setFinancialProfile] = useState(null);
// The list of scenario "headers" (career_paths)
// The list of scenario "headers" (rows from career_paths)
const [scenarios, setScenarios] = useState([]);
useEffect(() => {
loadScenariosAndFinancial();
}, []);
/**
* Fetch users financial profile + scenario list
*/
async function loadScenariosAndFinancial() {
setLoading(true);
setError(null);
try {
// 1) fetch users global financialProfile
const finRes = await authFetch('/api/premium/financial-profile');
@ -44,112 +54,214 @@ export default function MultiScenarioView() {
}
}
// Add a new scenario => then reload
/**
* Create a brand-new scenario with minimal defaults
*/
async function handleAddScenario() {
try {
const body = {
// minimal fields so the scenario is valid
career_name: 'New Scenario ' + new Date().toLocaleDateString(),
status: 'planned',
start_date: new Date().toISOString(),
college_enrollment_status: 'not_enrolled',
currently_working: 'no'
};
const res = await authFetch('/api/premium/career-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
if (!res.ok) throw new Error(`Add scenario error: ${res.status}`);
// reload
await loadScenariosAndFinancial();
} catch (err) {
alert(err.message);
}
}
// Clone a scenario => then reload
async function handleCloneScenario(s) {
/**
* CLONE a scenario: (A) create new scenario row, (B) also clone old scenarios college_profile
*/
async function handleCloneScenario(oldScenario) {
try {
const body = {
scenario_title: s.scenario_title ? s.scenario_title + ' (Copy)' : null,
career_name: s.career_name ? s.career_name + ' (Copy)' : 'Untitled (Copy)',
status: s.status,
start_date: s.start_date,
projected_end_date: s.projected_end_date,
college_enrollment_status: s.college_enrollment_status,
currently_working: s.currently_working,
planned_monthly_expenses: s.planned_monthly_expenses,
planned_monthly_debt_payments: s.planned_monthly_debt_payments,
planned_monthly_retirement_contribution: s.planned_monthly_retirement_contribution,
planned_monthly_emergency_contribution: s.planned_monthly_emergency_contribution,
planned_surplus_emergency_pct: s.planned_surplus_emergency_pct,
planned_surplus_retirement_pct: s.planned_surplus_retirement_pct,
planned_additional_income: s.planned_additional_income
// 1) create the new scenario row
const scenarioPayload = {
scenario_title: oldScenario.scenario_title
? oldScenario.scenario_title + ' (Copy)'
: null,
career_name: oldScenario.career_name
? oldScenario.career_name + ' (Copy)'
: 'Untitled (Copy)',
status: oldScenario.status,
start_date: oldScenario.start_date,
projected_end_date: oldScenario.projected_end_date,
college_enrollment_status: oldScenario.college_enrollment_status,
currently_working: oldScenario.currently_working,
planned_monthly_expenses: oldScenario.planned_monthly_expenses,
planned_monthly_debt_payments: oldScenario.planned_monthly_debt_payments,
planned_monthly_retirement_contribution:
oldScenario.planned_monthly_retirement_contribution,
planned_monthly_emergency_contribution:
oldScenario.planned_monthly_emergency_contribution,
planned_surplus_emergency_pct: oldScenario.planned_surplus_emergency_pct,
planned_surplus_retirement_pct: oldScenario.planned_surplus_retirement_pct,
planned_additional_income: oldScenario.planned_additional_income
};
const res = await authFetch('/api/premium/career-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
body: JSON.stringify(scenarioPayload)
});
if (!res.ok) throw new Error(`Clone scenario error: ${res.status}`);
// parse the newly created scenario_id
const newScenarioData = await res.json();
const newScenarioId = newScenarioData.career_path_id;
// 2) Clone the old scenarios college_profile => new scenario
await cloneCollegeProfile(oldScenario.id, newScenarioId);
// 3) reload
await loadScenariosAndFinancial();
} catch (err) {
alert(err.message);
}
}
// Remove => reload
/**
* Helper to clone old scenarios college_profile => new scenario
*/
async function cloneCollegeProfile(oldScenarioId, newScenarioId) {
try {
// fetch old scenarios college_profile
const getRes = await authFetch(
`/api/premium/college-profile?careerPathId=${oldScenarioId}`
);
if (!getRes.ok) {
console.warn(
'Could not fetch old college profile for scenarioId=' + oldScenarioId
);
return;
}
let oldCollegeData = await getRes.json();
if (Array.isArray(oldCollegeData)) {
oldCollegeData = oldCollegeData[0] || null;
}
if (!oldCollegeData || !oldCollegeData.id) {
// no old college profile => nothing to clone
return;
}
// build new payload
const clonePayload = {
career_path_id: newScenarioId,
selected_school: oldCollegeData.selected_school,
selected_program: oldCollegeData.selected_program,
program_type: oldCollegeData.program_type,
academic_calendar: oldCollegeData.academic_calendar,
is_in_state: oldCollegeData.is_in_state,
is_in_district: oldCollegeData.is_in_district,
is_online: oldCollegeData.is_online,
college_enrollment_status: oldCollegeData.college_enrollment_status,
annual_financial_aid: oldCollegeData.annual_financial_aid,
existing_college_debt: oldCollegeData.existing_college_debt,
tuition_paid: oldCollegeData.tuition_paid,
tuition: oldCollegeData.tuition,
loan_deferral_until_graduation: oldCollegeData.loan_deferral_until_graduation,
loan_term: oldCollegeData.loan_term,
interest_rate: oldCollegeData.interest_rate,
extra_payment: oldCollegeData.extra_payment,
credit_hours_per_year: oldCollegeData.credit_hours_per_year,
hours_completed: oldCollegeData.hours_completed,
program_length: oldCollegeData.program_length,
credit_hours_required: oldCollegeData.credit_hours_required,
expected_graduation: oldCollegeData.expected_graduation,
expected_salary: oldCollegeData.expected_salary
};
// insert new row in college_profiles
const postRes = await authFetch('/api/premium/college-profile', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(clonePayload)
});
if (!postRes.ok) {
console.warn(
'Could not clone old collegeProfile => new scenario',
postRes.status
);
}
} catch (err) {
console.error('Error cloning college profile:', err);
}
}
/**
* Remove scenario => server also deletes its college_profile => reload
*/
async function handleRemoveScenario(id) {
const confirmDel = window.confirm('Delete this scenario?');
if (!confirmDel) return;
try {
const res = await authFetch(`/api/premium/career-profile/${id}`, {
method: 'DELETE'
});
if (!res.ok) throw new Error(`Delete scenario error: ${res.status}`);
// reload
await loadScenariosAndFinancial();
} catch (err) {
alert(err.message);
}
}
// If user wants to "edit" a scenario, we'll pass it down to the container's "onEdit"
// or you can open a modal at this level. For now, we rely on the ScenarioContainer
// "onEdit" prop if needed.
if (loading) return <p>Loading scenarios...</p>;
if (error) return <p style={{ color:'red' }}>{error}</p>;
if (error) return <p style={{ color: 'red' }}>{error}</p>;
return (
<div style={{ display:'flex', flexWrap:'wrap', gap:'1rem' }}>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
{scenarios.map(sc => (
<ScenarioContainer
key={sc.id}
scenario={sc} // pass the scenario row
scenario={sc}
financialProfile={financialProfile}
onClone={(s) => handleCloneScenario(s)}
onRemove={(id) => handleRemoveScenario(id)}
onClone={handleCloneScenario}
onRemove={handleRemoveScenario}
onEdit={(sc) => {
// Example: open an edit modal or navigate to a scenario editor.
// Example: open an edit modal or navigate to a scenario editor
console.log('Edit scenario clicked:', sc);
}}
/>
))}
<div style={{ alignSelf: 'flex-start' }}>
<button
onClick={handleAddScenario}
style={{
padding: '0.5rem 1rem',
height: 'auto',
backgroundColor: '#76b900', // or whatever
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
+ Add Scenario
</button>
</div>
{/* Add Scenario button */}
<div style={{ alignSelf: 'flex-start' }}>
<button
onClick={handleAddScenario}
style={{
padding: '0.5rem 1rem',
height: 'auto',
backgroundColor: '#76b900',
border: 'none',
borderRadius: '4px',
cursor: 'pointer'
}}
>
+ Add Scenario
</button>
</div>
</div>
);
}

Binary file not shown.