Added Task CRUD to MilestoneTimeline.js and ScenarioContainer.js

This commit is contained in:
Josh 2025-05-02 17:10:24 +00:00
parent a2c9dc7157
commit b3c1d522ee
5 changed files with 166 additions and 92 deletions

View File

@ -141,7 +141,7 @@ function App() {
className="text-blue-600 hover:text-blue-800" className="text-blue-600 hover:text-blue-800"
to="/milestone-tracker" to="/milestone-tracker"
> >
Milestone Tracker Career Planner
</Link> </Link>
) : ( ) : (
<span className="text-gray-400 cursor-not-allowed"> <span className="text-gray-400 cursor-not-allowed">

View File

@ -3,16 +3,21 @@
import React, { useEffect, useState, useCallback } from 'react'; import React, { useEffect, useState, useCallback } from 'react';
import { Button } from './ui/button.js'; import { Button } from './ui/button.js';
/**
* Renders a simple vertical list of milestones for the given careerPathId.
* Also includes Task CRUD (create/edit/delete) for each milestone,
* plus a small "copy milestone" wizard, "financial impacts" form, etc.
*/
export default function MilestoneTimeline({ export default function MilestoneTimeline({
careerPathId, careerPathId,
authFetch, authFetch,
activeView, activeView, // 'Career' or 'Financial'
setActiveView, setActiveView, // optional, if you need to switch between views
onMilestoneUpdated onMilestoneUpdated // callback after saving/deleting a milestone
}) { }) {
const [milestones, setMilestones] = useState({ Career: [], Financial: [] }); const [milestones, setMilestones] = useState({ Career: [], Financial: [] });
// We'll keep your existing milestone form state, tasks, copy wizard, etc. // For CREATE/EDIT milestone
const [newMilestone, setNewMilestone] = useState({ const [newMilestone, setNewMilestone] = useState({
title: '', title: '',
description: '', description: '',
@ -26,14 +31,21 @@ export default function MilestoneTimeline({
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const [editingMilestone, setEditingMilestone] = useState(null); const [editingMilestone, setEditingMilestone] = useState(null);
const [showTaskForm, setShowTaskForm] = useState(null); // For CREATE/EDIT tasks
const [newTask, setNewTask] = useState({ title: '', description: '', due_date: '' }); const [showTaskForm, setShowTaskForm] = useState(null); // which milestone ID is showing the form
const [newTask, setNewTask] = useState({
id: null,
title: '',
description: '',
due_date: ''
});
// For the "Copy to other scenarios" wizard
const [scenarios, setScenarios] = useState([]); const [scenarios, setScenarios] = useState([]);
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null); const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// 1) Financial Impacts sub-form helpers (no change) // 1) Financial Impacts sub-form helpers
// ------------------------------------------------------------------ // ------------------------------------------------------------------
function addNewImpact() { function addNewImpact() {
setNewMilestone((prev) => ({ setNewMilestone((prev) => ({
@ -81,11 +93,13 @@ export default function MilestoneTimeline({
console.warn('No milestones in response:', data); console.warn('No milestones in response:', data);
return; return;
} }
// Separate them by type
const categorized = { Career: [], Financial: [] }; const categorized = { Career: [], Financial: [] };
data.milestones.forEach((m) => { data.milestones.forEach((m) => {
if (categorized[m.milestone_type]) { if (categorized[m.milestone_type]) {
categorized[m.milestone_type].push(m); categorized[m.milestone_type].push(m);
} else { } else {
// If there's a random type, log or store somewhere else
console.warn(`Unknown milestone type: ${m.milestone_type}`); console.warn(`Unknown milestone type: ${m.milestone_type}`);
} }
}); });
@ -167,7 +181,7 @@ export default function MilestoneTimeline({
const method = editingMilestone ? 'PUT' : 'POST'; const method = editingMilestone ? 'PUT' : 'POST';
const payload = { const payload = {
milestone_type: activeView, milestone_type: activeView, // 'Career' or 'Financial'
title: newMilestone.title, title: newMilestone.title,
description: newMilestone.description, description: newMilestone.description,
date: newMilestone.date, date: newMilestone.date,
@ -197,6 +211,7 @@ export default function MilestoneTimeline({
const savedMilestone = await res.json(); const savedMilestone = await res.json();
console.log('Milestone saved/updated:', savedMilestone); console.log('Milestone saved/updated:', savedMilestone);
// If it's a "Financial" milestone => handle impacts
if (activeView === 'Financial') { if (activeView === 'Financial') {
// 1) Delete old impacts // 1) Delete old impacts
for (const impactId of impactsToDelete) { for (const impactId of impactsToDelete) {
@ -253,7 +268,7 @@ export default function MilestoneTimeline({
} }
} }
// Re-fetch // Re-fetch milestones
await fetchMilestones(); await fetchMilestones();
// reset form // reset form
@ -279,44 +294,91 @@ export default function MilestoneTimeline({
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// 6) Add Task // 6) TASK CRUD
// ------------------------------------------------------------------ // ------------------------------------------------------------------
async function addTask(milestoneId) {
try { // A) “Add Task” button => sets newTask for a new item
const taskPayload = { function handleAddTask(milestoneId) {
setShowTaskForm(milestoneId);
setNewTask({ id: null, title: '', description: '', due_date: '' });
}
// B) “Edit Task” => fill newTask with the existing fields
function handleEditTask(milestoneId, task) {
setShowTaskForm(milestoneId);
setNewTask({
id: task.id,
title: task.title,
description: task.description || '',
due_date: task.due_date || ''
});
}
// C) Save (create or update) task
async function saveTask(milestoneId) {
if (!newTask.title.trim()) {
alert('Task needs a title');
return;
}
const payload = {
milestone_id: milestoneId, milestone_id: milestoneId,
title: newTask.title, title: newTask.title,
description: newTask.description, description: newTask.description,
due_date: newTask.due_date due_date: newTask.due_date
}; };
console.log('Creating new task:', taskPayload);
const res = await authFetch('/api/premium/tasks', { let url = '/api/premium/tasks';
method: 'POST', let method = 'POST';
if (newTask.id) {
// existing => PUT
url = `/api/premium/tasks/${newTask.id}`;
method = 'PUT';
}
try {
const res = await authFetch(url, {
method,
headers: { 'Content-Type': 'application/json' }, headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(taskPayload) body: JSON.stringify(payload)
}); });
if (!res.ok) { if (!res.ok) {
const errorData = await res.json(); const errData = await res.json().catch(() => ({}));
console.error('Failed to create task:', errorData); console.error('Failed to save task:', errData);
alert(errorData.error || 'Error creating task'); alert(errData.error || 'Error saving task');
return; return;
} }
const createdTask = await res.json();
console.log('Task created:', createdTask);
// Re-fetch so the list shows the new task // re-fetch
await fetchMilestones(); await fetchMilestones();
setNewTask({ title: '', description: '', due_date: '' }); // reset
setShowTaskForm(null); setShowTaskForm(null);
setNewTask({ id: null, title: '', description: '', due_date: '' });
} catch (err) { } catch (err) {
console.error('Error adding task:', err); console.error('Error saving task:', err);
}
}
// D) Delete an existing task
async function deleteTask(taskId) {
if (!taskId) return;
try {
const res = await authFetch(`/api/premium/tasks/${taskId}`, { method: 'DELETE' });
if (!res.ok) {
const errData = await res.json().catch(() => ({}));
console.error('Failed to delete task:', errData);
alert(errData.error || 'Error deleting task');
return;
}
await fetchMilestones();
} catch (err) {
console.error('Error deleting task:', err);
} }
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// 7) Copy Wizard // 7) Copy Wizard for universal/cross-scenario
// ------------------------------------------------------------------ // ------------------------------------------------------------------
function CopyMilestoneWizard({ milestone, scenarios, onClose, authFetch }) { function CopyMilestoneWizard({ milestone, scenarios, onClose, authFetch }) {
const [selectedScenarios, setSelectedScenarios] = useState([]); const [selectedScenarios, setSelectedScenarios] = useState([]);
@ -425,9 +487,9 @@ export default function MilestoneTimeline({
} }
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// 9) RENDER: remove the "timeline" code, show a list instead // 9) Render
// ------------------------------------------------------------------ // ------------------------------------------------------------------
// Combined array if you want to show them all in one list // Combine "Career" + "Financial" if you want them in a single list:
const allMilestones = [...milestones.Career, ...milestones.Financial]; const allMilestones = [...milestones.Career, ...milestones.Financial];
return ( return (
@ -458,7 +520,7 @@ export default function MilestoneTimeline({
{showForm ? 'Cancel' : '+ New Milestone'} {showForm ? 'Cancel' : '+ New Milestone'}
</Button> </Button>
{/* If showForm => the same create/edit form */} {/* If showForm => the create/edit milestone sub-form */}
{showForm && ( {showForm && (
<div className="border p-2 my-2"> <div className="border p-2 my-2">
<h4>{editingMilestone ? 'Edit Milestone' : 'New Milestone'}</h4> <h4>{editingMilestone ? 'Edit Milestone' : 'New Milestone'}</h4>
@ -583,8 +645,7 @@ export default function MilestoneTimeline({
</div> </div>
)} )}
{/* *** REPLACEMENT FOR THE OLD “TIMELINE VISUAL” *** */} {/* Render the (Career + Financial) milestones in a simple vertical list */}
{/* Instead of a horizontal timeline, we list them in a simple vertical list. */}
{Object.keys(milestones).map((typeKey) => {Object.keys(milestones).map((typeKey) =>
milestones[typeKey].map((m) => { milestones[typeKey].map((m) => {
const tasks = m.tasks || []; const tasks = m.tasks || [];
@ -607,21 +668,40 @@ export default function MilestoneTimeline({
<strong>{t.title}</strong> <strong>{t.title}</strong>
{t.description ? ` - ${t.description}` : ''} {t.description ? ` - ${t.description}` : ''}
{t.due_date ? ` (Due: ${t.due_date})` : ''}{' '} {t.due_date ? ` (Due: ${t.due_date})` : ''}{' '}
{/* If you'd like to add “Edit”/“Delete” for tasks, replicate scenario container logic */} {/* EDIT & DELETE Task buttons */}
<Button
onClick={() => handleEditTask(m.id, t)}
style={{ marginLeft: '0.5rem' }}
>
Edit
</Button>
<Button
onClick={() => deleteTask(t.id)}
style={{ marginLeft: '0.5rem', color: 'red' }}
>
Delete
</Button>
</li> </li>
))} ))}
</ul> </ul>
)} )}
{/* Add or edit a task */}
<Button <Button
onClick={() => { onClick={() => {
setShowTaskForm(showTaskForm === m.id ? null : m.id); // if we are already showing the form for this milestone => Cancel
setNewTask({ title: '', description: '', due_date: '' }); if (showTaskForm === m.id) {
setShowTaskForm(null);
setNewTask({ id: null, title: '', description: '', due_date: '' });
} else {
handleAddTask(m.id);
}
}} }}
style={{ marginRight: '0.5rem' }} style={{ marginRight: '0.5rem' }}
> >
{showTaskForm === m.id ? 'Cancel Task' : 'Add Task'} {showTaskForm === m.id ? 'Cancel Task' : '+ Task'}
</Button> </Button>
<Button onClick={() => handleEditMilestone(m)}>Edit</Button> <Button onClick={() => handleEditMilestone(m)}>Edit</Button>
<Button <Button
style={{ marginLeft: '0.5rem' }} style={{ marginLeft: '0.5rem' }}
@ -636,30 +716,42 @@ export default function MilestoneTimeline({
Delete Delete
</Button> </Button>
{/* The "Add Task" form if showTaskForm === m.id */} {/* If this is the milestone whose tasks we're editing => show the form */}
{showTaskForm === m.id && ( {showTaskForm === m.id && (
<div <div
style={{ marginTop: '0.5rem', border: '1px solid #aaa', padding: '0.5rem' }} style={{
marginTop: '0.5rem',
border: '1px solid #aaa',
padding: '0.5rem'
}}
> >
<h5>New Task</h5> <h5>{newTask.id ? 'Edit Task' : 'New Task'}</h5>
<input <input
type="text" type="text"
placeholder="Task Title" placeholder="Task Title"
value={newTask.title} value={newTask.title}
onChange={(e) => setNewTask({ ...newTask, title: e.target.value })} onChange={(e) =>
setNewTask((prev) => ({ ...prev, title: e.target.value }))
}
/> />
<input <input
type="text" type="text"
placeholder="Task Description" placeholder="Task Description"
value={newTask.description} value={newTask.description}
onChange={(e) => setNewTask({ ...newTask, description: e.target.value })} onChange={(e) =>
setNewTask((prev) => ({ ...prev, description: e.target.value }))
}
/> />
<input <input
type="date" type="date"
value={newTask.due_date} value={newTask.due_date || ''}
onChange={(e) => setNewTask({ ...newTask, due_date: e.target.value })} onChange={(e) =>
setNewTask((prev) => ({ ...prev, due_date: e.target.value }))
}
/> />
<Button onClick={() => addTask(m.id)}>Save Task</Button> <Button onClick={() => saveTask(m.id)}>
{newTask.id ? 'Update' : 'Add'} Task
</Button>
</div> </div>
)} )}
</div> </div>

View File

@ -1,5 +1,3 @@
// src/components/MilestoneTracker.js
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from 'react-router-dom';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
@ -19,9 +17,7 @@ import authFetch from '../utils/authFetch.js';
import CareerSelectDropdown from './CareerSelectDropdown.js'; import CareerSelectDropdown from './CareerSelectDropdown.js';
import CareerSearch from './CareerSearch.js'; import CareerSearch from './CareerSearch.js';
// Keep MilestoneTimeline for +Add Milestone & tasks CRUD import MilestoneTimeline from './MilestoneTimeline.js'; // Key: This handles Milestone & Task CRUD
import MilestoneTimeline from './MilestoneTimeline.js';
import AISuggestedMilestones from './AISuggestedMilestones.js'; import AISuggestedMilestones from './AISuggestedMilestones.js';
import ScenarioEditModal from './ScenarioEditModal.js'; import ScenarioEditModal from './ScenarioEditModal.js';
@ -58,17 +54,15 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
const [scenarioRow, setScenarioRow] = useState(null); const [scenarioRow, setScenarioRow] = useState(null);
const [collegeProfile, setCollegeProfile] = useState(null); const [collegeProfile, setCollegeProfile] = useState(null);
// We will store the scenarios milestones in state so we can build annotation lines const [scenarioMilestones, setScenarioMilestones] = useState([]); // for annotation
const [scenarioMilestones, setScenarioMilestones] = useState([]);
const [projectionData, setProjectionData] = useState([]); const [projectionData, setProjectionData] = useState([]);
const [loanPayoffMonth, setLoanPayoffMonth] = useState(null); const [loanPayoffMonth, setLoanPayoffMonth] = useState(null);
const [simulationYearsInput, setSimulationYearsInput] = useState('20'); const [simulationYearsInput, setSimulationYearsInput] = useState('20');
const simulationYears = parseInt(simulationYearsInput, 10) || 20; const simulationYears = parseInt(simulationYearsInput, 10) || 20;
// --- ADDED: showEditModal state // Show/hide scenario edit modal
const [showEditModal, setShowEditModal] = useState(false); const [showEditModal, setShowEditModal] = useState(false);
const [pendingCareerForModal, setPendingCareerForModal] = useState(null); const [pendingCareerForModal, setPendingCareerForModal] = useState(null);
const { const {
@ -86,6 +80,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
const data = await res.json(); const data = await res.json();
setExistingCareerPaths(data.careerPaths); setExistingCareerPaths(data.careerPaths);
// If user came from a different route passing in a selected scenario:
const fromPopout = location.state?.selectedCareer; const fromPopout = location.state?.selectedCareer;
if (fromPopout) { if (fromPopout) {
setSelectedCareer(fromPopout); setSelectedCareer(fromPopout);
@ -172,9 +167,9 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
} }
const milestonesData = await milRes.json(); const milestonesData = await milRes.json();
const allMilestones = milestonesData.milestones || []; const allMilestones = milestonesData.milestones || [];
setScenarioMilestones(allMilestones); // store them for annotation lines setScenarioMilestones(allMilestones);
// fetch impacts for each // fetch impacts for each milestone
const impactPromises = allMilestones.map((m) => const impactPromises = allMilestones.map((m) =>
authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`) authFetch(`${apiURL}/premium/milestone-impacts?milestone_id=${m.id}`)
.then((r) => (r.ok ? r.json() : null)) .then((r) => (r.ok ? r.json() : null))
@ -190,10 +185,10 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
impacts: impactsForEach[i] || [] impacts: impactsForEach[i] || []
})); }));
// flatten all // flatten
const allImpacts = milestonesWithImpacts.flatMap((m) => m.impacts); const allImpacts = milestonesWithImpacts.flatMap((m) => m.impacts);
// mergedProfile // Build mergedProfile
const mergedProfile = { const mergedProfile = {
currentSalary: financialProfile.current_salary || 0, currentSalary: financialProfile.current_salary || 0,
monthlyExpenses: monthlyExpenses:
@ -271,12 +266,6 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
})(); })();
}, [financialProfile, scenarioRow, collegeProfile, careerPathId, apiURL, simulationYears]); }, [financialProfile, scenarioRow, collegeProfile, careerPathId, apiURL, simulationYears]);
// If you want to re-run simulation after any milestone changes:
const reSimulate = async () => {
// Put your logic to re-fetch scenario + milestones, then re-run sim (if needed).
};
// handle user typing simulation length
const handleSimulationYearsChange = (e) => setSimulationYearsInput(e.target.value); const handleSimulationYearsChange = (e) => setSimulationYearsInput(e.target.value);
const handleSimulationYearsBlur = () => { const handleSimulationYearsBlur = () => {
if (!simulationYearsInput.trim()) { if (!simulationYearsInput.trim()) {
@ -284,7 +273,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
} }
}; };
// Build annotation lines from scenarioMilestones // Build chart annotations from scenarioMilestones
const milestoneAnnotationLines = {}; const milestoneAnnotationLines = {};
scenarioMilestones.forEach((m) => { scenarioMilestones.forEach((m) => {
if (!m.date) return; if (!m.date) return;
@ -295,6 +284,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
const month = String(d.getUTCMonth() + 1).padStart(2, '0'); const month = String(d.getUTCMonth() + 1).padStart(2, '0');
const short = `${year}-${month}`; const short = `${year}-${month}`;
// check if we have data for that month
if (!projectionData.some((p) => p.month === short)) return; if (!projectionData.some((p) => p.month === short)) return;
milestoneAnnotationLines[`milestone_${m.id}`] = { milestoneAnnotationLines[`milestone_${m.id}`] = {
@ -308,16 +298,11 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
content: m.title || 'Milestone', content: m.title || 'Milestone',
color: 'orange', color: 'orange',
position: 'end' position: 'end'
},
// If you want them clickable:
onClick: () => {
console.log('Clicked milestone line => open editing for', m.title);
// e.g. open the MilestoneTimeline's edit feature, or do something
} }
}; };
}); });
// If we also show a line for payoff: // If we also want a line for payoff:
const annotationConfig = {}; const annotationConfig = {};
if (loanPayoffMonth) { if (loanPayoffMonth) {
annotationConfig.loanPaidOffLine = { annotationConfig.loanPaidOffLine = {
@ -355,7 +340,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
authFetch={authFetch} authFetch={authFetch}
/> />
{/* 2) We keep MilestoneTimeline for tasks, +Add Milestone button, etc. */} {/* 2) MilestoneTimeline for Milestone & Task CRUD */}
<MilestoneTimeline <MilestoneTimeline
careerPathId={careerPathId} careerPathId={careerPathId}
authFetch={authFetch} authFetch={authFetch}
@ -439,7 +424,7 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
</div> </div>
)} )}
{/* 5) Simulation length input + the new Edit button */} {/* 5) Simulation length + "Edit" Button => open ScenarioEditModal */}
<div className="space-x-2"> <div className="space-x-2">
<label className="font-medium">Simulation Length (years):</label> <label className="font-medium">Simulation Length (years):</label>
<input <input
@ -449,7 +434,6 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
onBlur={handleSimulationYearsBlur} onBlur={handleSimulationYearsBlur}
className="border rounded p-1 w-16" className="border rounded p-1 w-16"
/> />
{/* EDIT BUTTON => open ScenarioEditModal */}
<Button onClick={() => setShowEditModal(true)} className="ml-2"> <Button onClick={() => setShowEditModal(true)} className="ml-2">
Edit Edit
</Button> </Button>
@ -474,12 +458,11 @@ const MilestoneTracker = ({ selectedCareer: initialCareer }) => {
</Button> </Button>
)} )}
{/* Pass scenarioRow to the modal, and optionally do a hard refresh onClose */}
<ScenarioEditModal <ScenarioEditModal
show={showEditModal} show={showEditModal}
onClose={() => { onClose={() => {
setShowEditModal(false); setShowEditModal(false);
// Hard-refresh if you want to replicate "ScenarioContainer" approach: // optionally reload to see scenario changes
window.location.reload(); window.location.reload();
}} }}
scenario={scenarioRow} scenario={scenarioRow}

View File

@ -1,11 +1,9 @@
// src/components/ScenarioContainer.js
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { Line } from 'react-chartjs-2'; import { Line } from 'react-chartjs-2';
import { Chart as ChartJS } from 'chart.js'; import { Chart as ChartJS } from 'chart.js';
import annotationPlugin from 'chartjs-plugin-annotation'; import annotationPlugin from 'chartjs-plugin-annotation';
import { Button } from './ui/button.js'; // universal Button import { Button } from './ui/button.js';
import authFetch from '../utils/authFetch.js'; import authFetch from '../utils/authFetch.js';
import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js'; import { simulateFinancialProjection } from '../utils/FinancialProjectionService.js';
import AISuggestedMilestones from './AISuggestedMilestones.js'; import AISuggestedMilestones from './AISuggestedMilestones.js';
@ -339,7 +337,6 @@ export default function ScenarioContainer({
// tasks // tasks
const [showTaskForm, setShowTaskForm] = useState(null); const [showTaskForm, setShowTaskForm] = useState(null);
// We'll track a separate "editingTask" so we can fill in the form
const [editingTask, setEditingTask] = useState({ const [editingTask, setEditingTask] = useState({
id: null, id: null,
title: '', title: '',
@ -350,7 +347,6 @@ export default function ScenarioContainer({
// copy wizard // copy wizard
const [copyWizardMilestone, setCopyWizardMilestone] = useState(null); const [copyWizardMilestone, setCopyWizardMilestone] = useState(null);
// create new milestone
function handleNewMilestone() { function handleNewMilestone() {
setEditingMilestone(null); setEditingMilestone(null);
setNewMilestone({ setNewMilestone({
@ -366,7 +362,6 @@ export default function ScenarioContainer({
setShowForm(true); setShowForm(true);
} }
// edit an existing milestone => fetch impacts
async function handleEditMilestone(m) { async function handleEditMilestone(m) {
if (!localScenario?.id) return; if (!localScenario?.id) return;
setEditingMilestone(m); setEditingMilestone(m);
@ -559,7 +554,7 @@ export default function ScenarioContainer({
/************************************************************* /*************************************************************
* 6) TASK CRUD * 6) TASK CRUD
*************************************************************/ *************************************************************/
// This can handle both new and existing tasks // handle both new and existing tasks
function handleAddTask(milestoneId) { function handleAddTask(milestoneId) {
setShowTaskForm(milestoneId); setShowTaskForm(milestoneId);
setEditingTask({ setEditingTask({
@ -980,7 +975,6 @@ export default function ScenarioContainer({
{/* Render existing milestones */} {/* Render existing milestones */}
{milestones.map((m) => { {milestones.map((m) => {
// tasks
const tasks = m.tasks || []; const tasks = m.tasks || [];
return ( return (
<div <div
@ -994,8 +988,7 @@ export default function ScenarioContainer({
<h5>{m.title}</h5> <h5>{m.title}</h5>
{m.description && <p>{m.description}</p>} {m.description && <p>{m.description}</p>}
<p> <p>
<strong>Date:</strong> {m.date} {' '} <strong>Date:</strong> {m.date} <strong>Progress:</strong> {m.progress}%
<strong>Progress:</strong> {m.progress}%
</p> </p>
{/* tasks list */} {/* tasks list */}
@ -1043,9 +1036,15 @@ export default function ScenarioContainer({
Delete Delete
</Button> </Button>
{/* If this is the milestone whose tasks we're editing => show the task form */} {/* The "Add/Edit Task" form if showTaskForm === this milestone */}
{showTaskForm === m.id && ( {showTaskForm === m.id && (
<div style={{ marginTop: '0.5rem', border: '1px solid #aaa', padding: '0.5rem' }}> <div
style={{
marginTop: '0.5rem',
border: '1px solid #aaa',
padding: '0.5rem'
}}
>
<h5>{editingTask.id ? 'Edit Task' : 'New Task'}</h5> <h5>{editingTask.id ? 'Edit Task' : 'New Task'}</h5>
<input <input
type="text" type="text"

Binary file not shown.