diff --git a/ui/web/src/pages/teams/board/board-container.tsx b/ui/web/src/pages/teams/board/board-container.tsx index bac87385..f1317eaf 100644 --- a/ui/web/src/pages/teams/board/board-container.tsx +++ b/ui/web/src/pages/teams/board/board-container.tsx @@ -9,6 +9,7 @@ import { BoardToolbar } from "./board-toolbar"; import { KanbanBoard } from "./kanban-board"; import { TaskDetailDialog } from "../task-sections/task-detail-dialog"; import { TaskList } from "../task-sections"; +import { ConfirmDialog } from "@/components/shared/confirm-dialog"; import type { TeamTaskData, TeamTaskComment, TeamTaskEvent, TeamTaskAttachment, TeamMemberData, ScopeEntry, @@ -72,6 +73,8 @@ export const BoardContainer = memo(function BoardContainer({ const [statusFilter, setStatusFilter] = useState("all"); const [selectedScope, setSelectedScope] = useState(null); const [selectedTask, setSelectedTask] = useState(null); + const [deleteTargetId, setDeleteTargetId] = useState(null); + const [singleDeleting, setSingleDeleting] = useState(false); // Lookups for name resolution const taskLookup = useMemo(() => buildTaskLookup(tasks), [tasks]); @@ -227,16 +230,24 @@ export const BoardContainer = memo(function BoardContainer({ const deleteTaskRef = useRef(deleteTask); deleteTaskRef.current = deleteTask; - const handleDeleteTask = useCallback(async (taskId: string) => { + const handleDeleteTask = useCallback((taskId: string) => { if (!deleteTaskRef.current) return; - if (!window.confirm(t("tasks.deleteConfirm"))) return; + setDeleteTargetId(taskId); + }, []); + + const confirmDeleteTask = useCallback(async () => { + if (!deleteTaskRef.current || !deleteTargetId) return; + setSingleDeleting(true); try { - await deleteTaskRef.current(teamId, taskId); + await deleteTaskRef.current(teamId, deleteTargetId); toast.success(t("toast.taskDeleted")); + setDeleteTargetId(null); } catch { toast.error(t("toast.failedDeleteTask")); + } finally { + setSingleDeleting(false); } - }, [teamId, t]); + }, [teamId, deleteTargetId, t]); return (
@@ -280,6 +291,17 @@ export const BoardContainer = memo(function BoardContainer({ )}
+ !v && setDeleteTargetId(null)} + title={t("tasks.delete")} + description={t("tasks.deleteConfirm")} + confirmLabel={t("tasks.delete")} + variant="destructive" + onConfirm={confirmDeleteTask} + loading={singleDeleting} + /> + {selectedTask && ( >(new Set()); const [confirmOpen, setConfirmOpen] = useState(false); const [deleting, setDeleting] = useState(false); + const [deleteTargetId, setDeleteTargetId] = useState(null); + const [singleDeleting, setSingleDeleting] = useState(false); const taskLookup = useMemo(() => buildTaskLookup(tasks), [tasks]); const memberLookup = useMemo(() => buildMemberLookup(members), [members]); const { pageItems, pagination, setPage, setPageSize } = usePagination(tasks, { defaultPageSize: 20 }); @@ -107,10 +110,20 @@ export function TaskList({ const handleDelete = (e: React.MouseEvent, taskId: string) => { e.stopPropagation(); if (!deleteTask) return; - if (!window.confirm(t("tasks.deleteConfirm"))) return; - deleteTask(teamId, taskId); + setDeleteTargetId(taskId); }; + const handleSingleDelete = useCallback(async () => { + if (!deleteTask || !deleteTargetId) return; + setSingleDeleting(true); + try { + await deleteTask(teamId, deleteTargetId); + setDeleteTargetId(null); + } finally { + setSingleDeleting(false); + } + }, [deleteTask, teamId, deleteTargetId]); + const hasBulkDelete = !!deleteTasksBulk && pageTerminalIds.length > 0; const allPageSelected = pageTerminalIds.length > 0 && pageTerminalIds.every((id) => selectedIds.has(id)); const someSelected = selectedIds.size > 0 && !allPageSelected; @@ -255,6 +268,17 @@ export function TaskList({ /> )} + !v && setDeleteTargetId(null)} + title={t("tasks.delete")} + description={t("tasks.deleteConfirm")} + confirmLabel={t("tasks.delete")} + variant="destructive" + onConfirm={handleSingleDelete} + loading={singleDeleting} + /> +