From 814163b07f77ac8a26b9fda6a02592e21e5bfaa9 Mon Sep 17 00:00:00 2001 From: paul-nothaft <40865108-paul-nothaft@users.noreply.replit.com> Date: Fri, 24 Oct 2025 06:12:45 +0000 Subject: [PATCH] Add functionality to delete tasks with a confirmation dialog Implement task deletion with optimistic UI updates, confirmation dialogs using Shadcn UI's AlertDialog, and API integration for backend task removal. Includes i18n support for delete confirmation messages. Replit-Commit-Author: Agent Replit-Commit-Session-Id: ceced2fc-aa46-458d-ba87-ddd4b7bb1518 Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/659922a9-0087-461c-90dd-6d9a58b81d4d/ceced2fc-aa46-458d-ba87-ddd4b7bb1518/nn5bWKE --- .replit | 4 -- client/src/App.tsx | 75 +++++++++++++++++++++ client/src/components/TasksWithCalendar.tsx | 7 +- client/src/i18n/locales/de.json | 6 ++ client/src/i18n/locales/en.json | 6 ++ 5 files changed, 93 insertions(+), 5 deletions(-) diff --git a/.replit b/.replit index 49bc838..a572720 100644 --- a/.replit +++ b/.replit @@ -18,10 +18,6 @@ externalPort = 80 localPort = 35345 externalPort = 3002 -[[ports]] -localPort = 38837 -externalPort = 4200 - [[ports]] localPort = 39063 externalPort = 3001 diff --git a/client/src/App.tsx b/client/src/App.tsx index c6d7192..3a12094 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -6,6 +6,16 @@ import { Toaster } from "@/components/ui/toaster"; import { TooltipProvider } from "@/components/ui/tooltip"; import { Button } from "@/components/ui/button"; import { Card } from "@/components/ui/card"; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog"; // Components import BottomNavigation from './components/BottomNavigation'; @@ -98,6 +108,7 @@ function App() { const [isCreateModalOpen, setIsCreateModalOpen] = useState(false); const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false); const [selectedTask, setSelectedTask] = useState(null); + const [deleteTaskId, setDeleteTaskId] = useState(null); // Fetch tasks and labels from server on mount useEffect(() => { @@ -229,6 +240,44 @@ function App() { console.log('Task details saved:', updatedTask.title); }; + const handleTaskDelete = async (taskId: string) => { + setDeleteTaskId(taskId); + }; + + const confirmTaskDelete = async () => { + if (!deleteTaskId) return; + + // Optimistic deletion - remove from UI immediately + const taskToDelete = tasks.find(task => task.id === deleteTaskId); + setTasks(prev => prev.filter(task => task.id !== deleteTaskId)); + + // Close task details modal if the deleted task was open + if (selectedTask?.id === deleteTaskId) { + handleTaskDetailsClose(); + } + + setDeleteTaskId(null); + + try { + const response = await fetch(`/api/tasks/${deleteTaskId}`, { + method: 'DELETE' + }); + + // 404 is acceptable - task doesn't exist on backend (seeded task) + if (!response.ok && response.status !== 404) { + throw new Error('Failed to delete task'); + } + + console.log('Task deleted:', deleteTaskId); + } catch (error) { + console.error('Error deleting task:', error); + // Restore task on error + if (taskToDelete) { + setTasks(prev => [...prev, taskToDelete]); + } + } + }; + const handleCreateFromTemplate = (templateId: string, startDate: Date, projectName: string) => { console.log('Creating project from template:', { templateId, startDate, projectName }); // In a real app, this would create multiple tasks based on the template @@ -242,6 +291,7 @@ function App() { tasks={tasks} onTaskUpdate={handleTaskUpdate} onTaskEdit={handleTaskClick} + onTaskDelete={handleTaskDelete} onStartTimer={startTimer} onStopTimer={stopTimer} /> @@ -264,6 +314,7 @@ function App() { onTaskStatusChange={handleTaskStatusChange} onTaskUpdate={handleTaskUpdate} onTaskClick={handleTaskClick} + onTaskDelete={handleTaskDelete} onStartTimer={startTimer} onStopTimer={stopTimer} /> @@ -354,6 +405,30 @@ function App() { onSave={handleTaskDetailsSave} labels={labels} /> + + {/* Delete Confirmation Dialog */} + !open && setDeleteTaskId(null)}> + + + {t('deleteConfirmation.title')} + + {t('deleteConfirmation.description')} + + + + + {t('deleteConfirmation.cancel')} + + + {t('deleteConfirmation.confirm')} + + + + diff --git a/client/src/components/TasksWithCalendar.tsx b/client/src/components/TasksWithCalendar.tsx index dd13def..757556b 100644 --- a/client/src/components/TasksWithCalendar.tsx +++ b/client/src/components/TasksWithCalendar.tsx @@ -15,6 +15,7 @@ interface TasksWithCalendarProps { tasks: Task[]; onTaskUpdate?: (taskId: string, updates: Partial) => void; onTaskEdit?: (task: Task) => void; + onTaskDelete?: (taskId: string) => void; onStartTimer?: (taskId: string) => void; onStopTimer?: (taskId: string) => void; } @@ -22,7 +23,7 @@ interface TasksWithCalendarProps { type SortOption = 'dueDate' | 'priority' | 'title' | 'status'; type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue'; -export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onStartTimer, onStopTimer }: TasksWithCalendarProps) { +export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onTaskDelete, onStartTimer, onStopTimer }: TasksWithCalendarProps) { const [searchQuery, setSearchQuery] = useState(''); const [sortBy, setSortBy] = useState('dueDate'); const [filterBy, setFilterBy] = useState('all'); @@ -304,6 +305,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onS onTaskEdit?.(task); console.log(`Edit task ${task.title}`); }} + onDelete={() => { + onTaskDelete?.(task.id); + console.log(`Delete task ${task.title}`); + }} onStatusChange={(newStatus) => { if (newStatus === 'done') { handleTaskComplete(task); diff --git a/client/src/i18n/locales/de.json b/client/src/i18n/locales/de.json index 37e8326..c44a8c2 100644 --- a/client/src/i18n/locales/de.json +++ b/client/src/i18n/locales/de.json @@ -173,5 +173,11 @@ "close": "Schließen", "loading": "Laden...", "error": "Ein Fehler ist aufgetreten" + }, + "deleteConfirmation": { + "title": "Aufgabe löschen", + "description": "Möchten Sie diese Aufgabe wirklich löschen? Diese Aktion kann nicht rückgängig gemacht werden.", + "cancel": "Abbrechen", + "confirm": "Löschen" } } diff --git a/client/src/i18n/locales/en.json b/client/src/i18n/locales/en.json index 3c85c64..3793589 100644 --- a/client/src/i18n/locales/en.json +++ b/client/src/i18n/locales/en.json @@ -173,5 +173,11 @@ "close": "Close", "loading": "Loading...", "error": "An error occurred" + }, + "deleteConfirmation": { + "title": "Delete Task", + "description": "Are you sure you want to delete this task? This action cannot be undone.", + "cancel": "Cancel", + "confirm": "Delete" } }