dev1/src/components/ScenarioContainer.js

1122 lines
34 KiB
JavaScript

import React, { useState, useEffect, useCallback } from 'react';
import { Line } from 'react-chartjs-2';
import { Chart as ChartJS } from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation';
import { Button } from './ui/button.js';
import authFetch from '../utils/authFetch.js';
import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
import AISuggestedMilestones from './AISuggestedMilestones.js';
import ScenarioEditModal from './ScenarioEditModal.js';
// Register the annotation plugin globally
ChartJS.register(annotationPlugin);
export default function ScenarioContainer({
scenario,
financialProfile,
onRemove,
onClone,
onEdit
}) {
/*************************************************************
* 1) Scenario Dropdown
*************************************************************/
const [allScenarios, setAllScenarios] = useState([]);
const [localScenario, setLocalScenario] = useState(scenario || null);
useEffect(() => {
async function loadScenarios() {
try {
const res = await authFetch('/api/premium/career-profile/all');
if (!res.ok) {
throw new Error(`Failed fetching scenario list: ${res.status}`);
}
const data = await res.json();
setAllScenarios(data.careerPaths || []);
} catch (err) {
console.error('Error loading allScenarios for dropdown:', err);
}
}
loadScenarios();
}, []);
useEffect(() => {
setLocalScenario(scenario || null);
}, [scenario]);
function handleScenarioSelect(e) {
const chosenId = e.target.value;
const found = allScenarios.find((s) => s.id === chosenId);
setLocalScenario(found || null);
}
/*************************************************************
* 2) College Profile + Milestones
*************************************************************/
const [collegeProfile, setCollegeProfile] = useState(null);
const [milestones, setMilestones] = useState([]);
const [impactsByMilestone, setImpactsByMilestone] = useState({});
const [showEditModal, setShowEditModal] = useState(false);
const [editingScenarioData, setEditingScenarioData] = useState({
scenario: null,
collegeProfile: null
});
// load the college profile
useEffect(() => {
if (!localScenario?.id) {
setCollegeProfile(null);
return;
}
async function loadCollegeProfile() {
try {
const url = `/api/premium/college-profile?careerPathId=${localScenario.id}`;
const res = await authFetch(url);
if (res.ok) {
const data = await res.json();
setCollegeProfile(Array.isArray(data) ? data[0] || {} : data);
} else {
setCollegeProfile({});
}
} catch (err) {
console.error('Error loading collegeProfile:', err);
setCollegeProfile({});
}
}
loadCollegeProfile();
}, [localScenario]);
// load milestones (and each milestone's impacts)
const fetchMilestones = useCallback(async () => {
if (!localScenario?.id) {
setMilestones([]);
setImpactsByMilestone({});
return;
}
try {
const res = await authFetch(
`/api/premium/milestones?careerPathId=${localScenario.id}`
);
if (!res.ok) {
console.error('Failed fetching milestones. Status:', res.status);
return;
}
const data = await res.json();
const mils = data.milestones || [];
setMilestones(mils);
// For each milestone => fetch impacts
const impactsData = {};
for (const m of mils) {
const iRes = await authFetch(
`/api/premium/milestone-impacts?milestone_id=${m.id}`
);
if (iRes.ok) {
const iData = await iRes.json();
impactsData[m.id] = iData.impacts || [];
} else {
impactsData[m.id] = [];
}
}
setImpactsByMilestone(impactsData);
} catch (err) {
console.error('Error fetching milestones:', err);
}
}, [localScenario?.id]);
useEffect(() => {
fetchMilestones();
}, [fetchMilestones]);
/*************************************************************
* 3) Run Simulation
*************************************************************/
const [projectionData, setProjectionData] = useState([]);
const [loanPaidOffMonth, setLoanPaidOffMonth] = useState(null);
const [simulationYearsInput, setSimulationYearsInput] = useState('20');
useEffect(() => {
if (!financialProfile || !localScenario?.id || !collegeProfile) return;
// gather all milestoneImpacts
let allImpacts = [];
Object.keys(impactsByMilestone).forEach((mId) => {
allImpacts = allImpacts.concat(impactsByMilestone[mId]);
});
const simYears = parseInt(simulationYearsInput, 10) || 20;
// Merge scenario + user financial + college + milestone
const mergedProfile = {
currentSalary: financialProfile.current_salary || 0,
monthlyExpenses:
localScenario.planned_monthly_expenses ??
financialProfile.monthly_expenses ??
0,
monthlyDebtPayments:
localScenario.planned_monthly_debt_payments ??
financialProfile.monthly_debt_payments ??
0,
retirementSavings: financialProfile.retirement_savings ?? 0,
emergencySavings: financialProfile.emergency_fund ?? 0,
monthlyRetirementContribution:
localScenario.planned_monthly_retirement_contribution ??
financialProfile.retirement_contribution ??
0,
monthlyEmergencyContribution:
localScenario.planned_monthly_emergency_contribution ??
financialProfile.emergency_contribution ??
0,
surplusEmergencyAllocation:
localScenario.planned_surplus_emergency_pct ??
financialProfile.extra_cash_emergency_pct ??
50,
surplusRetirementAllocation:
localScenario.planned_surplus_retirement_pct ??
financialProfile.extra_cash_retirement_pct ??
50,
additionalIncome:
localScenario.planned_additional_income ??
financialProfile.additional_income ??
0,
// college
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 || 'monthly',
annualFinancialAid: collegeProfile.annual_financial_aid || 0,
calculatedTuition: collegeProfile.tuition || 0,
extraPayment: collegeProfile.extra_payment || 0,
inCollege:
collegeProfile.college_enrollment_status === 'currently_enrolled' ||
collegeProfile.college_enrollment_status === 'prospective_student',
gradDate: collegeProfile.expected_graduation || null,
programType: collegeProfile.program_type || null,
creditHoursPerYear: collegeProfile.credit_hours_per_year || 0,
hoursCompleted: collegeProfile.hours_completed || 0,
programLength: collegeProfile.program_length || 0,
expectedSalary:
collegeProfile.expected_salary || financialProfile.current_salary || 0,
// scenario horizon
startDate: localScenario.start_date || new Date().toISOString(),
simulationYears: simYears,
milestoneImpacts: allImpacts
};
const { projectionData, loanPaidOffMonth } =
simulateFinancialProjection(mergedProfile);
let cumulative = mergedProfile.emergencySavings || 0;
const finalData = projectionData.map((monthRow) => {
cumulative += monthRow.netSavings || 0;
return { ...monthRow, cumulativeNetSavings: cumulative };
});
setProjectionData(finalData);
setLoanPaidOffMonth(loanPaidOffMonth);
}, [
financialProfile,
localScenario,
collegeProfile,
impactsByMilestone,
simulationYearsInput
]);
function handleSimulationYearsChange(e) {
setSimulationYearsInput(e.target.value);
}
function handleSimulationYearsBlur() {
if (!simulationYearsInput.trim()) {
setSimulationYearsInput('20');
}
}
/*************************************************************
* 4) Chart + Annotations
*************************************************************/
const chartLabels = projectionData.map((p) => p.month);
const netSavingsData = projectionData.map((p) => p.cumulativeNetSavings || 0);
const retData = projectionData.map((p) => p.retirementSavings || 0);
const loanData = projectionData.map((p) => p.loanBalance || 0);
const milestoneAnnotations = milestones
.map((m) => {
if (!m.date) return null;
const d = new Date(m.date);
if (isNaN(d)) return null;
const year = d.getUTCFullYear();
const month = String(d.getUTCMonth() + 1).padStart(2, '0');
const short = `${year}-${month}`;
if (!chartLabels.includes(short)) return null;
return {
type: 'line',
xMin: short,
xMax: short,
borderColor: 'orange',
borderWidth: 2,
label: {
display: true,
content: m.title || 'Milestone',
color: 'orange',
position: 'end'
},
milestoneObj: m,
onClick: () => handleEditMilestone(m)
};
})
.filter(Boolean);
const chartData = {
labels: chartLabels,
datasets: [
{
label: 'Net Savings',
data: netSavingsData,
borderColor: 'blue',
fill: false
},
{
label: 'Retirement',
data: retData,
borderColor: 'green',
fill: false
},
{
label: 'Loan',
data: loanData,
borderColor: 'red',
fill: false
}
]
};
const chartOptions = {
responsive: true,
scales: {
x: { type: 'category' },
y: { title: { display: true, text: 'Amount ($)' } }
},
plugins: {
annotation: {
annotations: milestoneAnnotations
},
tooltip: {
callbacks: {
label: (context) =>
`${context.dataset.label}: ${context.formattedValue}`
}
}
}
};
/*************************************************************
* 5) MILESTONE CRUD
*************************************************************/
const [showForm, setShowForm] = useState(false);
const [editingMilestone, setEditingMilestone] = useState(null);
const [newMilestone, setNewMilestone] = useState({
title: '',
description: '',
date: '',
progress: 0,
newSalary: '',
impacts: [],
isUniversal: 0
});
const [impactsToDelete, setImpactsToDelete] = useState([]);
// tasks
const [showTaskForm, setShowTaskForm] = useState(null);
const [editingTask, setEditingTask] = useState({
id: null,
title: '',
description: '',
due_date: ''
});
// copy wizard
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
function handleNewMilestone() {
setEditingMilestone(null);
setNewMilestone({
title: '',
description: '',
date: '',
progress: 0,
newSalary: '',
impacts: [],
isUniversal: 0
});
setImpactsToDelete([]);
setShowForm(true);
}
async function handleEditMilestone(m) {
if (!localScenario?.id) return;
setEditingMilestone(m);
setImpactsToDelete([]);
try {
const impRes = await authFetch(
`/api/premium/milestone-impacts?milestone_id=${m.id}`
);
if (impRes.ok) {
const data = await impRes.json();
const fetchedImpacts = data.impacts || [];
setNewMilestone({
title: m.title || '',
description: m.description || '',
date: m.date || '',
progress: m.progress || 0,
newSalary: m.new_salary || '',
impacts: fetchedImpacts.map((imp) => ({
id: imp.id,
impact_type: imp.impact_type || 'ONE_TIME',
direction: imp.direction || 'subtract',
amount: imp.amount || 0,
start_date: imp.start_date || '',
end_date: imp.end_date || ''
})),
isUniversal: m.is_universal ? 1 : 0
});
setShowForm(true);
}
} catch (err) {
console.error('Error loading milestone impacts:', err);
}
}
function addNewImpact() {
setNewMilestone((prev) => ({
...prev,
impacts: [
...prev.impacts,
{
impact_type: 'ONE_TIME',
direction: 'subtract',
amount: 0,
start_date: '',
end_date: ''
}
]
}));
}
function removeImpact(idx) {
setNewMilestone((prev) => {
const copy = [...prev.impacts];
const removed = copy[idx];
if (removed && removed.id) {
setImpactsToDelete((old) => [...old, removed.id]);
}
copy.splice(idx, 1);
return { ...prev, impacts: copy };
});
}
function updateImpact(idx, field, value) {
setNewMilestone((prev) => {
const copy = [...prev.impacts];
copy[idx] = { ...copy[idx], [field]: value };
return { ...prev, impacts: copy };
});
}
async function saveMilestone() {
if (!localScenario?.id) return;
const url = editingMilestone
? `/api/premium/milestones/${editingMilestone.id}`
: `/api/premium/milestone`;
const method = editingMilestone ? 'PUT' : 'POST';
const payload = {
milestone_type: 'Financial',
title: newMilestone.title,
description: newMilestone.description,
date: newMilestone.date,
career_path_id: localScenario.id,
progress: newMilestone.progress,
status: newMilestone.progress >= 100 ? 'completed' : 'planned',
new_salary: newMilestone.newSalary
? parseFloat(newMilestone.newSalary)
: null,
is_universal: newMilestone.isUniversal || 0
};
try {
const res = await authFetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
const errData = await res.json();
alert(errData.error || 'Error saving milestone');
return;
}
const savedMilestone = await res.json();
// handle impacts
for (const id of impactsToDelete) {
await authFetch(`/api/premium/milestone-impacts/${id}`, {
method: 'DELETE'
});
}
for (let i = 0; i < newMilestone.impacts.length; i++) {
const imp = newMilestone.impacts[i];
const impPayload = {
milestone_id: savedMilestone.id,
impact_type: imp.impact_type,
direction: imp.direction,
amount: parseFloat(imp.amount) || 0,
start_date: imp.start_date || null,
end_date: imp.end_date || null
};
if (imp.id) {
await authFetch(`/api/premium/milestone-impacts/${imp.id}`, {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(impPayload)
});
} else {
await authFetch('/api/premium/milestone-impacts', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(impPayload)
});
}
}
// re-fetch
await fetchMilestones();
// reset form
setShowForm(false);
setEditingMilestone(null);
setNewMilestone({
title: '',
description: '',
date: '',
progress: 0,
newSalary: '',
impacts: [],
isUniversal: 0
});
setImpactsToDelete([]);
} catch (err) {
console.error('Error saving milestone:', err);
alert('Failed to save milestone');
}
}
async function handleDeleteMilestone(m) {
if (m.is_universal === 1) {
const userChoice = window.confirm(
'Universal milestone. OK => remove from ALL scenarios, or Cancel => just remove from this scenario.'
);
if (userChoice) {
try {
await authFetch(`/api/premium/milestones/${m.id}/all`, {
method: 'DELETE'
});
} catch (err) {
console.error('Error removing universal milestone from all:', err);
}
} else {
await deleteSingleMilestone(m);
}
} else {
await deleteSingleMilestone(m);
}
await fetchMilestones();
}
async function deleteSingleMilestone(m) {
try {
await authFetch(`/api/premium/milestones/${m.id}`, { method: 'DELETE' });
} catch (err) {
console.error('Error removing milestone:', err);
}
}
/*************************************************************
* 6) TASK CRUD
*************************************************************/
// handle both new and existing tasks
function handleAddTask(milestoneId) {
setShowTaskForm(milestoneId);
setEditingTask({
id: null,
title: '',
description: '',
due_date: ''
});
}
function handleEditTask(milestoneId, task) {
setShowTaskForm(milestoneId);
setEditingTask({
id: task.id,
title: task.title,
description: task.description || '',
due_date: task.due_date || ''
});
}
async function saveTask(milestoneId) {
if (!editingTask.title.trim()) {
alert('Task needs a title');
return;
}
const payload = {
milestone_id: milestoneId,
title: editingTask.title,
description: editingTask.description,
due_date: editingTask.due_date
};
// If we have editingTask.id => PUT, else => POST
try {
let url = '/api/premium/tasks';
let method = 'POST';
if (editingTask.id) {
url = `/api/premium/tasks/${editingTask.id}`;
method = 'PUT';
}
const res = await authFetch(url, {
method,
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
if (!res.ok) {
alert('Failed to save task');
return;
}
await fetchMilestones();
setShowTaskForm(null);
setEditingTask({
id: null,
title: '',
description: '',
due_date: ''
});
} catch (err) {
console.error('Error saving task:', err);
}
}
async function deleteTask(taskId) {
if (!taskId) return;
try {
const res = await authFetch(`/api/premium/tasks/${taskId}`, {
method: 'DELETE'
});
if (!res.ok) {
alert('Failed to delete task');
return;
}
await fetchMilestones();
} catch (err) {
console.error('Error deleting task:', err);
}
}
// scenario-level editing
function handleEditScenario() {
if (!localScenario) return;
setEditingScenarioData({
scenario: localScenario,
collegeProfile
});
setShowEditModal(true);
}
function handleDeleteScenario() {
if (localScenario) onRemove(localScenario.id);
}
function handleCloneScenario() {
if (localScenario) onClone(localScenario);
}
/*************************************************************
* 7) COPY WIZARD
*************************************************************/
function CopyMilestoneWizard({ milestone, scenarios, onClose }) {
const [selectedScenarios, setSelectedScenarios] = useState([]);
if (!milestone) return null;
function toggleScenario(id) {
setSelectedScenarios((prev) =>
prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id]
);
}
async function handleCopy() {
try {
const res = await authFetch('/api/premium/milestone/copy', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
milestoneId: milestone.id,
scenarioIds: selectedScenarios
})
});
if (!res.ok) throw new Error('Failed to copy milestone');
onClose();
window.location.reload();
} catch (err) {
console.error('Error copying milestone:', err);
}
}
return (
<div
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
background: 'rgba(0,0,0,0.4)',
zIndex: 9999
}}
>
<div
style={{
background: '#fff',
width: '400px',
padding: '1rem',
margin: '100px auto',
borderRadius: '4px'
}}
>
<h4>Copy Milestone to Other Scenarios</h4>
<p>
Milestone: <strong>{milestone.title}</strong>
</p>
{scenarios.map((s) => (
<div key={s.id}>
<label>
<input
type="checkbox"
checked={selectedScenarios.includes(s.id)}
onChange={() => toggleScenario(s.id)}
/>
{s.scenario_title || s.career_name || '(untitled)'}
</label>
</div>
))}
<div style={{ marginTop: '1rem' }}>
<Button onClick={onClose} style={{ marginRight: '0.5rem' }}>
Cancel
</Button>
<Button onClick={handleCopy}>Copy</Button>
</div>
</div>
</div>
);
}
/*************************************************************
* 8) RENDER
*************************************************************/
return (
<div style={{ width: '420px', border: '1px solid #ccc', padding: '1rem' }}>
{/* scenario dropdown */}
<select
style={{ marginBottom: '0.5rem', width: '100%' }}
value={localScenario?.id || ''}
onChange={handleScenarioSelect}
>
<option value="">-- Select a Scenario --</option>
{allScenarios.map((sc) => (
<option key={sc.id} value={sc.id}>
{sc.scenario_title || sc.career_name || 'Untitled'}
</option>
))}
</select>
{localScenario && (
<>
<h4>{localScenario.scenario_title || localScenario.career_name}</h4>
<p>
Status: {localScenario.status} <br />
Start: {localScenario.start_date} <br />
End: {localScenario.projected_end_date}
</p>
<div style={{ margin: '0.5rem 0' }}>
<label>Simulation Length (years): </label>
<input
type="text"
style={{ width: '3rem' }}
value={simulationYearsInput}
onChange={handleSimulationYearsChange}
onBlur={handleSimulationYearsBlur}
/>
</div>
{/* The line chart */}
<Line data={chartData} options={chartOptions} />
{projectionData.length > 0 && (
<div style={{ marginTop: '0.5rem' }}>
<strong>Loan Paid Off:</strong> {loanPaidOffMonth || 'N/A'} <br />
<strong>Final Retirement:</strong>{' '}
{Math.round(
projectionData[projectionData.length - 1].retirementSavings
)}
</div>
)}
<Button onClick={handleNewMilestone} style={{ marginTop: '0.5rem' }}>
+ New Milestone
</Button>
<AISuggestedMilestones
career={
localScenario.career_name || localScenario.scenario_title || ''
}
careerPathId={localScenario.id}
authFetch={authFetch}
activeView="Financial"
projectionData={projectionData}
/>
<div style={{ marginTop: '0.5rem' }}>
<Button onClick={handleEditScenario}>Edit</Button>
<Button onClick={handleCloneScenario} style={{ marginLeft: '0.5rem' }}>
Clone
</Button>
<Button
onClick={handleDeleteScenario}
style={{ marginLeft: '0.5rem', background: 'red', color: 'black' }}
>
Delete
</Button>
</div>
{/* The milestone form */}
{showForm && (
<div className="form border p-2 my-2">
<h4>
{editingMilestone ? 'Edit Milestone' : 'New Milestone'}
</h4>
<input
type="text"
placeholder="Title"
value={newMilestone.title}
onChange={(e) =>
setNewMilestone({ ...newMilestone, title: e.target.value })
}
/>
<input
type="text"
placeholder="Description"
value={newMilestone.description}
onChange={(e) =>
setNewMilestone({
...newMilestone,
description: e.target.value
})
}
/>
<input
type="date"
placeholder="Milestone Date"
value={newMilestone.date}
onChange={(e) =>
setNewMilestone({ ...newMilestone, date: e.target.value })
}
/>
<input
type="number"
placeholder="Progress (%)"
value={
newMilestone.progress === 0 ? '' : newMilestone.progress
}
onChange={(e) =>
setNewMilestone((prev) => ({
...prev,
progress: parseInt(e.target.value || '0', 10)
}))
}
/>
{/* Impacts sub-form */}
<div
style={{
border: '1px solid #ccc',
padding: '1rem',
marginTop: '1rem'
}}
>
<h5>Financial Impacts</h5>
{newMilestone.impacts.map((imp, idx) => (
<div
key={idx}
style={{
border: '1px solid #bbb',
margin: '0.5rem',
padding: '0.5rem'
}}
>
{imp.id && (
<p style={{ fontSize: '0.8rem' }}>ID: {imp.id}</p>
)}
<div>
<label>Type: </label>
<select
value={imp.impact_type}
onChange={(e) =>
updateImpact(idx, 'impact_type', e.target.value)
}
>
<option value="ONE_TIME">One-Time</option>
<option value="MONTHLY">Monthly</option>
</select>
</div>
<div>
<label>Direction: </label>
<select
value={imp.direction}
onChange={(e) =>
updateImpact(idx, 'direction', e.target.value)
}
>
<option value="add">Add (Income)</option>
<option value="subtract">Subtract (Expense)</option>
</select>
</div>
<div>
<label>Amount: </label>
<input
type="number"
value={imp.amount}
onChange={(e) =>
updateImpact(idx, 'amount', e.target.value)
}
/>
</div>
<div>
<label>Start Date: </label>
<input
type="date"
value={imp.start_date || ''}
onChange={(e) =>
updateImpact(idx, 'start_date', e.target.value)
}
/>
</div>
{imp.impact_type === 'MONTHLY' && (
<div>
<label>End Date (blank indefinite): </label>
<input
type="date"
value={imp.end_date || ''}
onChange={(e) =>
updateImpact(idx, 'end_date', e.target.value)
}
/>
</div>
)}
<Button
style={{ marginLeft: '0.5rem', color: 'red' }}
onClick={() => removeImpact(idx)}
>
Remove
</Button>
</div>
))}
<Button onClick={addNewImpact}>+ Add Impact</Button>
</div>
<div style={{ marginTop: '1rem' }}>
<Button onClick={saveMilestone}>
{editingMilestone ? 'Update' : 'Add'} Milestone
</Button>
<Button
style={{ marginLeft: '0.5rem' }}
onClick={() => {
setShowForm(false);
setEditingMilestone(null);
setNewMilestone({
title: '',
description: '',
date: '',
progress: 0,
newSalary: '',
impacts: [],
isUniversal: 0
});
setImpactsToDelete([]);
}}
>
Cancel
</Button>
</div>
</div>
)}
{/* Render existing milestones */}
{milestones.map((m) => {
const tasks = m.tasks || [];
return (
<div
key={m.id}
style={{
border: '1px solid #ccc',
marginTop: '1rem',
padding: '0.5rem'
}}
>
<h5>{m.title}</h5>
{m.description && <p>{m.description}</p>}
<p>
<strong>Date:</strong> {m.date} <strong>Progress:</strong> {m.progress}%
</p>
{/* tasks list */}
{tasks.length > 0 && (
<ul>
{tasks.map((t) => (
<li key={t.id}>
<strong>{t.title}</strong>
{t.description ? ` - ${t.description}` : ''}
{t.due_date ? ` (Due: ${t.due_date})` : ''}{' '}
<Button
style={{ marginLeft: '0.5rem' }}
onClick={() => handleEditTask(m.id, t)}
>
Edit
</Button>
<Button
style={{ marginLeft: '0.5rem', color: 'red' }}
onClick={() => deleteTask(t.id)}
>
Delete
</Button>
</li>
))}
</ul>
)}
<Button
onClick={() => handleAddTask(m.id)}
style={{ marginRight: '0.5rem' }}
>
+ Task
</Button>
<Button onClick={() => handleEditMilestone(m)}>Edit</Button>
<Button
style={{ marginLeft: '0.5rem' }}
onClick={() => setCopyWizardMilestone(m)}
>
Copy
</Button>
<Button
style={{ marginLeft: '0.5rem', background: 'red', color: 'black' }}
onClick={() => handleDeleteMilestone(m)}
>
Delete
</Button>
{/* The "Add/Edit Task" form if showTaskForm === this milestone */}
{showTaskForm === m.id && (
<div
style={{
marginTop: '0.5rem',
border: '1px solid #aaa',
padding: '0.5rem'
}}
>
<h5>{editingTask.id ? 'Edit Task' : 'New Task'}</h5>
<input
type="text"
placeholder="Task Title"
value={editingTask.title}
onChange={(e) =>
setEditingTask({ ...editingTask, title: e.target.value })
}
/>
<input
type="text"
placeholder="Task Description"
value={editingTask.description}
onChange={(e) =>
setEditingTask({
...editingTask,
description: e.target.value
})
}
/>
<input
type="date"
value={editingTask.due_date}
onChange={(e) =>
setEditingTask({
...editingTask,
due_date: e.target.value
})
}
/>
<Button onClick={() => saveTask(m.id)}>
{editingTask.id ? 'Update' : 'Add'} Task
</Button>
<Button
style={{ marginLeft: '0.5rem' }}
onClick={() => {
setShowTaskForm(null);
setEditingTask({
id: null,
title: '',
description: '',
due_date: ''
});
}}
>
Cancel
</Button>
</div>
)}
</div>
);
})}
{/* Scenario edit modal */}
<ScenarioEditModal
show={showEditModal}
onClose={() => setShowEditModal(false)}
scenario={editingScenarioData.scenario}
collegeProfile={editingScenarioData.collegeProfile}
/>
{/* Copy wizard */}
{copyWizardMilestone && (
<CopyMilestoneWizard
milestone={copyWizardMilestone}
scenarios={allScenarios}
onClose={() => setCopyWizardMilestone(null)}
/>
)}
</>
)}
</div>
);
}