From 983dbb01b52cb26503119098942dbbbd1323b913 Mon Sep 17 00:00:00 2001 From: Arthur Belleville Date: Thu, 16 Apr 2026 12:02:18 +0200 Subject: [PATCH] fix: move task deletion to inline actions --- .../src/pages/tablo-details.layout.test.tsx | 5 +- apps/main/src/pages/tasks.test.tsx | 92 +++++++++++++++++++ apps/main/src/pages/tasks.tsx | 26 +++++- .../tablo-views/src/TabloTasksSection.tsx | 1 + .../src/components/kanban/KanbanBoard.tsx | 3 + .../src/components/kanban/KanbanColumn.tsx | 3 + .../src/components/kanban/KanbanTaskCard.tsx | 26 +++++- .../src/components/kanban/TaskModal.tsx | 17 ---- 8 files changed, 147 insertions(+), 26 deletions(-) create mode 100644 apps/main/src/pages/tasks.test.tsx diff --git a/apps/main/src/pages/tablo-details.layout.test.tsx b/apps/main/src/pages/tablo-details.layout.test.tsx index a327343..1e36a04 100644 --- a/apps/main/src/pages/tablo-details.layout.test.tsx +++ b/apps/main/src/pages/tablo-details.layout.test.tsx @@ -281,7 +281,7 @@ describe("TabloDetailsPage overview layout", () => { confirmSpy.mockRestore(); }); - it("deletes a task from the task modal", async () => { + it("deletes a task from the inline kanban action", async () => { const user = userEvent.setup(); renderWithProviders(, { @@ -290,8 +290,7 @@ describe("TabloDetailsPage overview layout", () => { }); await user.click(screen.getByRole("button", { name: "Tâches" })); - await user.click(screen.getByText("Task A")); - await user.click(screen.getByRole("button", { name: "Supprimer la tâche" })); + await user.click(screen.getByRole("button", { name: "Supprimer la tâche Task A" })); expect(mutateDeleteTask).toHaveBeenCalledWith("task-1"); }); diff --git a/apps/main/src/pages/tasks.test.tsx b/apps/main/src/pages/tasks.test.tsx new file mode 100644 index 0000000..e421f12 --- /dev/null +++ b/apps/main/src/pages/tasks.test.tsx @@ -0,0 +1,92 @@ +import { screen } from "@testing-library/react"; +import userEvent from "@testing-library/user-event"; +import { beforeEach, describe, expect, it, vi } from "vitest"; +import { TasksPage } from "./tasks"; +import { renderWithProviders } from "../utils/testHelpers"; + +const mutateUpdateTask = vi.fn(); +const mutateDeleteTask = vi.fn(); + +vi.mock("../hooks/tablos", () => ({ + useTablosList: () => ({ + data: [ + { + id: "tablo-1", + name: "Projet Alpha", + color: "bg-blue-500", + }, + ], + isLoading: false, + }), +})); + +vi.mock("../hooks/tasks", () => ({ + useAllTasks: () => ({ + data: [ + { + id: "task-1", + tablo_id: "tablo-1", + title: "Task A", + description: "Description", + status: "todo", + due_date: null, + assignee_id: "123", + assignee_name: "John Doe", + assignee_avatar: null, + tablos: { + id: "tablo-1", + name: "Projet Alpha", + color: "bg-blue-500", + }, + }, + ], + isLoading: false, + }), + useUpdateTask: () => ({ + mutate: mutateUpdateTask, + }), + useDeleteTask: () => ({ + mutate: mutateDeleteTask, + }), +})); + +vi.mock("@xtablo/tablo-views", async (importOriginal) => { + const actual = await importOriginal(); + return { + ...actual, + GanttChart: () =>
, + TaskModal: () => null, + }; +}); + +describe("TasksPage", () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + it("deletes a task from the inline kanban action", async () => { + const user = userEvent.setup(); + + renderWithProviders(, { + route: "/tasks?view=kanban", + path: "/tasks", + }); + + await user.click(screen.getByRole("button", { name: "Supprimer la tâche Task A" })); + + expect(mutateDeleteTask).toHaveBeenCalledWith("task-1"); + }); + + it("deletes a task from the inline list action", async () => { + const user = userEvent.setup(); + + renderWithProviders(, { + route: "/tasks?view=aggregated", + path: "/tasks", + }); + + await user.click(screen.getByRole("button", { name: "Supprimer la tâche Task A" })); + + expect(mutateDeleteTask).toHaveBeenCalledWith("task-1"); + }); +}); diff --git a/apps/main/src/pages/tasks.tsx b/apps/main/src/pages/tasks.tsx index 40c6568..97ff27b 100644 --- a/apps/main/src/pages/tasks.tsx +++ b/apps/main/src/pages/tasks.tsx @@ -33,6 +33,7 @@ import { Sparkles, Star, Sun, + Trash2Icon, UserIcon, Waves, Zap, @@ -42,7 +43,7 @@ import { useTranslation } from "react-i18next"; import { useNavigate, useSearchParams } from "react-router-dom"; import { twMerge } from "tailwind-merge"; import { useTablosList } from "../hooks/tablos"; -import { useAllTasks, useUpdateTask } from "../hooks/tasks"; +import { useAllTasks, useDeleteTask, useUpdateTask } from "../hooks/tasks"; import { useUser } from "../providers/UserStoreProvider"; type TaskStatus = "all" | "todo" | "in_progress" | "in_review" | "done"; @@ -134,6 +135,7 @@ export function TasksPage() { // Mutation for updating task status const updateTaskMutation = useUpdateTask(); + const deleteTaskMutation = useDeleteTask(); const openTaskModal = (dueDate?: Date) => { setTaskModalInitialDueDate(dueDate ? new Date(dueDate) : undefined); @@ -556,6 +558,17 @@ export function TasksPage() { ))} +
{formattedDate && ( @@ -878,6 +891,17 @@ export function TasksPage() { ))} + ); diff --git a/packages/tablo-views/src/TabloTasksSection.tsx b/packages/tablo-views/src/TabloTasksSection.tsx index 350a38c..90eef07 100644 --- a/packages/tablo-views/src/TabloTasksSection.tsx +++ b/packages/tablo-views/src/TabloTasksSection.tsx @@ -262,6 +262,7 @@ export const TabloTasksSection = ({ etapes={etapes} etapeTitles={etapeTitleMap} onTaskClick={handleTaskClick} + onDeleteTask={onDeleteTask} onAddTask={handleAddTask} onAddTaskInline={handleCreateTask} onTaskMove={handleTaskMove} diff --git a/packages/tablo-views/src/components/kanban/KanbanBoard.tsx b/packages/tablo-views/src/components/kanban/KanbanBoard.tsx index 35293c7..0522fd2 100644 --- a/packages/tablo-views/src/components/kanban/KanbanBoard.tsx +++ b/packages/tablo-views/src/components/kanban/KanbanBoard.tsx @@ -14,6 +14,7 @@ interface KanbanBoardProps { etapes: Etape[]; etapeTitles: Record; onTaskClick: (task: KanbanTask) => void; + onDeleteTask?: (taskId: string) => void; onAddTask: (status: TaskStatus) => void; onAddTaskInline: (task: { title: string; @@ -30,6 +31,7 @@ export const KanbanBoard = ({ members, etapes, onTaskClick, + onDeleteTask, onAddTask, onAddTaskInline, onTaskMove, @@ -63,6 +65,7 @@ export const KanbanBoard = ({ members={members} etapes={etapes} onTaskClick={onTaskClick} + onDeleteTask={onDeleteTask} onAddTask={onAddTask} onAddTaskInline={onAddTaskInline} onDragStart={handleDragStart} diff --git a/packages/tablo-views/src/components/kanban/KanbanColumn.tsx b/packages/tablo-views/src/components/kanban/KanbanColumn.tsx index 516db0a..a913ae6 100644 --- a/packages/tablo-views/src/components/kanban/KanbanColumn.tsx +++ b/packages/tablo-views/src/components/kanban/KanbanColumn.tsx @@ -15,6 +15,7 @@ interface KanbanColumnProps { members: TabloMember[]; etapes: Etape[]; onTaskClick: (task: KanbanTask) => void; + onDeleteTask?: (taskId: string) => void; onAddTask: (status: KanbanColumnType["status"]) => void; onAddTaskInline: (task: { title: string; @@ -33,6 +34,7 @@ export const KanbanColumn = ({ members, etapes, onTaskClick, + onDeleteTask, onAddTask, onAddTaskInline, onDragStart, @@ -84,6 +86,7 @@ export const KanbanColumn = ({ task={task} etapeTitle={etape?.title} onClick={() => onTaskClick(task)} + onDelete={onDeleteTask ? () => onDeleteTask(task.id) : undefined} /> ); diff --git a/packages/tablo-views/src/components/kanban/KanbanTaskCard.tsx b/packages/tablo-views/src/components/kanban/KanbanTaskCard.tsx index ba58c74..8ceaae9 100644 --- a/packages/tablo-views/src/components/kanban/KanbanTaskCard.tsx +++ b/packages/tablo-views/src/components/kanban/KanbanTaskCard.tsx @@ -1,11 +1,12 @@ import type { KanbanTask } from "@xtablo/shared-types"; import { TypographyH4, TypographyMuted } from "@xtablo/ui/components/typography"; -import { CalendarIcon, User } from "lucide-react"; +import { CalendarIcon, Trash2Icon, User } from "lucide-react"; interface KanbanTaskCardProps { task: KanbanTask; etapeTitle?: string; onClick: () => void; + onDelete?: () => void; } function formatDueDate(dateStr: string): string { @@ -23,7 +24,7 @@ function isOverdue(dateStr: string): boolean { return due < today; } -export const KanbanTaskCard = ({ task, etapeTitle, onClick }: KanbanTaskCardProps) => { +export const KanbanTaskCard = ({ task, etapeTitle, onClick, onDelete }: KanbanTaskCardProps) => { const overdue = task.due_date && task.status !== "done" && isOverdue(task.due_date); return ( @@ -31,9 +32,24 @@ export const KanbanTaskCard = ({ task, etapeTitle, onClick }: KanbanTaskCardProp onClick={onClick} className="bg-card border border-border rounded-lg p-3 hover:shadow-md transition-shadow cursor-pointer group" > - - {task.title} - +
+ + {task.title} + + {onDelete && ( + + )} +
{task.description && ( diff --git a/packages/tablo-views/src/components/kanban/TaskModal.tsx b/packages/tablo-views/src/components/kanban/TaskModal.tsx index b87066b..aebfe4f 100644 --- a/packages/tablo-views/src/components/kanban/TaskModal.tsx +++ b/packages/tablo-views/src/components/kanban/TaskModal.tsx @@ -58,7 +58,6 @@ export const TaskModal = ({ initialDueDate, onCreateTask, onUpdateTask, - onDeleteTask, }: TaskModalProps) => { const [title, setTitle] = useState(""); const [description, setDescription] = useState(""); @@ -140,12 +139,6 @@ export const TaskModal = ({ onClose(); }; - const handleDelete = () => { - if (!taskId || !task) return; - onDeleteTask?.(task.id); - onClose(); - }; - if (!isOpen) return null; return ( @@ -261,16 +254,6 @@ export const TaskModal = ({ {/* Actions */}
- {taskId && task && ( - - )}