diff --git a/ui/web/src/pages/teams/task-sections/task-list.tsx b/ui/web/src/pages/teams/task-sections/task-list.tsx index 134d2c72..6aac4d86 100644 --- a/ui/web/src/pages/teams/task-sections/task-list.tsx +++ b/ui/web/src/pages/teams/task-sections/task-list.tsx @@ -4,6 +4,8 @@ import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { useTranslation } from "react-i18next"; import { ConfirmDeleteDialog } from "@/components/shared/confirm-delete-dialog"; +import { Pagination } from "@/components/shared/pagination"; +import { usePagination } from "@/hooks/use-pagination"; import type { TeamTaskData, TeamTaskComment, TeamTaskEvent, TeamTaskAttachment } from "@/types/team"; import type { TeamMemberData } from "@/types/team"; import { taskStatusBadgeVariant, isTerminalStatus } from "./task-utils"; @@ -36,10 +38,12 @@ export function TaskList({ const [deleting, setDeleting] = useState(false); const taskLookup = useMemo(() => buildTaskLookup(tasks), [tasks]); const memberLookup = useMemo(() => buildMemberLookup(members), [members]); + const { pageItems, pagination, setPage, setPageSize } = usePagination(tasks, { defaultPageSize: 20 }); - const terminalTaskIds = useMemo( - () => tasks.filter((t) => isTerminalStatus(t.status)).map((t) => t.id), - [tasks], + // "Select all" applies to terminal tasks on the current page only. + const pageTerminalIds = useMemo( + () => pageItems.filter((t) => isTerminalStatus(t.status)).map((t) => t.id), + [pageItems], ); // Clear selection when tasks change (e.g. after delete/refresh). @@ -61,12 +65,18 @@ export function TaskList({ }, []); const toggleSelectAll = useCallback(() => { - setSelectedIds((prev) => - prev.size === terminalTaskIds.length && terminalTaskIds.length > 0 - ? new Set() - : new Set(terminalTaskIds), - ); - }, [terminalTaskIds]); + setSelectedIds((prev) => { + const allPageSelected = pageTerminalIds.length > 0 && pageTerminalIds.every((id) => prev.has(id)); + if (allPageSelected) { + // Deselect current page's terminal tasks, keep other pages' selections. + const next = new Set(prev); + for (const id of pageTerminalIds) next.delete(id); + return next; + } + // Add current page's terminal tasks to selection. + return new Set([...prev, ...pageTerminalIds]); + }); + }, [pageTerminalIds]); const handleBulkDelete = useCallback(async () => { if (!deleteTasksBulk || selectedIds.size === 0) return; @@ -101,9 +111,9 @@ export function TaskList({ deleteTask(teamId, taskId); }; - const hasBulkDelete = !!deleteTasksBulk && terminalTaskIds.length > 0; - const allSelected = terminalTaskIds.length > 0 && selectedIds.size === terminalTaskIds.length; - const someSelected = selectedIds.size > 0 && !allSelected; + const hasBulkDelete = !!deleteTasksBulk && pageTerminalIds.length > 0; + const allPageSelected = pageTerminalIds.length > 0 && pageTerminalIds.every((id) => selectedIds.has(id)); + const someSelected = selectedIds.size > 0 && !allPageSelected; const gridCols = hasBulkDelete ? "grid-cols-[36px_70px_1fr_90px_100px_60px_40px]" @@ -135,7 +145,7 @@ export function TaskList({ { if (el) el.indeterminate = someSelected; }} onChange={toggleSelectAll} /> @@ -148,7 +158,7 @@ export function TaskList({ {t("tasks.columns.priority")} - {tasks.map((task) => { + {pageItems.map((task) => { const ownerEmoji = task.owner_agent_id && emojiLookup?.get(task.owner_agent_id); const isTerminal = isTerminalStatus(task.status); const isChecked = selectedIds.has(task.id); @@ -218,6 +228,15 @@ export function TaskList({ })} + + {selectedTask && (