From e5a62758631bedf695e3bc2ca13756e960b7754d Mon Sep 17 00:00:00 2001 From: Josh Date: Tue, 29 Apr 2025 15:54:23 +0000 Subject: [PATCH] Fixed Clone Scenario --- src/components/MultiScenarioView.js | 212 +++++++++++++++++++++------- user_profile.db | Bin 106496 -> 106496 bytes 2 files changed, 162 insertions(+), 50 deletions(-) diff --git a/src/components/MultiScenarioView.js b/src/components/MultiScenarioView.js index 83f33ef..84c74c2 100644 --- a/src/components/MultiScenarioView.js +++ b/src/components/MultiScenarioView.js @@ -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 -// for each. It also has the "Add Scenario" and "Clone/Remove" logic. - +/** + * MultiScenarioView + * ----------------- + * - Loads the user’s global financialProfile + * - Loads all scenarios from `career_paths` + * - Renders a 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 user’s 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 user’s financial profile + scenario list + */ async function loadScenariosAndFinancial() { setLoading(true); setError(null); + try { // 1) fetch user’s 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 scenario’s 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 scenario’s college_profile => new scenario + await cloneCollegeProfile(oldScenario.id, newScenarioId); + + // 3) reload await loadScenariosAndFinancial(); } catch (err) { 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) { 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

Loading scenarios...

; - if (error) return

{error}

; + if (error) return

{error}

; return ( -
+
{scenarios.map(sc => ( 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); }} /> ))} -
- -
+ {/* Add Scenario button */} +
+ +
); } diff --git a/user_profile.db b/user_profile.db index ecc73c3ca017f57d98962424eb9f3692846564a2..f9ca5b5c402d8ff4f01686e68f781d0c2b979daa 100644 GIT binary patch delta 1429 zcma)+O=uKJ6vw-IV7~0`Is{>tF>xjgm_RC4S65Y6kBr0(IVfrt$&e+uJE`gqm0c!5 z-NS+~84uosNFalIR)VJxMt9hYh#uUFCohBEqDNWxxGMZf)Q&6ITK;K#?XQM7 zLA*JeRT!x_WRi2bKrmql)A*MIdgo!I4zfBdX|M$Eb&TcXu$%;-GuwPJH44+~8mwDS ztzq+)@z$suxu5?tKcD+0cQku5^EyM)>szhmbhGs(%-3qeLwZT8RwETj%G?0im|;Xk z9HSsILY^PT?FrY9aL9rP3(O>nIJOa`QXwxQ68V?}0r!XEvTo(AY=OD)u5>YqaJnBhgt1t{A5t7g) zOi3SO*GJ6Z@g3?rNI8ClTtbBB5Qht@_TX;(sBT_gW4{&3tU^S^b|ziy9!A;MJFj2b zV{tJyHMj|T9pjG;IG&^!`iWF6*PfaH<;Ngj)UESfqX?!f-kR58O+SvzQnsMgs?}UA z3443xB$${Z*oo^D*~oKjg{Tx1#jfN?az`qPvBLyDl?O> z9ETC&0t-Y5@mmf!K%Ui-SfWoplG!yv+nWH?8MuA7*w{Xt`Mx_QNv!8%C;)pJ?7?2w zz+eM5lcZ{p_#|Zg|DpM_W?ivP!%b_|`W5!9qwo%-*0%YCt*yIP%}Zmc z8c5PFuzxX$cdhP+3*ZYqo$CZ(YkUoUH&eGYeZ3Bjol~J3gf8}xQa(eJyBNvP zCWxp=ISfn1Wt1CDV#&4H=9%_J9h@2=UU6>_{o{*$sr&gmkO3(jKJR80!E;#npSjk3 czXXcKo&M;-&91Q;*J(Tn^G<`jx(v#H1MGBcWdHyG delta 852 zcma)4O-K}B7@lvooY{3|K1+mh{h5s|75ujO=KE&nCpK1uZlMPUL8NuY*$u%g%2R~8 z9->>y5KFg+N;-*bHFOE_QinR3u+XiaLm?4S5R%Z0i)fyFPcJV#@B2K@`^>F2%&j&o zB*DgFTR+%X8GBUVf%GIMJr#deg|ScKNga=rG23jj{mC42`dBW<*NgH0&ZPa!WV?Uw z%DEKPElM&b#v`htYDl3-HHI*;G;9$gV;V;F@l*lu04_3c5vHobwN4l zz89pQfrQKo4+B94d1=_t9ItG%&}KV(#1x-o>|8Ex8`w15ELD-NI4YvF~v^X1<&|L-lQvY zXF*5hK?X>Xctp%0tXhh0VJ(B{z9$@*C&GKXCI*56mSE3eJlSzMIx9yDL~)amE=%X( zva}$*gf*!J-iB2AC~m^Wph*F5#bEOyIKhTN+GeC}@oOLqg72al@pre2!U99eBUG7eP6UU=&Tde>Bg7Hk-BQgY8Rz12F)<1l$-{gT6AE3_f24 Wvb-C#S|BV$9vy~jJ&Er|LHZwnOxyYZ