This commit is contained in:
rgrgogu
2026-05-23 11:49:52 +08:00
11 changed files with 243 additions and 270 deletions
@@ -73,7 +73,7 @@ export default function ArchiveTaskListTable() {
const selectionActions = buildSelectionActions({
exportConfig,
onRestoreMany: (ids) => setRestoreIds(ids),
onBulkRestore: (ids) => setRestoreIds(ids),
getTableInstance: () => tableRefsRef.current.tableInstance,
});
@@ -129,7 +129,7 @@ export default function ArchiveTaskListTable() {
onOpenChange={(v) => !v && setRestoreIds(null)}
ids={restoreIds ?? []}
entityLabel="Task List"
onRestore={(ids) => bulkRestoreTaskLists(ids)}
onRestore={({ ids }) => bulkRestoreTaskLists(ids)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -1,4 +1,4 @@
import { useMemo, useRef, useState, useCallback } from "react";
import { useMemo, useRef, useState, useCallback, useEffect } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useAdminTask } from "@/contexts/AdminTaskContext";
@@ -31,7 +31,7 @@ export default function ArchivedTaskTable() {
const {
taskList, tasks, attributes, pagination, loading,
fetchArchivedTasks,
fetchTaskList, fetchArchivedTasks, fetchTaskFieldValues,
restoreTask,
bulkRestoreTasks,
} = useAdminTask();
@@ -47,6 +47,11 @@ export default function ArchivedTaskTable() {
tableInstance: null,
});
useEffect(() => {
fetchTaskList(taskListId);
fetchArchivedTasks(taskListId, { page: 1, limit: 10 });
}, [taskListId]);
const handleRefsReady = (refs) => {
tableRefsRef.current = refs;
};
@@ -82,19 +87,16 @@ export default function ArchivedTaskTable() {
}), [navigate]);
const toolbarActions = useMemo(() => buildToolbarActions({
taskListId,
navigate,
getSort: () => tableRefsRef.current.getSort(),
fetchArchivedTasks: handleFetch,
pagination,
exportConfig,
fetchArchivedTasks: handleFetch, taskListId,
navigate, pagination, exportConfig,
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
getTableInstance: () => tableRefsRef.current.tableInstance,
}), [taskListId, navigate, handleFetch, pagination, exportConfig]);
const selectionActions = useMemo(() => buildSelectionActions({
exportConfig,
onRestoreMany: (ids) => setRestoreIds(ids),
onBulkRestore: (ids) => setRestoreIds(ids), // ← was onRestoreMany
getTableInstance: () => tableRefsRef.current.tableInstance,
}), [exportConfig]);
@@ -182,7 +184,7 @@ export default function ArchivedTaskTable() {
pagination={pagination}
loading={loading}
onFetch={handleFetch}
onFetchFilterData={() => []}
onFetchFilterData={(field) => fetchTaskFieldValues(taskListId, field)}
onRefsReady={handleRefsReady}
renderFilterSheet={({ open, onOpenChange, column, attr, data, loading }) => (
<FilterSheet
@@ -219,7 +221,7 @@ export default function ArchivedTaskTable() {
onOpenChange={(v) => !v && setRestoreIds(null)}
ids={restoreIds ?? []}
entityLabel="Task"
onRestore={(ids) => bulkRestoreTasks(taskListId, ids)}
onRestore={({ ids }) => bulkRestoreTasks(taskListId, ids)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -72,12 +72,12 @@ export default function TaskListTable() {
};
// ── Export config ─────────────────────────────────────────────────────────
const exportConfig = {
const exportConfig = useMemo(() => ({
allData: taskLists,
attributes,
filename: `${getTimestamp()}_TaskLists`,
sheetName: "Task Lists",
};
}), [taskLists, attributes]);
// ── Row actions ───────────────────────────────────────────────────────────
const rowActions = buildRowActions({
@@ -90,6 +90,7 @@ export default function TaskListTable() {
// ── Toolbar ───────────────────────────────────────────────────────────────
const toolbarActions = buildToolbarActions({
fetchTaskLists, fetchArchivedTaskLists, pagination, navigate,
exportConfig,
showArchived,
onToggleArchived: handleToggleArchived,
getFilters: () => tableRefsRef.current.getFilters(),
@@ -101,10 +102,8 @@ export default function TaskListTable() {
const selectionActions = buildSelectionActions({
exportConfig,
showArchived,
onArchive: (row) => setArchiveTarget(row),
onArchiveMany: (ids) => setArchiveIds(ids),
onRestore: (row) => setRestoreTarget(row),
onRestoreMany: (ids) => setRestoreIds(ids),
onBulkArchive: (ids) => setArchiveIds(ids),
onBulkRestore: (ids) => setRestoreIds(ids),
getTableInstance: () => tableRefsRef.current.tableInstance,
});
@@ -150,7 +149,7 @@ export default function TaskListTable() {
entity={archiveTarget}
entityLabel="Task List"
getName={(r) => r?.name}
onArchive={(r) => archiveTaskList(r?.task_list_id)}
onArchive={(entity) => archiveTaskList(entity?.task_list_id)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -162,7 +161,7 @@ export default function TaskListTable() {
entity={restoreTarget}
entityLabel="Task List"
getName={(r) => r?.name}
onRestore={(r) => restoreTaskList(r?.task_list_id)}
onRestore={(entity) => restoreTaskList(entity?.task_list_id)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -173,7 +172,7 @@ export default function TaskListTable() {
onOpenChange={(v) => !v && setArchiveIds(null)}
ids={archiveIds ?? []}
entityLabel="Task List"
onArchive={(ids) => bulkArchiveTaskLists(ids)}
onArchive={({ ids }) => bulkArchiveTaskLists(ids)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -184,7 +183,7 @@ export default function TaskListTable() {
onOpenChange={(v) => !v && setRestoreIds(null)}
ids={restoreIds ?? []}
entityLabel="Task List"
onRestore={(ids) => bulkRestoreTaskLists(ids)}
onRestore={({ ids }) => bulkRestoreTaskLists(ids)}
loading={loading}
onSuccess={handleSuccess}
/>
@@ -1,31 +1,32 @@
import { RotateCcw } from "lucide-react";
import { Download, RotateCcw } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
import { Download } from "lucide-react";
export function buildSelectionActions({
exportConfig,
onRestoreMany,
onBulkRestore,
getTableInstance,
}) {
return [
{
key: "export-selected",
label: "Export Selected",
icon: <Download className="size-4" />,
onClick: () =>
label: "Export",
icon: <Download className="h-3.5 w-3.5" />,
onClick: (rows, table) =>
exportTableToExcel({
...exportConfig,
tableInstance: getTableInstance?.(),
selectedOnly: true,
selectedRows: rows,
tableInstance: table ?? getTableInstance?.(),
}),
},
{
key: "bulk-restore",
key: "restore-selected",
label: "Restore Selected",
icon: <RotateCcw className="size-4" />,
variant: "outline",
onClick: (selectedRows) =>
onRestoreMany(selectedRows.map((r) => r.task_list_id)),
icon: <RotateCcw className="h-3.5 w-3.5" />,
onClick: (rows) => {
const ids = rows.map((r) => r.task_list_id).filter(Boolean);
if (!ids.length) return;
onBulkRestore?.(ids);
},
},
];
}
@@ -39,7 +39,7 @@ export function buildSelectionActions({
),
className: "text-destructive border-destructive/40 hover:bg-destructive/10 hover:text-destructive",
onClick: (rows) => {
const ids = rows.map((r) => r.task_id).filter(Boolean);
const ids = rows.map((r) => r.task_list_id).filter(Boolean);
if (!ids.length) return;
showArchived ? onBulkRestore?.(ids) : onBulkArchive?.(ids);
},
@@ -1,34 +1,32 @@
// config/selection.config.jsx
import { Download, Archive, Trash2 } from "lucide-react";
import { Download, RotateCcw } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
/**
* @param {Object} deps
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
export function buildSelectionActions({ exportConfig, archiveUser, archiveUsers, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: <Download className="h-3.5 w-3.5" />,
onClick: (rows, table) =>
exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance() }),
},
{
key: "archive-selected",
label: "Archive",
icon: <Archive className="h-3.5 w-3.5" />,
className: "text-destructive border-destructive/40 hover:bg-destructive/10 hover:text-destructive",
onClick: (rows) => {
const ids = rows.map((r) => r.user_id);
ids.length === 1
? archiveUser(rows[0]) // opens single dialog
: archiveUsers(ids); // opens bulk dialog
},
hidden: (rows) => rows.every((r) => r.status === "archived"),
},
];
export function buildSelectionActions({
exportConfig,
onBulkRestore,
getTableInstance,
}) {
return [
{
key: "export-selected",
label: "Export",
icon: <Download className="h-3.5 w-3.5" />,
onClick: (rows, table) =>
exportTableToExcel({
...exportConfig,
selectedRows: rows,
tableInstance: table ?? getTableInstance?.(),
}),
},
{
key: "restore-selected",
label: "Restore Selected",
icon: <RotateCcw className="h-3.5 w-3.5" />,
onClick: (rows) => {
const ids = rows.map((r) => r.task_id).filter(Boolean);
if (!ids.length) return;
onBulkRestore?.(ids);
},
},
];
}
@@ -1,57 +1,39 @@
// ─── config/toolbar.config.jsx ────────────────────────────────────────────────
import { RefreshCw, Download, Plus, Archive } from "lucide-react";
import { RefreshCw, Download } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
/**
* @param {Object} deps
* @param {Function} deps.fetchTasks
* @param {string} deps.taskId
* @param {Object} deps.pagination
* @param {Object} deps.exportConfig
* @param {Function} deps.navigate
* @param {boolean} deps.showArchived
* @param {Function} deps.onToggleArchived
* @param {Function} deps.getFilters
* @param {Function} deps.getSort
* @param {Function} deps.getTableInstance
*/
export function buildToolbarActions({
fetchTasks,
taskId,
pagination,
exportConfig,
navigate,
showArchived,
onToggleArchived,
getFilters,
getSort,
getTableInstance,
fetchArchivedTasks,
taskListId,
pagination,
exportConfig,
getFilters,
getSort,
getTableInstance,
}) {
return [
{
key: "refresh",
type: "button",
icon: <RefreshCw className="size-4" />,
label: "Refresh",
onClick: () =>
fetchTasks(taskId, {
page: 1,
limit: pagination?.limit ?? 10,
filters: getFilters?.() ?? [],
sort: getSort?.() ?? [],
archived: showArchived,
}),
},
{
key: "export",
type: "button",
icon: <Download className="size-4" />,
label: "Export",
onClick: (table) =>
exportTableToExcel({
...exportConfig,
tableInstance: table ?? getTableInstance?.(),
}),
},
];
return [
{
key: "refresh",
type: "button",
icon: <RefreshCw className="size-4" />,
label: "Refresh",
onClick: () =>
fetchArchivedTasks(taskListId, {
page: 1,
limit: pagination?.limit ?? 10,
filters: getFilters?.() ?? [],
sort: getSort?.() ?? [],
}),
},
{
key: "export",
type: "button",
icon: <Download className="size-4" />,
label: "Export",
onClick: (table) =>
exportTableToExcel({
...exportConfig,
tableInstance: table ?? getTableInstance?.(),
}),
},
];
}
@@ -1,34 +1,39 @@
// config/selection.config.jsx
import { Download, Archive, Trash2 } from "lucide-react";
import { Download, Archive, RotateCcw } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
/**
* @param {Object} deps
* @param {Object} deps.exportConfig { allData, attributes, filename, sheetName }
* @param {Function} deps.archiveUser Archive handler from useManagement
* @param {Function} deps.deleteUser Delete handler from useManagement
*/
export function buildSelectionActions({ exportConfig, archiveUser, archiveUsers, getTableInstance }) {
return [
{
key: "export-selected",
label: "Export",
icon: <Download className="h-3.5 w-3.5" />,
onClick: (rows, table) =>
exportTableToExcel({ ...exportConfig, selectedRows: rows, tableInstance: table ?? getTableInstance() }),
},
{
key: "archive-selected",
label: "Archive",
icon: <Archive className="h-3.5 w-3.5" />,
className: "text-destructive border-destructive/40 hover:bg-destructive/10 hover:text-destructive",
onClick: (rows) => {
const ids = rows.map((r) => r.user_id);
ids.length === 1
? archiveUser(rows[0]) // opens single dialog
: archiveUsers(ids); // opens bulk dialog
},
hidden: (rows) => rows.every((r) => r.status === "archived"),
},
];
export function buildSelectionActions({
exportConfig,
showArchived,
onBulkArchive,
onBulkRestore,
getTableInstance,
}) {
return [
{
key: "export-selected",
label: "Export",
icon: <Download className="h-3.5 w-3.5" />,
onClick: (rows, table) =>
exportTableToExcel({
...exportConfig,
selectedRows: rows,
tableInstance: table ?? getTableInstance?.(),
}),
},
{
key: showArchived ? "restore-selected" : "archive-selected",
label: showArchived ? "Restore" : "Archive",
icon: showArchived ? (
<RotateCcw className="h-3.5 w-3.5" />
) : (
<Archive className="h-3.5 w-3.5" />
),
className: "text-destructive border-destructive/40 hover:bg-destructive/10 hover:text-destructive",
onClick: (rows) => {
const ids = rows.map((r) => r.task_id).filter(Boolean);
if (!ids.length) return;
showArchived ? onBulkRestore?.(ids) : onBulkArchive?.(ids);
},
},
];
}
@@ -1,76 +1,63 @@
// ─── config/toolbar.config.jsx ────────────────────────────────────────────────
import { RefreshCw, Download, Plus, Archive } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
/**
* @param {Object} deps
* @param {Function} deps.fetchTasks
* @param {string} deps.taskListId
* @param {Object} deps.pagination
* @param {Object} deps.exportConfig
* @param {Function} deps.navigate
* @param {boolean} deps.showArchived
* @param {Function} deps.onToggleArchived
* @param {Function} deps.getFilters
* @param {Function} deps.getSort
* @param {Function} deps.getTableInstance
*/
export function buildToolbarActions({
fetchTasks,
taskListId,
pagination,
exportConfig,
navigate,
showArchived,
onToggleArchived,
getFilters,
getSort,
getTableInstance,
fetchTasks,
fetchArchivedTasks,
taskListId,
pagination,
exportConfig,
navigate,
showArchived,
onToggleArchived,
getFilters,
getSort,
getTableInstance,
}) {
return [
{
key: "refresh",
type: "button",
icon: <RefreshCw className="size-4" />,
label: "Refresh",
onClick: () =>
fetchTasks(taskId, {
page: 1,
limit: pagination?.limit ?? 10,
filters: getFilters?.() ?? [],
sort: getSort?.() ?? [],
archived: showArchived,
}),
},
{
key: "export",
type: "button",
icon: <Download className="size-4" />,
label: "Export",
onClick: (table) =>
exportTableToExcel({
...exportConfig,
tableInstance: table ?? getTableInstance?.(),
}),
},
{
key: "add-task",
type: "button",
icon: <Plus className="size-4" />,
label: "Create Task",
variant: "default",
className: "text-primary-foreground",
onClick: () => navigate(`/admin/taskList/${taskListId}/tasks/create`),
},
{
key: "toggle-archived-task",
type: "button",
icon: <Archive className="size-4" />,
label: showArchived ? "Active Tasks" : "Archived Tasks",
variant: "secondary",
className: "border border-border",
// onClick: onToggleArchived,
onClick: () => navigate(`/admin/taskList/${taskListId}/tasks/archived`),
},
];
return [
{
key: "refresh",
type: "button",
icon: <RefreshCw className="size-4" />,
label: "Refresh",
onClick: () => {
const fetcher = showArchived ? fetchArchivedTasks : fetchTasks;
fetcher(taskListId, {
page: 1,
limit: pagination?.limit ?? 10,
filters: getFilters?.() ?? [],
sort: getSort?.() ?? [],
});
},
},
{
key: "export",
type: "button",
icon: <Download className="size-4" />,
label: "Export",
onClick: (table) =>
exportTableToExcel({
...exportConfig,
tableInstance: table ?? getTableInstance?.(),
}),
},
{
key: "add-task",
type: "button",
icon: <Plus className="size-4" />,
label: "Create Task",
variant: "default",
className: "text-primary-foreground",
onClick: () => navigate(`/admin/taskList/${taskListId}/tasks/create`),
},
{
key: "toggle-archived-task",
type: "button",
icon: <Archive className="size-4" />,
label: showArchived ? "Active Tasks" : "Archived Tasks",
variant: "secondary",
className: "border border-border",
onClick: () => navigate(`/admin/taskList/${taskListId}/tasks/archived`),
},
];
}
@@ -1,24 +1,10 @@
// ─── config/toolbar.config.jsx ────────────────────────────────────────────────
// ─── config/task_list/toolbar.config.jsx ─────────────────────────────────────
import { RefreshCw, Download, Plus, Archive } from "lucide-react";
import { exportTableToExcel } from "@/utils/excel.util";
/**
* @param {Object} deps
* @param {Function} deps.fetchTasks
* @param {string} deps.taskListId
* @param {Object} deps.pagination
* @param {Object} deps.exportConfig
* @param {Function} deps.navigate
* @param {boolean} deps.showArchived
* @param {Function} deps.onToggleArchived
* @param {Function} deps.getFilters
* @param {Function} deps.getSort
* @param {Function} deps.getTableInstance
*/
export function buildToolbarActions({
fetchTasks,
fetchTaskLists,
fetchArchivedTaskLists,
taskListId,
pagination,
exportConfig,
navigate,
@@ -34,14 +20,15 @@ export function buildToolbarActions({
type: "button",
icon: <RefreshCw className="size-4" />,
label: "Refresh",
onClick: () =>
fetchTasks(taskListId, {
onClick: () => {
const fetcher = showArchived ? fetchArchivedTaskLists : fetchTaskLists;
fetcher({
page: 1,
limit: pagination?.limit ?? 10,
filters: getFilters?.() ?? [],
sort: getSort?.() ?? [],
archived: showArchived,
}),
});
},
},
{
key: "export",
@@ -55,7 +42,7 @@ export function buildToolbarActions({
}),
},
{
key: "add-task",
key: "add-task-list",
type: "button",
icon: <Plus className="size-4" />,
label: "Create Task List",
@@ -64,10 +51,10 @@ export function buildToolbarActions({
onClick: () => navigate(`/admin/taskList/create`),
},
{
key: "toggle-archived-task",
key: "toggle-archived",
type: "button",
icon: <Archive className="size-4" />,
label: showArchived ? "Active Task Lists" : "Archived Task List",
label: showArchived ? "Active Task Lists" : "Archived Task Lists",
variant: "secondary",
className: "border border-border",
onClick: () => navigate("/admin/taskList/archived"),
@@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
import { useAdminTask } from '@/contexts/AdminTaskContext';
import DataTable from '@/components/generic/Table/DataTable';
import { FilterSheet } from "@/components/generic/Sheet/FilterSheet";
import { ArchiveDialog } from '@/components/generic/Dialogs/ArchiveDialog';
import { RestoreDialog } from '@/components/generic/Dialogs/RestoreDialog';
import { Button } from '@/components/ui/button';
@@ -23,6 +24,7 @@ import { buildDataColumns, columnPinning } from '@/modules/admin/config/task_lis
import { buildToolbarActions } from '@/modules/admin/config/task_list/task/toolbar.config';
import { buildSelectionActions } from '@/modules/admin/config/task_list/task/selection.config';
import { buildRowActions } from '@/modules/admin/config/task_list/task/rowActions.config';
import { getTimestamp } from '@/utils/timestamp.util';
export default function Tasks() {
const navigate = useNavigate();
@@ -81,20 +83,30 @@ export default function Tasks() {
});
};
const rowActions = useMemo(() => buildRowActions({
const exportConfig = useMemo(() => ({
allData: tasks,
attributes,
filename: `${getTimestamp()}_Tasks`,
sheetName: "Tasks",
}), [tasks, attributes]);
const rowActions = buildRowActions({
navigate,
onArchive: (row) => setArchiveTarget(row),
onRestore: (row) => setRestoreTarget(row),
showArchived,
}), [navigate, showArchived]);
});
const toolbarActions = buildToolbarActions({
fetchTasks, fetchArchivedTasks, taskListId, pagination, navigate,
fetchTasks, fetchArchivedTasks, taskListId,
pagination, navigate, exportConfig,
showArchived, onToggleArchived: handleToggleArchived,
getFilters: () => tableRefsRef.current.getFilters(),
getSort: () => tableRefsRef.current.getSort(),
getTableInstance: () => tableRefsRef.current.tableInstance,
});
// ── Selection ─────────────────────────────────────────────────────────────
const selectionActions = buildSelectionActions({
showArchived,
onBulkArchive: (ids) => setBulkArchiveIds(ids),
@@ -214,11 +226,21 @@ export default function Tasks() {
pagination={pagination}
loading={loading}
onFetch={handleFetch}
onFetchFilterData={fetchTaskFieldValues}
onFetchFilterData={(field) => fetchTaskFieldValues(taskListId, field)}
toolbarActions={toolbarActions}
selectionActions={selectionActions}
columnPinning={columnPinning}
onRefsReady={handleRefsReady}
renderFilterSheet={({ open, onOpenChange, column, attr, data, loading }) => (
<FilterSheet
open={open}
onOpenChange={onOpenChange}
column={column}
attr={attr}
data={data}
loading={loading}
/>
)}
/>
{/* ── All Groups Dialog ─────────────────────────────────────────── */}
@@ -251,11 +273,9 @@ export default function Tasks() {
entity={archiveTarget}
entityLabel="Task"
getName={(r) => r?.name}
onArchive={async (r) => {
const ok = await archiveTask(taskListId, r?.task_id);
if (ok) { setArchiveTarget(null); afterMutation(); }
}}
onArchive={(entity) => archiveTask(taskListId, entity?.task_id)}
loading={loading}
onSuccess={afterMutation}
/>
{/* ── Single restore ────────────────────────────────────────────── */}
@@ -265,39 +285,31 @@ export default function Tasks() {
entity={restoreTarget}
entityLabel="Task"
getName={(r) => r?.name}
onRestore={async (r) => {
const ok = await restoreTask(taskListId, r?.task_id);
if (ok) { setRestoreTarget(null); afterMutation(); }
}}
onRestore={(entity) => restoreTask(taskListId, entity?.task_id)}
loading={loading}
onSuccess={afterMutation}
/>
{/* ── Bulk archive ──────────────────────────────────────────────── */}
<ArchiveDialog
open={!!bulkArchiveIds}
onOpenChange={(v) => !v && setBulkArchiveIds(null)}
entity={bulkArchiveIds}
entityLabel={`${bulkArchiveIds?.length ?? 0} Task(s)`}
getName={() => `${bulkArchiveIds?.length ?? 0} task(s)`}
onArchive={async () => {
const ok = await bulkArchiveTasks(taskListId, bulkArchiveIds);
if (ok) { setBulkArchiveIds(null); afterMutation(); }
}}
ids={bulkArchiveIds ?? []}
entityLabel="Task"
onArchive={({ ids }) => bulkArchiveTasks(taskListId, ids)}
loading={loading}
onSuccess={afterMutation}
/>
{/* ── Bulk restore ──────────────────────────────────────────────── */}
<RestoreDialog
open={!!bulkRestoreIds}
onOpenChange={(v) => !v && setBulkRestoreIds(null)}
entity={bulkRestoreIds}
entityLabel={`${bulkRestoreIds?.length ?? 0} Task(s)`}
getName={() => `${bulkRestoreIds?.length ?? 0} task(s)`}
onRestore={async () => {
const ok = await bulkRestoreTasks(taskListId, bulkRestoreIds);
if (ok) { setBulkRestoreIds(null); afterMutation(); }
}}
ids={bulkRestoreIds ?? []}
entityLabel="Task"
onRestore={({ ids }) => bulkRestoreTasks(taskListId, ids)}
loading={loading}
onSuccess={afterMutation}
/>
</div>