From 0eacc440758b9efe6e8583306dfd4db76c5130fa Mon Sep 17 00:00:00 2001 From: AdamJ Date: Wed, 21 Oct 2026 10:42:13 -0400 Subject: [PATCH 1/2] fix: prompt to resume previous task when active task is deleted Deleting the active task left no running task and no way to get the previous one back. Index now shows a resume-or-start-new prompt when the deleted task was active and an earlier task exists; resuming it goes through new reopenTask, which clears endTime/duration and makes it current again. Co-Authored-By: Claude Sonnet 5 --- src/contexts/TimeTrackingContext.tsx | 20 ++++++++++++ src/pages/Index.tsx | 48 +++++++++++++++++++++++++++- 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/contexts/TimeTrackingContext.tsx b/src/contexts/TimeTrackingContext.tsx index 1fc9796..9a5e062 100644 --- a/src/contexts/TimeTrackingContext.tsx +++ b/src/contexts/TimeTrackingContext.tsx @@ -187,6 +187,7 @@ interface TimeTrackingContextType { ) => string; updateTask: (taskId: string, updates: Partial) => void; deleteTask: (taskId: string) => void; + reopenTask: (taskId: string) => void; postDay: (notes?: string) => void; // Project management @@ -909,6 +910,23 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ } }, [tasks, currentTask, dayStartTime, isDayStarted, dataService]); + // Reopens a previously completed task as the current task (clears endTime/duration). + // Used when the active task is deleted and the user chooses to resume the one before it. + const reopenTask = useCallback((taskId: string) => { + const target = tasks.find(t => t.id === taskId); + if (!target) return; + const { endTime, duration, ...reopened } = target; + const updatedTasks = tasks.map(t => (t.id === taskId ? reopened : t)); + setTasks(updatedTasks); + setCurrentTask(reopened); + setHasUnsavedChanges(true); + if (dataService) { + dataService.saveCurrentDay({ isDayStarted, dayStartTime, currentTask: reopened, tasks: updatedTasks }) + .then(() => setLastSyncTime(new Date())) + .catch(error => console.error("❌ Error saving after reopening task:", error)); + } + }, [tasks, dayStartTime, isDayStarted, dataService]); + const postDay = useCallback(async (notes?: string) => { if (!dayStartTime) return; @@ -1761,6 +1779,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ startNewTask, updateTask, deleteTask, + reopenTask, postDay, plannedTasks, addPlannedTask, @@ -1832,6 +1851,7 @@ export const TimeTrackingProvider: React.FC<{ children: React.ReactNode }> = ({ startNewTask, updateTask, deleteTask, + reopenTask, postDay, plannedTasks, addPlannedTask, diff --git a/src/pages/Index.tsx b/src/pages/Index.tsx index baf720f..f863a96 100644 --- a/src/pages/Index.tsx +++ b/src/pages/Index.tsx @@ -30,6 +30,17 @@ import { SheetTrigger, } from "@/components/ui/sheet"; import { Popover, PopoverContent, PopoverHeader, PopoverTrigger, PopoverTitle } from "@/components/ui/popover"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; +import type { Task } from "@/contexts/TimeTrackingContext"; // Stable epoch constant — avoids creating new Date(0) on every render const EPOCH = new Date(0); @@ -47,6 +58,7 @@ const TimeTrackerContent = () => { discardDay, postDay, deleteTask, + reopenTask, startNewTask, getTotalHoursForPeriod, } = useTimeTracking(); @@ -54,6 +66,7 @@ const TimeTrackerContent = () => { const [showStartDayDialog, setShowStartDayDialog] = useState(false); const [showAddTaskForm, setShowAddTaskForm] = useState(false); + const [resumeCandidate, setResumeCandidate] = useState(null); useEffect(() => { if (consumePendingMenuAction("new-task")) { @@ -82,6 +95,15 @@ const TimeTrackerContent = () => { discardDay(); }; + const handleDeleteTask = (taskId: string) => { + const wasActive = currentTask?.id === taskId; + const remaining = tasks.filter(t => t.id !== taskId); + deleteTask(taskId); + if (wasActive && remaining.length > 0) { + setResumeCandidate(remaining[remaining.length - 1]); + } + }; + const handleNewTask = ( title: string, description?: string, @@ -385,7 +407,7 @@ const TimeTrackerContent = () => { ? getCurrentTaskDuration(currentTask, now) : 0 } - onDelete={deleteTask} + onDelete={handleDeleteTask} /> ))} @@ -406,6 +428,30 @@ const TimeTrackerContent = () => { + + !open && setResumeCandidate(null)}> + + + Resume previous task? + + The active task was deleted. Resume "{resumeCandidate?.title}" or start a new task? + + + + setResumeCandidate(null)}> + Start New Task + + { + if (resumeCandidate) reopenTask(resumeCandidate.id); + setResumeCandidate(null); + }} + > + Resume "{resumeCandidate?.title}" + + + + ); }; From d38b17d04066ae44f2c928a85a3ab0a78167dd13 Mon Sep 17 00:00:00 2001 From: AdamJ Date: Wed, 21 Oct 2026 10:42:21 -0400 Subject: [PATCH 2/2] docs: sync documentation with recent changes --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index efdf0c2..f67e863 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed +- Deleting the active task used to leave the day with no running task and no way to resume the one before it. `deleteTask` now surfaces an "Resume previous task?" prompt on the Dashboard when the deleted task was active and an earlier task exists, letting the user resume it (via new `reopenTask`, which clears its `endTime`/`duration` and makes it current again) or start a new task instead + — `src/contexts/TimeTrackingContext.tsx`, `src/pages/Index.tsx` - Manually editing a current-day task's end time (via `TaskEditDialog`) no longer leaves a linked planned task's "Time Spent" stale. `adjustTaskTime` previously updated only the day task's own `duration`; it now also calls `accumulatePlannedTaskTime` so the planned task's `timeSpent` and matching `timeEntries` duration resync to the edited time — `src/contexts/TimeTrackingContext.tsx`, `src/contexts/TimeTracking.test.tsx` - Archive editors now reject an end time before the start time instead of silently saving a zero-length task. `ArchivedTaskRow` and the Add Task form in `ArchiveEditDialog` show an inline `role="alert"` message, set `aria-invalid`/`aria-describedby` on the end-time input, and disable Save/Add Task until corrected; the day summary form rejects it through its zod schema, and Save Changes reopens a collapsed summary editor so the error is visible. Equal start and end times remain allowed because 15-minute rounding can collapse a short task to zero length. New shared `isEndBeforeStart` and `END_BEFORE_START_MESSAGE` in `timeUtil`; `TimePicker` accepts `aria-invalid`