fix(project): detail all task list fix

This commit is contained in:
zhangheng
2026-02-26 19:26:46 +08:00
parent 77a9571b72
commit 4c72bbe55d
9 changed files with 780 additions and 166 deletions

View File

@@ -1,32 +1,114 @@
"use client"
import { ProjectDetail } from "@/components/label/api/project/typing"
import { getTaskList } from "@/components/label/api/task"
import { getTaskList, taskRelease } from "@/components/label/api/task"
import { Task } from "@/components/label/api/task/typing"
import { usePermissionStore } from "@/components/label/store/auth"
import {
useAllTaskStore,
usePermissionStore,
} from "@/components/label/store/auth"
import { TaskStatusOpts } from "@/components/label/utils/constants"
TaskStatusEnum,
TaskStatusOpts,
} from "@/components/label/utils/constants"
import {
ActionIcon,
Button,
Collapse,
Flex,
Group,
MultiSelect,
Paper,
Select,
SimpleGrid,
Stack,
Text,
TextInput,
} from "@mantine/core"
import { modals } from "@mantine/modals"
import { notifications } from "@mantine/notifications"
import { IconArrowsShuffle, IconRoute, IconTrashX } from "@tabler/icons-react"
import {
IconChevronDown,
IconChevronUp,
IconRefresh,
IconSearch,
} from "@tabler/icons-react"
import { DataTable, DataTableColumn } from "mantine-datatable"
import { useSearchParams } from "next/navigation"
import { useCallback, useEffect, useMemo, useState } from "react"
import * as XLSX from "xlsx-js-style"
import DispatchModal from "./components/DispatchModal"
import ReleaseModal from "./components/ReleaseModal"
import TaskStatusTag from "./components/TaskStatusTag"
import WorkflowModal from "./components/WorkflowModal"
interface FilterFormRecord {
search_id: string
search_status: string
rejected?: string
current_uid: string[]
label_user: string[]
review_user1: string[]
review_user2: string[]
}
function createInitialFilters(): FilterFormRecord {
return {
search_id: "",
search_status: "0",
rejected: undefined,
current_uid: [],
label_user: [],
review_user1: [],
review_user2: [],
}
}
export interface selectedDataProps {
status: number
uid: number | null
reject: boolean
}
const checkSelectedData: (
keys: React.Key[],
records: Task.DataProps[]
) => {
flag: boolean
resultData: selectedDataProps
} = (keys: React.Key[], records: Task.DataProps[]) => {
const checkData = records.filter((item: any) => keys.includes(item.id))
const initStatus = checkData[0].label_status
const initReject = checkData[0].rejected
const initCurrentUid = checkData[0].current_uid || null
const initCurrentName = checkData[0].current_username || ""
if (checkData.length === 1)
return {
flag: true,
resultData: {
uid: initCurrentUid,
status: initStatus,
reject: initReject,
},
}
let statusFlag = true
let currentFlag = true
let rejectFlag = true
checkData.forEach((item: any) => {
const s = item.label_status
const c = item.current_username || ""
const r = item.rejected
if (s !== initStatus) statusFlag = false
if (c !== initCurrentName) currentFlag = false
if (r !== initReject) rejectFlag = false
})
return {
flag: statusFlag && currentFlag && rejectFlag,
resultData: {
uid: initCurrentUid,
status: initStatus,
reject: initReject,
},
}
}
export default function TaskTableContainer(props: {
info: ProjectDetail.DataProps | undefined
userEnums: {
@@ -39,33 +121,37 @@ export default function TaskTableContainer(props: {
const { info, userEnums } = props
false && console.log(info)
const { user_id } = usePermissionStore()
const urlParams = useSearchParams()
const id = urlParams.get("id")
const projectId =
typeof id === "string" && !isNaN(Number(id)) ? Number(id) : -1
const currentUid = usePermissionStore((s) => s.user_id)
false && console.log(currentUid)
const [total, setTotal] = useState(0)
const { params, total, setPagination, resetParams, setTotal } =
useAllTaskStore()
const [pageNumber, setPageNumber] = useState(1)
const [pageSize, setPageSize] = useState(15)
const [form, setForm] = useState<{
search_id: string
search_status: string
}>({
search_id: "",
search_status: "0",
})
const [searchExpanded, setSearchExpanded] = useState(false)
const [filters, setFilters] = useState(() => createInitialFilters())
const [form, setForm] = useState<FilterFormRecord>(createInitialFilters())
const [records, setRecords] = useState<Task.DataProps[]>([])
const [selectedRecords, setSelectedRecords] = useState<Task.DataProps[]>([])
const [loading, setLoading] = useState(false)
const [queryTrigger, setQueryTrigger] = useState(0)
const [workflowTaskId, setWorkflowTaskId] = useState<number | null>(null)
const [dispatchRecord, setDispatchRecord] = useState<Task.DataProps | null>(
null
)
const [dispatchRecordModal, setDispatchModalConfig] = useState<{
open: boolean
dispatchData?: selectedDataProps
task_ids: Task.DataProps[]
}>({
open: false,
task_ids: [],
})
const [releaseRecord, setReleaseRecord] = useState<Task.DataProps | null>(
null
)
@@ -80,9 +166,8 @@ export default function TaskTableContainer(props: {
const appliedParams = useMemo(() => {
const obj: Task.ListRequest = {
...params,
page_number: params.page_number ?? 1,
page_size: params.page_size ?? 20,
page_number: pageNumber ?? 1,
page_size: pageSize ?? 20,
project_id: projectId !== -1 ? [projectId] : undefined,
get_data: false,
}
@@ -96,8 +181,35 @@ export default function TaskTableContainer(props: {
} else {
delete obj.label_status
}
if (form.rejected) {
const rejected = Number(form.rejected)
if (Number.isFinite(rejected)) obj.rejected = rejected
}
if (form.current_uid?.length) {
obj.current_uid = form.current_uid?.map((item) => Number(item))
}
if (form.label_user?.length) {
obj.label_user = form.label_user?.map((item) => Number(item))
}
if (form.review_user1?.length) {
obj.review_user1 = form.review_user1?.map((item) => Number(item))
}
if (form.review_user2?.length) {
obj.review_user2 = form.review_user2?.map((item) => Number(item))
}
return obj
}, [form.search_id, form.search_status, params, projectId])
}, [
form.current_uid,
form.label_user,
form.rejected,
form.review_user1,
form.review_user2,
form.search_id,
form.search_status,
pageNumber,
pageSize,
projectId,
])
const load = useCallback(async () => {
if (!queryTrigger) return
@@ -124,13 +236,209 @@ export default function TaskTableContainer(props: {
load()
}, [load])
useEffect(() => {
queueMicrotask(() => {
setQueryTrigger((v) => v + 1)
})
}, [])
const clickDispatchTask = () => {
if (!selectedRecords.length) {
notifications.show({
color: "yellow",
title: "警告",
message: "请先选择任务",
})
return
}
const { flag, resultData } = checkSelectedData(
selectedRecords.map((item) => item.id),
records
)
if (!flag) {
notifications.show({
color: "yellow",
title: "警告",
message: "请保证选取任务的标注状态及负责人一致",
})
return
}
setDispatchModalConfig({
open: true,
dispatchData: resultData,
task_ids: selectedRecords,
})
}
const clickReleaseTask = () => {
if (!selectedRecords.length) {
notifications.show({
color: "yellow",
title: "警告",
message: "请先选择任务",
})
return
}
const { flag, resultData } = checkSelectedData(
selectedRecords.map((item) => item.id),
records
)
if (!flag) {
notifications.show({
color: "yellow",
title: "警告",
message: "请保证选取任务的标注状态及负责人一致",
})
return
}
if (![2, 4, 6].includes(resultData.status)) {
notifications.show({
color: "red",
title: "错误",
message: "所选任务状态无法释放",
})
return
}
modals.openConfirmModal({
title: "释放任务",
centered: true,
children: <Text size="sm"></Text>,
labels: { confirm: "释放", cancel: "取消" },
confirmProps: { color: "red" },
onConfirm: async () => {
const reqParams = {
operation_uid: user_id,
task_ids: selectedRecords.map((item) => item.id),
uid: resultData.uid || 0,
reject: resultData.reject,
task_status: resultData.status,
}
try {
await taskRelease(reqParams)
notifications.show({
color: "green",
title: "操作成功",
message: "已释放所选所有任务",
})
load()
} catch {}
},
})
}
const handleExport = async () => {
try {
notifications.show({
color: "yellow",
title: "提示",
message: "导出统计数据中...",
})
const res = await getTaskList({
id: selectedRecords.map((item) => item.id),
page_number: 1,
page_size: selectedRecords.length,
get_data: false,
})
const { task_list } = res
const exportTableHeader = [
"任务ID",
"状态",
"当前负责人",
"标注员",
"审核员",
"复审员",
"对象总数",
"有效对象数",
"新增对象数",
"关键帧数",
"动态对象数",
"静态对象数",
"预标注对象数",
"预标注对象修改数",
"问题数",
"总问答组数",
"单轮问答组数",
"多轮问答组数",
"新增问答组数",
"新增单轮问答组数",
"新增多轮问答组数",
"审核问题数",
"首次批注问题数",
"审核对象数",
"审核动态对象数",
"审核静态对象数",
"批注数(1)",
"批注数(2)",
"标注首次提交时间",
"审核首次提交时间",
"驳回次数",
"标注总耗时",
"审核总耗时",
"复审总耗时",
"标注首次耗时",
"审核首次耗时",
"复审首次耗时",
"审核首次驳回时间",
]
let data = task_list.map((item) => {
return [
item.id,
`${TaskStatusEnum.get(item.label_status)}${
item.rejected ? "返工" : ""
}`,
item.current_username,
item.label_username,
item.review1_username,
item.review2_username,
item.label_object_size.object_size,
item.label_object_size.valid_size,
item.label_object_size.new_size,
item.label_object_size.key_frame_size,
item.label_object_size.dynamic_size,
item.label_object_size.static_size,
item.label_object_size.prelabel_size,
item.label_object_size.prelabel_modify_size,
item.label_object_size.question_size,
item.label_object_size.total_qa_group_size,
item.label_object_size.single_qa_group_size,
item.label_object_size.multiple_qa_group_size,
item.label_object_size.new_qa_group_size,
item.label_object_size.new_single_qa_group_size,
item.label_object_size.new_multiple_qa_group_size,
item.label_object_size.review_question_size,
item.label_object_size.comment_question_size,
item.label_object_size.review_size,
item.label_object_size.review_dynamic_size,
item.label_object_size.review_static_size,
item.label_object_size.first_comment_size,
item.label_object_size.second_comment_size,
item.first_commit_label_time,
item.first_commit_review1_time,
item.reject_times,
item.work_time.label_work_time,
item.work_time.first_review_work_time,
item.work_time.second_review_work_time,
item.label_first_work_time,
item.review1_first_work_time,
item.review2_first_work_time,
item.first_reject_label_time,
]
})
const exportData = [exportTableHeader, ...data]
const wb = XLSX.utils.book_new()
const ws = XLSX.utils.aoa_to_sheet(exportData)
XLSX.utils.book_append_sheet(wb, ws, "任务列表数据")
XLSX.writeFile(wb, `任务列表数据.xlsx`)
} catch {}
}
const columns = useMemo(() => {
const cols: DataTableColumn<Task.DataProps>[] = [
{ accessor: "id", title: "任务ID", width: 90 },
{
accessor: "label_status",
title: "状态",
width: 130,
width: 110,
render: (record) => (
<TaskStatusTag
status={record.label_status}
@@ -140,7 +448,7 @@ export default function TaskTableContainer(props: {
},
{
accessor: "current_username",
title: "当前处理人",
title: "当前负责人",
width: 120,
render: (record) => record.current_username ?? "-",
},
@@ -168,35 +476,232 @@ export default function TaskTableContainer(props: {
width: 110,
render: (record) => record.label_object_size?.object_size ?? "-",
},
{
accessor: "label_object_size.valid_size",
title: "有效对象数",
width: 120,
render: (record) => record.label_object_size?.valid_size ?? "-",
},
{
accessor: "label_object_size.new_size",
title: "新增对象数",
width: 120,
render: (record) => record.label_object_size?.new_size ?? "-",
},
{
accessor: "label_object_size.key_frame_size",
title: "关键帧数",
width: 120,
render: (record) => record.label_object_size?.key_frame_size ?? "-",
},
{
accessor: "label_object_size.dynamic_size",
title: "动态对象数",
width: 120,
render: (record) => record.label_object_size?.dynamic_size ?? "-",
},
{
accessor: "label_object_size.static_size",
title: "静态对象数",
width: 120,
render: (record) => record.label_object_size?.static_size ?? "-",
},
{
accessor: "label_object_size.prelabel_size",
title: "预标注对象数",
width: 120,
render: (record) => record.label_object_size?.prelabel_size ?? "-",
},
{
accessor: "label_object_size.prelabel_modify_size",
title: "预标注对象修改数",
width: 160,
render: (record) =>
record.label_object_size?.prelabel_modify_size ?? "-",
},
{
accessor: "label_object_size.question_size",
title: "问题数",
width: 120,
render: (record) => record.label_object_size?.question_size ?? "-",
},
{
accessor: "label_object_size.total_qa_group_size",
title: "总问答组数",
width: 120,
render: (record) =>
record.label_object_size?.total_qa_group_size ?? "-",
},
{
accessor: "label_object_size.single_qa_group_size",
title: "单轮问答组数",
width: 120,
render: (record) =>
record.label_object_size?.single_qa_group_size ?? "-",
},
{
accessor: "label_object_size.multiple_qa_group_size",
title: "多轮问答组数",
width: 120,
render: (record) =>
record.label_object_size?.multiple_qa_group_size ?? "-",
},
{
accessor: "label_object_size.new_qa_group_size",
title: "新增问答组数",
width: 120,
render: (record) => record.label_object_size?.new_qa_group_size ?? "-",
},
{
accessor: "label_object_size.new_single_qa_group_size",
title: "新增单轮问答组数",
width: 160,
render: (record) =>
record.label_object_size?.new_single_qa_group_size ?? "-",
},
{
accessor: "label_object_size.new_multiple_qa_group_size",
title: "新增多轮问答组数",
width: 160,
render: (record) =>
record.label_object_size?.new_multiple_qa_group_size ?? "-",
},
{
accessor: "label_object_size.review_question_size",
title: "审核问题数",
width: 120,
render: (record) =>
record.label_object_size?.review_question_size ?? "-",
},
{
accessor: "label_object_size.comment_question_size",
title: "首次批注问题数",
width: 140,
render: (record) =>
record.label_object_size?.comment_question_size ?? "-",
},
{
accessor: "label_object_size.review_size",
title: "审核对象数",
width: 120,
render: (record) => record.label_object_size?.review_size ?? "-",
},
{
accessor: "label_object_size.review_dynamic_size",
title: "审核动态对象数",
width: 140,
render: (record) =>
record.label_object_size?.review_dynamic_size ?? "-",
},
{
accessor: "label_object_size.review_static_size",
title: "审核静态对象数",
width: 140,
render: (record) => record.label_object_size?.review_static_size ?? "-",
},
{
accessor: "label_object_size.first_comment_size",
title: "批注数(1)",
width: 120,
render: (record) => record.label_object_size?.first_comment_size ?? "-",
},
{
accessor: "label_object_size.second_comment_size",
title: "批注数(2)",
width: 120,
render: (record) =>
record.label_object_size?.second_comment_size ?? "-",
},
{
accessor: "first_commit_label_time",
title: "标注首次提交时间",
width: 180,
render: (record) => record.first_commit_label_time ?? "-",
},
{
accessor: "first_commit_review1_time",
title: "审核首次提交时间",
width: 180,
render: (record) => record.first_commit_review1_time ?? "-",
},
{
accessor: "reject_times",
title: "驳回次数",
width: 120,
render: (record) => record.reject_times ?? "-",
},
{
accessor: "work_time.label_work_time",
title: "标注总耗时",
width: 120,
render: (record) => record.work_time?.label_work_time ?? "-",
},
{
accessor: "work_time.first_review_work_time",
title: "审核总耗时",
width: 120,
render: (record) => record.work_time?.first_review_work_time ?? "-",
},
{
accessor: "work_time.second_review_work_time",
title: "复审总耗时",
width: 120,
render: (record) => record.work_time?.second_review_work_time ?? "-",
},
{
accessor: "label_first_work_time",
title: "标注首次耗时",
width: 120,
render: (record) => record.label_first_work_time ?? "-",
},
{
accessor: "review1_first_work_time",
title: "审核首次耗时",
width: 120,
render: (record) => record.review1_first_work_time ?? "-",
},
{
accessor: "review2_first_work_time",
title: "复审首次耗时",
width: 120,
render: (record) => record.review2_first_work_time ?? "-",
},
{
accessor: "first_reject_label_time",
title: "审核首次驳回时间",
width: 180,
render: (record) => record.first_reject_label_time ?? "-",
},
{
accessor: "operation",
title: "操作",
width: 140,
textAlign: "center",
render: (record) => (
<Group gap={6} justify="center" wrap="nowrap">
<ActionIcon
variant="subtle"
color="blue"
onClick={() => setWorkflowTaskId(record.id)}
title="工作流">
<IconRoute size={16} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="violet"
onClick={() => setDispatchRecord(record)}
title="调度">
<IconArrowsShuffle size={16} />
</ActionIcon>
<ActionIcon
variant="subtle"
color="red"
onClick={() => setReleaseRecord(record)}
title="释放">
<IconTrashX size={16} />
</ActionIcon>
</Group>
<Flex gap={6} justify="space-around" wrap="nowrap">
<Button
px={0}
variant="transparent"
onClick={() => {
setDispatchModalConfig({
open: true,
dispatchData: {
status: record.label_status,
uid: record.current_uid || null,
reject: record.rejected,
},
task_ids: [record],
})
}}>
</Button>
<Button
px={0}
variant="transparent"
onClick={() => setWorkflowTaskId(record.id)}>
</Button>
</Flex>
),
},
]
@@ -206,95 +711,226 @@ export default function TaskTableContainer(props: {
return (
<>
<Stack gap="sm" style={{ flex: 1, minHeight: 0 }}>
<Paper
withBorder
p="sm"
radius="md"
style={{
borderColor:
"light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4))",
}}>
<Stack gap="sm">
<Group justify="space-between" wrap="wrap" gap="sm">
<Group gap="sm" wrap="wrap">
<Paper withBorder p="sm" radius="sm">
<Group justify="space-between" wrap="wrap" gap="sm">
<Group gap="xs">
<Text fw={700}></Text>
<Button
size="xs"
variant="transparent"
rightSection={
searchExpanded ? (
<IconChevronUp size={16} />
) : (
<IconChevronDown size={16} />
)
}
onClick={() => setSearchExpanded((v) => !v)}>
{searchExpanded ? "收起筛选" : "展开筛选"}
</Button>
</Group>
<Group gap="xs">
<Button
size="xs"
radius={"xs"}
variant="default"
leftSection={<IconRefresh size={16} />}
onClick={() => {
const next = createInitialFilters()
setFilters(next)
setForm(next)
setPageNumber(1)
}}>
</Button>
<Button
size="xs"
radius={"xs"}
leftSection={<IconSearch size={16} />}
onClick={() => {
setPageNumber(1)
setForm({ ...filters })
}}>
</Button>
</Group>
</Group>
<Collapse
in={searchExpanded}
transitionDuration={250}
transitionTimingFunction="ease"
animateOpacity>
<Stack gap="sm" mt="sm">
<SimpleGrid cols={{ base: 1, sm: 2, md: 3, lg: 4 }} spacing="sm">
<TextInput
size="xs"
radius="xs"
label="任务ID"
value={form.search_id}
value={filters.search_id}
onChange={(e) =>
setForm((s) => ({ ...s, search_id: e.target.value }))
setFilters((s) => ({
...s,
search_id: e.target.value,
}))
}
style={{ width: 160 }}
/>
<Select
size="xs"
label="状态"
radius="xs"
label="任务状态"
data={Object.entries(TaskStatusOpts).map(
([value, label]) => ({
value,
label,
})
)}
value={form.search_status}
value={filters.search_status}
onChange={(v) =>
setForm((s) => ({ ...s, search_status: v ?? "0" }))
setFilters((s) => ({ ...s, search_status: v ?? "0" }))
}
allowDeselect={false}
style={{ width: 160 }}
/>
</Group>
<Group gap="xs" wrap="wrap">
<Select
size="xs"
radius="xs"
label="是否返工"
data={[
{ label: "是", value: "1" },
{ label: "否", value: "0" },
]}
value={filters.rejected}
onChange={(v) =>
setFilters((s) => ({ ...s, rejected: v || undefined }))
}
allowDeselect={true}
/>
<MultiSelect
label="当前负责人"
size="xs"
radius="xs"
data={Array.from(userEnums.allEnum.entries()).map(
([x, y]) => ({ label: y, value: x.toString() })
)}
searchable
clearable
value={filters.current_uid}
onChange={(v) =>
setFilters((s) => ({ ...s, current_uid: v }))
}
/>
<MultiSelect
label="标注员"
size="xs"
radius="xs"
data={Array.from(userEnums.labelEnum.entries()).map(
([x, y]) => ({ label: y, value: x.toString() })
)}
searchable
clearable
value={filters.label_user}
onChange={(v) => setFilters((s) => ({ ...s, label_user: v }))}
/>
<MultiSelect
label="审核员"
size="xs"
radius="xs"
data={Array.from(userEnums.review1Enum.entries()).map(
([x, y]) => ({ label: y, value: x.toString() })
)}
searchable
clearable
value={filters.review_user1}
onChange={(v) =>
setFilters((s) => ({ ...s, review_user1: v }))
}
/>
<MultiSelect
label="复审员"
size="xs"
radius="xs"
data={Array.from(userEnums.review2Enum.entries()).map(
([x, y]) => ({ label: y, value: x.toString() })
)}
searchable
clearable
value={filters.review_user2}
onChange={(v) =>
setFilters((s) => ({ ...s, review_user2: v }))
}
/>
</SimpleGrid>
</Stack>
</Collapse>
</Paper>
<Paper
withBorder
p="md"
radius="sm"
style={{
flex: 1,
minHeight: 0,
display: "flex",
flexDirection: "column",
}}>
<Stack gap={12} align="stretch" style={{ flex: 1, minHeight: 0 }}>
<Flex justify={"space-between"} align="center" w="100%">
<Flex gap={12}>
<Button
size="xs"
variant="default"
onClick={() => {
resetParams()
setForm({ search_id: "", search_status: "0" })
setQueryTrigger((v) => v + 1)
}}>
radius="xs"
variant="outline"
disabled={!selectedRecords.length}
onClick={handleExport}>
</Button>
<Button
size="xs"
onClick={() => {
setPagination(1, params.page_size ?? 20)
setQueryTrigger((v) => v + 1)
}}>
radius="xs"
variant="outline"
disabled={!selectedRecords.length}
onClick={clickDispatchTask}>
</Button>
</Group>
</Group>
<Button
size="xs"
radius="xs"
variant="outline"
disabled={!selectedRecords.length}
onClick={clickReleaseTask}>
</Button>
</Flex>
<ActionIcon onClick={load} variant="transparent">
<IconRefresh size={16} />
</ActionIcon>
</Flex>
<DataTable<Task.DataProps>
width="100%"
style={{ width: "100%" }}
withTableBorder
withRowBorders
pinFirstColumn
pinLastColumn
scrollAreaProps={{ type: "auto" }}
fetching={loading}
records={records}
columns={columns}
totalRecords={total}
recordsPerPage={pageSize}
page={pageNumber}
onPageChange={(p) => setPageNumber(p)}
onRecordsPerPageChange={(s) => {
setPageSize(s)
setPageNumber(1)
}}
selectedRecords={selectedRecords}
onSelectedRecordsChange={setSelectedRecords}
recordsPerPageOptions={[10, 15, 20]}
noRecordsText="暂无数据"
recordsPerPageLabel="当前页数"
/>
</Stack>
</Paper>
<Group gap={0} align="stretch" style={{ flex: 1, minHeight: 0 }}>
<DataTable<Task.DataProps>
width="100%"
style={{ width: "100%" }}
withTableBorder
withRowBorders
pinFirstColumn
pinLastColumn
scrollAreaProps={{ type: "auto" }}
fetching={loading}
records={records}
columns={columns}
totalRecords={total}
recordsPerPage={params.page_size ?? 20}
page={params.page_number ?? 1}
onPageChange={(p) => {
setPagination(p, params.page_size ?? 20)
setQueryTrigger((v) => v + 1)
}}
onRecordsPerPageChange={(s) => {
setPagination(1, s)
setQueryTrigger((v) => v + 1)
}}
recordsPerPageOptions={[10, 20, 50, 100]}
noRecordsText="暂无数据"
/>
</Group>
</Stack>
<WorkflowModal
@@ -303,11 +939,15 @@ export default function TaskTableContainer(props: {
onCloseAction={() => setWorkflowTaskId(null)}
/>
<DispatchModal
opened={dispatchRecord !== null}
record={dispatchRecord}
opened={dispatchRecordModal.open}
dispatchData={dispatchRecordModal.dispatchData}
task_ids={dispatchRecordModal.task_ids}
userOptions={userOptions}
onCloseAction={(refresh) => {
setDispatchRecord(null)
setDispatchModalConfig({
open: false,
task_ids: [],
})
if (refresh) setQueryTrigger((v) => v + 1)
}}
/>