Fixed Clone Scenario
This commit is contained in:
parent
a9545abc0a
commit
e5a6275863
@ -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 user’s 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 user’s single overall financial profile
|
// The user’s 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 user’s financial profile + scenario list
|
||||||
|
*/
|
||||||
async function loadScenariosAndFinancial() {
|
async function loadScenariosAndFinancial() {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1) fetch user’s global financialProfile
|
// 1) fetch user’s global financialProfile
|
||||||
const finRes = await authFetch('/api/premium/financial-profile');
|
const finRes = await authFetch('/api/premium/financial-profile');
|
||||||
@ -44,78 +54,179 @@ 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 scenario’s 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 scenario’s 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 scenario’s college_profile => new scenario
|
||||||
|
*/
|
||||||
|
async function cloneCollegeProfile(oldScenarioId, newScenarioId) {
|
||||||
|
try {
|
||||||
|
// fetch old scenario’s 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>;
|
||||||
|
|
||||||
@ -124,24 +235,25 @@ export default function MultiScenarioView() {
|
|||||||
{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'
|
||||||
|
BIN
user_profile.db
BIN
user_profile.db
Binary file not shown.
Loading…
Reference in New Issue
Block a user