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
This commit is contained in:
@@ -18,10 +18,6 @@ externalPort = 80
|
|||||||
localPort = 35345
|
localPort = 35345
|
||||||
externalPort = 3002
|
externalPort = 3002
|
||||||
|
|
||||||
[[ports]]
|
|
||||||
localPort = 38837
|
|
||||||
externalPort = 4200
|
|
||||||
|
|
||||||
[[ports]]
|
[[ports]]
|
||||||
localPort = 39063
|
localPort = 39063
|
||||||
externalPort = 3001
|
externalPort = 3001
|
||||||
|
|||||||
@@ -6,6 +6,16 @@ import { Toaster } from "@/components/ui/toaster";
|
|||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card } from "@/components/ui/card";
|
import { Card } from "@/components/ui/card";
|
||||||
|
import {
|
||||||
|
AlertDialog,
|
||||||
|
AlertDialogAction,
|
||||||
|
AlertDialogCancel,
|
||||||
|
AlertDialogContent,
|
||||||
|
AlertDialogDescription,
|
||||||
|
AlertDialogFooter,
|
||||||
|
AlertDialogHeader,
|
||||||
|
AlertDialogTitle,
|
||||||
|
} from "@/components/ui/alert-dialog";
|
||||||
|
|
||||||
// Components
|
// Components
|
||||||
import BottomNavigation from './components/BottomNavigation';
|
import BottomNavigation from './components/BottomNavigation';
|
||||||
@@ -98,6 +108,7 @@ function App() {
|
|||||||
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
const [isCreateModalOpen, setIsCreateModalOpen] = useState(false);
|
||||||
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
|
const [isTaskDetailsOpen, setIsTaskDetailsOpen] = useState(false);
|
||||||
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
const [selectedTask, setSelectedTask] = useState<Task | null>(null);
|
||||||
|
const [deleteTaskId, setDeleteTaskId] = useState<string | null>(null);
|
||||||
|
|
||||||
// Fetch tasks and labels from server on mount
|
// Fetch tasks and labels from server on mount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -229,6 +240,44 @@ function App() {
|
|||||||
console.log('Task details saved:', updatedTask.title);
|
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) => {
|
const handleCreateFromTemplate = (templateId: string, startDate: Date, projectName: string) => {
|
||||||
console.log('Creating project from template:', { templateId, startDate, projectName });
|
console.log('Creating project from template:', { templateId, startDate, projectName });
|
||||||
// In a real app, this would create multiple tasks based on the template
|
// In a real app, this would create multiple tasks based on the template
|
||||||
@@ -242,6 +291,7 @@ function App() {
|
|||||||
tasks={tasks}
|
tasks={tasks}
|
||||||
onTaskUpdate={handleTaskUpdate}
|
onTaskUpdate={handleTaskUpdate}
|
||||||
onTaskEdit={handleTaskClick}
|
onTaskEdit={handleTaskClick}
|
||||||
|
onTaskDelete={handleTaskDelete}
|
||||||
onStartTimer={startTimer}
|
onStartTimer={startTimer}
|
||||||
onStopTimer={stopTimer}
|
onStopTimer={stopTimer}
|
||||||
/>
|
/>
|
||||||
@@ -264,6 +314,7 @@ function App() {
|
|||||||
onTaskStatusChange={handleTaskStatusChange}
|
onTaskStatusChange={handleTaskStatusChange}
|
||||||
onTaskUpdate={handleTaskUpdate}
|
onTaskUpdate={handleTaskUpdate}
|
||||||
onTaskClick={handleTaskClick}
|
onTaskClick={handleTaskClick}
|
||||||
|
onTaskDelete={handleTaskDelete}
|
||||||
onStartTimer={startTimer}
|
onStartTimer={startTimer}
|
||||||
onStopTimer={stopTimer}
|
onStopTimer={stopTimer}
|
||||||
/>
|
/>
|
||||||
@@ -354,6 +405,30 @@ function App() {
|
|||||||
onSave={handleTaskDetailsSave}
|
onSave={handleTaskDetailsSave}
|
||||||
labels={labels}
|
labels={labels}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* Delete Confirmation Dialog */}
|
||||||
|
<AlertDialog open={deleteTaskId !== null} onOpenChange={(open) => !open && setDeleteTaskId(null)}>
|
||||||
|
<AlertDialogContent data-testid="dialog-delete-confirmation">
|
||||||
|
<AlertDialogHeader>
|
||||||
|
<AlertDialogTitle>{t('deleteConfirmation.title')}</AlertDialogTitle>
|
||||||
|
<AlertDialogDescription>
|
||||||
|
{t('deleteConfirmation.description')}
|
||||||
|
</AlertDialogDescription>
|
||||||
|
</AlertDialogHeader>
|
||||||
|
<AlertDialogFooter>
|
||||||
|
<AlertDialogCancel data-testid="button-cancel-delete">
|
||||||
|
{t('deleteConfirmation.cancel')}
|
||||||
|
</AlertDialogCancel>
|
||||||
|
<AlertDialogAction
|
||||||
|
onClick={confirmTaskDelete}
|
||||||
|
className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
|
||||||
|
data-testid="button-confirm-delete"
|
||||||
|
>
|
||||||
|
{t('deleteConfirmation.confirm')}
|
||||||
|
</AlertDialogAction>
|
||||||
|
</AlertDialogFooter>
|
||||||
|
</AlertDialogContent>
|
||||||
|
</AlertDialog>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Toaster />
|
<Toaster />
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ interface TasksWithCalendarProps {
|
|||||||
tasks: Task[];
|
tasks: Task[];
|
||||||
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
onTaskUpdate?: (taskId: string, updates: Partial<Task>) => void;
|
||||||
onTaskEdit?: (task: Task) => void;
|
onTaskEdit?: (task: Task) => void;
|
||||||
|
onTaskDelete?: (taskId: string) => void;
|
||||||
onStartTimer?: (taskId: string) => void;
|
onStartTimer?: (taskId: string) => void;
|
||||||
onStopTimer?: (taskId: string) => void;
|
onStopTimer?: (taskId: string) => void;
|
||||||
}
|
}
|
||||||
@@ -22,7 +23,7 @@ interface TasksWithCalendarProps {
|
|||||||
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
type SortOption = 'dueDate' | 'priority' | 'title' | 'status';
|
||||||
type FilterOption = 'all' | 'todo' | 'inProgress' | 'done' | 'overdue';
|
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 [searchQuery, setSearchQuery] = useState('');
|
||||||
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
const [sortBy, setSortBy] = useState<SortOption>('dueDate');
|
||||||
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
const [filterBy, setFilterBy] = useState<FilterOption>('all');
|
||||||
@@ -304,6 +305,10 @@ export default function TasksWithCalendar({ tasks, onTaskUpdate, onTaskEdit, onS
|
|||||||
onTaskEdit?.(task);
|
onTaskEdit?.(task);
|
||||||
console.log(`Edit task ${task.title}`);
|
console.log(`Edit task ${task.title}`);
|
||||||
}}
|
}}
|
||||||
|
onDelete={() => {
|
||||||
|
onTaskDelete?.(task.id);
|
||||||
|
console.log(`Delete task ${task.title}`);
|
||||||
|
}}
|
||||||
onStatusChange={(newStatus) => {
|
onStatusChange={(newStatus) => {
|
||||||
if (newStatus === 'done') {
|
if (newStatus === 'done') {
|
||||||
handleTaskComplete(task);
|
handleTaskComplete(task);
|
||||||
|
|||||||
@@ -173,5 +173,11 @@
|
|||||||
"close": "Schließen",
|
"close": "Schließen",
|
||||||
"loading": "Laden...",
|
"loading": "Laden...",
|
||||||
"error": "Ein Fehler ist aufgetreten"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,5 +173,11 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"error": "An error occurred"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user