Fixed Clone Scenario

This commit is contained in:
Josh 2025-04-29 15:54:23 +00:00
parent a9545abc0a
commit e5a6275863
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 React, { useEffect, useState } from 'react';
import authFetch from '../utils/authFetch.js'; import authFetch from '../utils/authFetch.js';
import ScenarioContainer from './ScenarioContainer.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> * MultiScenarioView
// for each. It also has the "Add Scenario" and "Clone/Remove" logic. * -----------------
* - 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() { export default function MultiScenarioView() {
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [error, setError] = useState(null); const [error, setError] = useState(null);
@ -13,16 +19,20 @@ export default function MultiScenarioView() {
// The users single overall financial profile // The users single overall financial profile
const [financialProfile, setFinancialProfile] = useState(null); 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([]); const [scenarios, setScenarios] = useState([]);
useEffect(() => { useEffect(() => {
loadScenariosAndFinancial(); loadScenariosAndFinancial();
}, []); }, []);
/**
* Fetch users financial profile + scenario list
*/
async function loadScenariosAndFinancial() { async function loadScenariosAndFinancial() {
setLoading(true); setLoading(true);
setError(null); setError(null);
try { try {
// 1) fetch users global financialProfile // 1) fetch users global financialProfile
const finRes = await authFetch('/api/premium/financial-profile'); const finRes = await authFetch('/api/premium/financial-profile');
@ -44,104 +54,206 @@ export default function MultiScenarioView() {
} }
} }
// Add a new scenario => then reload /**
* Create a brand-new scenario with minimal defaults
*/
async function handleAddScenario() { async function handleAddScenario() {
try { try {
const body = { const body = {
// minimal fields so the scenario is valid
career_name: 'New Scenario ' + new Date().toLocaleDateString(), career_name: 'New Scenario ' + new Date().toLocaleDateString(),
status: 'planned', status: 'planned',
start_date: new Date().toISOString(), start_date: new Date().toISOString(),
college_enrollment_status: 'not_enrolled', college_enrollment_status: 'not_enrolled',
currently_working: 'no' currently_working: 'no'
}; };
const res = await authFetch('/api/premium/career-profile', { const res = await authFetch('/api/premium/career-profile', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body) body: JSON.stringify(body)
}); });
if (!res.ok) throw new Error(`Add scenario error: ${res.status}`); if (!res.ok) throw new Error(`Add scenario error: ${res.status}`);
// reload
await loadScenariosAndFinancial(); await loadScenariosAndFinancial();
} catch (err) { } catch (err) {
alert(err.message); 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 { try {
const body = { // 1) create the new scenario row
scenario_title: s.scenario_title ? s.scenario_title + ' (Copy)' : null, const scenarioPayload = {
career_name: s.career_name ? s.career_name + ' (Copy)' : 'Untitled (Copy)', scenario_title: oldScenario.scenario_title
status: s.status, ? oldScenario.scenario_title + ' (Copy)'
start_date: s.start_date, : null,
projected_end_date: s.projected_end_date, career_name: oldScenario.career_name
college_enrollment_status: s.college_enrollment_status, ? oldScenario.career_name + ' (Copy)'
currently_working: s.currently_working, : 'Untitled (Copy)',
planned_monthly_expenses: s.planned_monthly_expenses, status: oldScenario.status,
planned_monthly_debt_payments: s.planned_monthly_debt_payments, start_date: oldScenario.start_date,
planned_monthly_retirement_contribution: s.planned_monthly_retirement_contribution, projected_end_date: oldScenario.projected_end_date,
planned_monthly_emergency_contribution: s.planned_monthly_emergency_contribution, college_enrollment_status: oldScenario.college_enrollment_status,
planned_surplus_emergency_pct: s.planned_surplus_emergency_pct, currently_working: oldScenario.currently_working,
planned_surplus_retirement_pct: s.planned_surplus_retirement_pct,
planned_additional_income: s.planned_additional_income 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', { const res = await authFetch('/api/premium/career-profile', {
method: 'POST', method: 'POST',
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body) body: JSON.stringify(scenarioPayload)
}); });
if (!res.ok) throw new Error(`Clone scenario error: ${res.status}`); 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(); await loadScenariosAndFinancial();
} catch (err) { } catch (err) {
alert(err.message); 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) { async function handleRemoveScenario(id) {
const confirmDel = window.confirm('Delete this scenario?'); const confirmDel = window.confirm('Delete this scenario?');
if (!confirmDel) return; if (!confirmDel) return;
try { try {
const res = await authFetch(`/api/premium/career-profile/${id}`, { const res = await authFetch(`/api/premium/career-profile/${id}`, {
method: 'DELETE' method: 'DELETE'
}); });
if (!res.ok) throw new Error(`Delete scenario error: ${res.status}`); if (!res.ok) throw new Error(`Delete scenario error: ${res.status}`);
// reload
await loadScenariosAndFinancial(); await loadScenariosAndFinancial();
} catch (err) { } catch (err) {
alert(err.message); 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 (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 ( return (
<div style={{ display:'flex', flexWrap:'wrap', gap:'1rem' }}> <div style={{ display: 'flex', flexWrap: 'wrap', gap: '1rem' }}>
{scenarios.map(sc => ( {scenarios.map(sc => (
<ScenarioContainer <ScenarioContainer
key={sc.id} key={sc.id}
scenario={sc} // pass the scenario row scenario={sc}
financialProfile={financialProfile} financialProfile={financialProfile}
onClone={(s) => handleCloneScenario(s)} onClone={handleCloneScenario}
onRemove={(id) => handleRemoveScenario(id)} onRemove={handleRemoveScenario}
onEdit={(sc) => { 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); console.log('Edit scenario clicked:', sc);
}} }}
/> />
))} ))}
{/* Add Scenario button */}
<div style={{ alignSelf: 'flex-start' }}> <div style={{ alignSelf: 'flex-start' }}>
<button <button
onClick={handleAddScenario} onClick={handleAddScenario}
style={{ style={{
padding: '0.5rem 1rem', padding: '0.5rem 1rem',
height: 'auto', height: 'auto',
backgroundColor: '#76b900', // or whatever backgroundColor: '#76b900',
border: 'none', border: 'none',
borderRadius: '4px', borderRadius: '4px',
cursor: 'pointer' cursor: 'pointer'

Binary file not shown.