zhou zhou
14 小时以前 cfb11bfebee5b4ec56c822f27fe339c315e497cc
#路径流程页
9个文件已修改
1个文件已添加
487 ■■■■ 已修改文件
rsf-admin/src/config/setting.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/en.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/i18n/zh.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/ResourceContent.js 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basStation/BasStationList.jsx 22 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/basicInfo/basStation/ContainerTypesField.jsx 20 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/components/ChipArrayField.jsx 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplate/TaskTemplateFlowViewer.jsx 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeEdit.jsx 21 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx 159 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
rsf-admin/src/config/setting.js
@@ -25,7 +25,7 @@
export const ABORT_SIGNAL = false;
export const DEFAULT_PAGE_SIZE = 20;
export const DEFAULT_PAGE_SIZE = 25;
export const DEFAULT_START_PAGE = 1;
rsf-admin/src/i18n/en.js
@@ -224,9 +224,28 @@
        taskPathTemplateNode: 'TaskPathTemplateNode',
        subsystemFlowTemplate: 'SubsystemFlowTemplate',
        flowStepTemplate: 'FlowStepTemplate',
        taskPathTemplateMerge: 'TaskPathTemplateMerge',
    },
    table: {
        field: {
            taskPathTemplateMerge: {
                templateCode: "templateCode",
                templateName: "templateName",
                sourceType: "sourceType",
                targetType: "targetType",
                conditionExpression: "conditionExpression",
                conditionDesc: "conditionDesc",
                version: "version",
                isCurrent: "isCurrent",
                effectiveTime: "effectiveTime",
                expireTime: "expireTime",
                priority: "priority",
                timeoutMinutes: "timeoutMinutes",
                maxRetryTimes: "maxRetryTimes",
                retryIntervalSeconds: "retryIntervalSeconds",
                remark: "remark",
                stepSize: "stepSize",
            },
            flowStepTemplate: {
                flowId: "flowId",
                flowCode: "flowCode",
rsf-admin/src/i18n/zh.js
@@ -240,9 +240,28 @@
        taskPathTemplateNode: '任务路径模板节点',
        subsystemFlowTemplate: '子系统流程模板',
        flowStepTemplate: '流程步骤模板',
        taskPathTemplateMerge: '任务路径模板合并',
    },
    table: {
        field: {
            taskPathTemplateMerge: {
                templateCode: "模板编码",
                templateName: "模板名称",
                sourceType: "源类型",
                targetType: "目标类型",
                conditionExpression: "条件表达式",
                conditionDesc: "条件描述",
                version: "版本",
                isCurrent: "是否当前",
                effectiveTime: "生效时间",
                expireTime: "过期时间",
                priority: "优先级",
                timeoutMinutes: "超时时间",
                maxRetryTimes: "最大重试次数",
                retryIntervalSeconds: "重试间隔",
                remark: "备注",
                stepSize: "步长",
            },
            flowStepTemplate: {
                flowId: "流程ID",
                flowCode: "流程编码",
rsf-admin/src/page/ResourceContent.js
@@ -68,6 +68,7 @@
import preparation from "./orders/preparation";
import menuPda from './menuPda';
import taskPathTemplate from './taskPathTemplate';
import taskPathTemplateMerge from './taskPathTemplateMerge';
const ResourceContent = (node) => {
  switch (node.component) {
@@ -199,6 +200,8 @@
      return menuPda;
    case 'taskPathTemplate':
      return taskPathTemplate;
    case 'taskPathTemplateMerge':
      return taskPathTemplateMerge;
    // case "locItem":
    //   return locItem;
    default:
rsf-admin/src/page/basicInfo/basStation/BasStationList.jsx
@@ -47,6 +47,7 @@
import WarehouseAreaField from "./WarehouseAreaField";
import CrossZoneAreaField from "./CrossZoneAreaField";
import ContainerTypesField from "./ContainerTypesField";
import ChipArrayField from '@/page/components/ChipArrayField';
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
@@ -160,9 +161,18 @@
                        render={record => record.inAble === 1 ? '是' : '否'}
                    />
                    <WrapperField cellClassName="crossZoneArea" label="table.field.basStation.crossZoneArea">
                        <CrossZoneAreaField
                        {/* <CrossZoneAreaField
                            open={areaFieldDialog}
                            setOpen={setAreaFieldDialog}
                        /> */}
                        <ChipArrayField
                            source="areaIds"
                            apiEndpoint="/warehouseAreas/many/{ids}"
                            labelField="name"
                            dialogTitle={translate('table.field.basStation.crossZoneArea')}
                            initialDisplayCount={1}
                            placeholderText="{count} 个区域"
                        />
                    </WrapperField>
                    <FunctionField
@@ -171,9 +181,13 @@
                        render={record => record.inAble === 1 ? '是' : '否'}
                    />
                    <WrapperField cellClassName="containerType" label="table.field.basStation.containerType">
                        <ContainerTypesField
                            open={areaFieldDialog2}
                            setOpen={setAreaFieldDialog2}
                        <ChipArrayField
                            source="containerTypes$"
                            apiEndpoint="/dictData/many/{ids}"
                            labelField="label"
                            dialogTitle={translate('table.field.basStation.containerType')}
                            initialDisplayCount={1}
                            placeholderText="{count} 个区域"
                        />
                    </WrapperField>
                    <FunctionField
rsf-admin/src/page/basicInfo/basStation/ContainerTypesField.jsx
@@ -21,9 +21,9 @@
    const fetchAreaNames = async () => {
        if (!record?.containerTypes || record.containerTypes.length === 0) return;
        setLoading(true);
        try {
        try {
            const res = await request.post(`/dictData/many/${record.containerTypes$.join(',')}`);
            if (res?.data?.code === 200) {
                setAreaNames(res.data.data || []);
@@ -36,10 +36,10 @@
    };
    React.useEffect(() => {
        if (record?.containerTypes   && record.containerTypes.length > 0) {
        if (record?.containerTypes && record.containerTypes.length > 0) {
            fetchAreaNames();
        }
    }, [record]);
    }, [record]);
    if (loading) {
        return <CircularProgress size={20} />;
@@ -47,10 +47,10 @@
    return (
        <>
            <Stack
                direction="row"
                gap={1}
                flexWrap="wrap"
            <Stack
                direction="row"
                gap={1}
                flexWrap="wrap"
                onClick={handleOpen}
                sx={{ cursor: 'pointer' }}
            >
@@ -75,8 +75,8 @@
                )}
            </Stack>
            <Dialog
                open={open}
            <Dialog
                open={open}
                onClose={handleClose}
                maxWidth="md"
                fullWidth
rsf-admin/src/page/components/ChipArrayField.jsx
New file
@@ -0,0 +1,210 @@
import * as React from 'react';
import {
    Stack,
    Chip,
    Dialog,
    DialogTitle,
    DialogContent,
    IconButton,
    CircularProgress
} from '@mui/material';
import { useRecordContext } from 'react-admin';
import CloseIcon from '@mui/icons-material/Close';
import request from '@/utils/request';
/**
 * 通用芯片数组字段组件
 * 用于展示从 API 获取的数组数据,以芯片形式显示
 *
 * @param {Object} props
 * @param {string|string[]} props.source - 数据源字段名,支持多个字段名(如 ['areaIds', 'areas'])
 * @param {string} props.apiEndpoint - API 端点模板,使用 {ids} 作为占位符,例如: '/warehouseAreas/many/{ids}'
 * @param {string} props.labelField - 用于显示的字段名,默认为 'label'
 * @param {string} props.dialogTitle - 弹窗标题
 * @param {number} props.initialDisplayCount - 初始显示的数量,默认为 1
 * @param {string} props.placeholderText - 占位符文本模板,使用 {count} 作为占位符,默认为 '{count} 项'
 * @param {function} props.dataTransform - 可选的数据转换函数,用于处理特殊格式的数据源
 */
const ChipArrayField = ({
    source,
    apiEndpoint,
    labelField = 'label',
    dialogTitle = '详情',
    initialDisplayCount = 1,
    placeholderText = '{count} 项',
    dataTransform = null
}) => {
    const record = useRecordContext();
    const [open, setOpen] = React.useState(false);
    const [items, setItems] = React.useState([]);
    const [loading, setLoading] = React.useState(false);
    const handleOpen = () => {
        setOpen(true);
    };
    const handleClose = () => {
        setOpen(false);
    };
    // 使用 useMemo 缓存数据源值
    const sourceData = React.useMemo(() => {
        if (!record) return null;
        // 支持多个字段名
        if (Array.isArray(source)) {
            for (const field of source) {
                if (record[field]) {
                    return record[field];
                }
            }
            return null;
        }
        return record[source];
    }, [record, source]);
    // 使用 useCallback 缓存 fetchItems 函数
    const fetchItems = React.useCallback(async () => {
        let data = sourceData;
        if (!data || data.length === 0) {
            setItems([]);
            return;
        }
        // 应用自定义数据转换函数
        if (dataTransform) {
            data = dataTransform(data);
        }
        setLoading(true);
        try {
            // 提取 ID 和排序信息
            const isObjectArray = data.length > 0 &&
                typeof data[0] === 'object' &&
                data[0] !== null &&
                'id' in data[0];
            let ids = [];
            let sortMap = new Map(); // 存储 id -> sort 的映射
            if (isObjectArray) {
                // 对象数组格式,提取 ID 和排序信息
                ids = data.map(item => {
                    const id = item.id;
                    sortMap.set(id, item.sort || 0);
                    return id;
                });
            } else {
                // 纯 ID 数组格式
                ids = data.map(id => Number(id));
            }
            // 替换 API 端点中的占位符
            const url = apiEndpoint.replace('{ids}', ids.join(','));
            const res = await request.post(url);
            if (res?.data?.code === 200) {
                let fetchedItems = res.data.data || [];
                // 如果有排序信息,按排序值排序
                if (sortMap.size > 0) {
                    fetchedItems = fetchedItems.sort((a, b) => {
                        const sortA = sortMap.get(a.id) || 0;
                        const sortB = sortMap.get(b.id) || 0;
                        return sortA - sortB;
                    });
                }
                setItems(fetchedItems);
            }
        } catch (error) {
            console.error('获取数据失败:', error);
        } finally {
            setLoading(false);
        }
    }, [sourceData, apiEndpoint, dataTransform]);
    React.useEffect(() => {
        if (sourceData && sourceData.length > 0) {
            fetchItems();
        } else {
            setItems([]);
        }
    }, [sourceData, fetchItems]);
    if (loading) {
        return <CircularProgress size={20} />;
    }
    return (
        <>
            <Stack
                direction="row"
                gap={1}
                flexWrap="wrap"
                onClick={handleOpen}
                sx={{ cursor: 'pointer' }}
            >
                {items.slice(0, initialDisplayCount).map((item) => (
                    <Chip
                        size="small"
                        key={item.id}
                        label={item[labelField] || item.id}
                    />
                ))}
                {items.length > initialDisplayCount && (
                    <Chip
                        size="small"
                        label={`+${items.length - initialDisplayCount}`}
                    />
                )}
                {items.length === 0 && sourceData && sourceData.length > 0 && (
                    <Chip
                        size="small"
                        label={placeholderText.replace('{count}', sourceData.length)}
                    />
                )}
            </Stack>
            <Dialog
                open={open}
                onClose={handleClose}
                maxWidth="md"
                fullWidth
            >
                <DialogTitle>
                    {dialogTitle}
                    <IconButton
                        aria-label="close"
                        onClick={handleClose}
                        sx={{
                            position: 'absolute',
                            right: 8,
                            top: 8,
                        }}
                    >
                        <CloseIcon />
                    </IconButton>
                </DialogTitle>
                <DialogContent>
                    {loading ? (
                        <CircularProgress />
                    ) : (
                        <Stack direction="row" gap={1} flexWrap="wrap" sx={{ mt: 1 }}>
                            {items.map((item) => (
                                <Chip
                                    size="small"
                                    key={item.id}
                                    label={item[labelField] || item.id}
                                />
                            ))}
                        </Stack>
                    )}
                </DialogContent>
            </Dialog>
        </>
    );
};
export default ChipArrayField;
rsf-admin/src/page/taskPathTemplate/TaskTemplateFlowViewer.jsx
@@ -12,15 +12,21 @@
import StickyDataTable from "@/page/components/StickyDataTable";
const ViewTable = ({ title, resource, filter, onClick, selectedId, columns }) => {
    // 只有在有 filter 时才调用 useListController,避免不必要的请求
    const shouldLoadData = !!filter;
    const listContext = useListController({
        resource: resource,
        filter: filter,
        filter: filter || {},
        perPage: 100,
        sort: { field: 'id', order: 'ASC' },
        disableSyncWithLocation: true,
        queryOptions: {
            enabled: shouldLoadData, // 只有在 shouldLoadData 为 true 时才执行查询
        },
    });
    if (!filter) {
    if (!shouldLoadData) {
        return (
            <Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
                <Box p={1} borderBottom={1} borderColor="divider">
@@ -147,7 +153,7 @@
                    <ViewTable
                        title={translate('menu.subsystemFlowTemplate')}
                        resource="subsystemFlowTemplate"
                        filter={selectedNode ? { systemCode: selectedNode.systemCode } : null}
                        filter={selectedNode ? { flowCode: selectedNode.nodeCode } : null}
                        onClick={handleFlowClick}
                        selectedId={selectedFlow?.id}
                        columns={flowColumns}
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeEdit.jsx
@@ -19,6 +19,7 @@
    required,
    useRecordContext,
    DeleteButton,
    SelectArrayInput,
} from 'react-admin';
import { useWatch, useFormContext } from "react-hook-form";
import { Stack, Grid, Box, Typography } from '@mui/material';
@@ -97,12 +98,20 @@
                            />
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
                                label="table.field.taskPathTemplateMerge.conditionExpression"
                                source="conditionExpression"
                                parse={v => v}
                                validate={required()}
                            />
                            <ReferenceArrayInput source="conditionExpression" reference="taskPathTemplate">
                                <SelectArrayInput
                                    label="table.field.taskPathTemplateMerge.conditionExpression"
                                    optionText="templateName"
                                    optionValue="id"
                                    fullWidth
                                    validate={(value) => {
                                        if (value && value.length > 1) {
                                            return '只能选择一个模板';
                                        }
                                        return undefined;
                                    }}
                                />
                            </ReferenceArrayInput>
                        </Stack>
                        <Stack direction='row' gap={2}>
                            <TextInput
rsf-admin/src/page/taskPathTemplateMerge/TaskPathTemplateMergeList.jsx
@@ -2,35 +2,25 @@
import { useNavigate } from 'react-router-dom';
import {
    List,
    DatagridConfigurable,
    SearchInput,
    TopToolbar,
    SelectColumnsButton,
    ColumnsButton,
    EditButton,
    FilterButton,
    CreateButton,
    ExportButton,
    BulkDeleteButton,
    WrapperField,
    useRecordContext,
    useTranslate,
    useNotify,
    useListContext,
    FunctionField,
    TextField,
    NumberField,
    DateField,
    BooleanField,
    ReferenceField,
    TextInput,
    DateTimeInput,
    DateInput,
    SelectInput,
    NumberInput,
    ReferenceInput,
    ReferenceArrayInput,
    AutocompleteInput,
    DeleteButton,
    DataTable
} from 'react-admin';
import { Box, Typography, Card, Stack } from '@mui/material';
import { styled } from '@mui/material/styles';
@@ -43,20 +33,10 @@
import MyField from "../components/MyField";
import { PAGE_DRAWER_WIDTH, OPERATE_MODE, DEFAULT_PAGE_SIZE } from '@/config/setting';
import * as Common from '@/utils/common';
import StickyDataTable from "@/page/components/StickyDataTable";
import useTableLayout from '@/utils/useTableLayout';
import ChipArrayField from '@/page/components/ChipArrayField';
const StyledDatagrid = styled(DatagridConfigurable)(({ theme }) => ({
    '& .css-1vooibu-MuiSvgIcon-root': {
        height: '.9em'
    },
    '& .RaDatagrid-row': {
        cursor: 'auto'
    },
    '& .column-name': {
    },
    '& .opt': {
        width: 200
    },
}));
const filters = [
    <SearchInput source="condition" alwaysOn />,
@@ -92,6 +72,49 @@
    />,
]
const getColumns = (translate) => [
    <NumberField source="id" />,
    <TextField source="templateCode" label="table.field.taskPathTemplateMerge.templateCode" />,
    <TextField source="templateName" label="table.field.taskPathTemplateMerge.templateName" />,
    <TextField source="sourceType" label="table.field.taskPathTemplateMerge.sourceType" />,
    <TextField source="targetType" label="table.field.taskPathTemplateMerge.targetType" />,
    <ChipArrayField
        source="conditionExpression"
        label="table.field.taskPathTemplateMerge.conditionExpression"
        apiEndpoint="/taskPathTemplate/many/{ids}"
        labelField="templateName"
        dialogTitle={translate('table.field.taskPathTemplateMerge.conditionExpression')}
        initialDisplayCount={1}
        placeholderText="{count} 个模板"
    />,
    <TextField source="conditionDesc" label="table.field.taskPathTemplateMerge.conditionDesc" />,
    <NumberField source="version" label="table.field.taskPathTemplateMerge.version" />,
    <NumberField source="isCurrent" label="table.field.taskPathTemplateMerge.isCurrent" />,
    <DateField source="effectiveTime" label="table.field.taskPathTemplateMerge.effectiveTime" showTime />,
    <DateField source="expireTime" label="table.field.taskPathTemplateMerge.expireTime" showTime />,
    <NumberField source="priority" label="table.field.taskPathTemplateMerge.priority" />,
    <NumberField source="timeoutMinutes" label="table.field.taskPathTemplateMerge.timeoutMinutes" />,
    <NumberField source="maxRetryTimes" label="table.field.taskPathTemplateMerge.maxRetryTimes" />,
    <NumberField source="retryIntervalSeconds" label="table.field.taskPathTemplateMerge.retryIntervalSeconds" />,
    <TextField source="remark" label="table.field.taskPathTemplateMerge.remark" />,
    <NumberField source="stepSize" label="table.field.taskPathTemplateMerge.stepSize" />,
    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
        <TextField source="nickname" />
    </ReferenceField>,
    <DateField source="updateTime" label="common.field.updateTime" showTime />,
    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
        <TextField source="nickname" />
    </ReferenceField>,
    <DateField source="createTime" label="common.field.createTime" showTime />,
    <BooleanField source="statusBool" label="common.field.status" sortable={false} />,
    <TextField source="memo" label="common.field.memo" sortable={false} />,
    <WrapperField source="opt" cellClassName="opt" label="common.field.opt">
        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
    </WrapperField>
];
const TaskPathTemplateMergeList = () => {
    const translate = useTranslate();
@@ -117,53 +140,13 @@
                    <TopToolbar>
                        <FilterButton />
                        <MyCreateButton onClick={() => { setCreateDialog(true) }} />
                        <SelectColumnsButton preferenceKey='taskPathTemplateMerge' />
                        <ColumnsButton storeKey='taskPathTemplateMerge' />
                        <MyExportButton />
                    </TopToolbar>
                )}
                perPage={DEFAULT_PAGE_SIZE}
            >
                <StyledDatagrid
                    preferenceKey='taskPathTemplateMerge'
                    bulkActionButtons={() => <BulkDeleteButton mutationMode={OPERATE_MODE} />}
                    rowClick={(id, resource, record) => false}
                    expand={() => <TaskPathTemplateMergePanel />}
                    expandSingle={true}
                    omit={['id', 'createTime', 'createBy', 'memo']}
                >
                    <NumberField source="id" />
                    <TextField source="templateCode" label="table.field.taskPathTemplateMerge.templateCode" />
                    <TextField source="templateName" label="table.field.taskPathTemplateMerge.templateName" />
                    <TextField source="sourceType" label="table.field.taskPathTemplateMerge.sourceType" />
                    <TextField source="targetType" label="table.field.taskPathTemplateMerge.targetType" />
                    <TextField source="conditionExpression" label="table.field.taskPathTemplateMerge.conditionExpression" />
                    <TextField source="conditionDesc" label="table.field.taskPathTemplateMerge.conditionDesc" />
                    <NumberField source="version" label="table.field.taskPathTemplateMerge.version" />
                    <NumberField source="isCurrent" label="table.field.taskPathTemplateMerge.isCurrent" />
                    <DateField source="effectiveTime" label="table.field.taskPathTemplateMerge.effectiveTime" showTime />
                    <DateField source="expireTime" label="table.field.taskPathTemplateMerge.expireTime" showTime />
                    <NumberField source="priority" label="table.field.taskPathTemplateMerge.priority" />
                    <NumberField source="timeoutMinutes" label="table.field.taskPathTemplateMerge.timeoutMinutes" />
                    <NumberField source="maxRetryTimes" label="table.field.taskPathTemplateMerge.maxRetryTimes" />
                    <NumberField source="retryIntervalSeconds" label="table.field.taskPathTemplateMerge.retryIntervalSeconds" />
                    <TextField source="remark" label="table.field.taskPathTemplateMerge.remark" />
                    <NumberField source="stepSize" label="table.field.taskPathTemplateMerge.stepSize" />
                    <ReferenceField source="updateBy" label="common.field.updateBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="updateTime" label="common.field.updateTime" showTime />
                    <ReferenceField source="createBy" label="common.field.createBy" reference="user" link={false} sortable={false}>
                        <TextField source="nickname" />
                    </ReferenceField>
                    <DateField source="createTime" label="common.field.createTime" showTime />
                    <BooleanField source="statusBool" label="common.field.status" sortable={false} />
                    <TextField source="memo" label="common.field.memo" sortable={false} />
                    <WrapperField cellClassName="opt" label="common.field.opt">
                        <EditButton sx={{ padding: '1px', fontSize: '.75rem' }} />
                        <DeleteButton sx={{ padding: '1px', fontSize: '.75rem' }} mutationMode={OPERATE_MODE} />
                    </WrapperField>
                </StyledDatagrid>
                <TableItems drawerVal={drawerVal} />
            </List>
            <TaskPathTemplateMergeCreate
                open={createDialog}
@@ -179,4 +162,46 @@
    )
}
const TableItems = ({ drawerVal }) => {
    const { isLoading } = useListContext();
    const translate = useTranslate();
    const { boxMaxWidth, boxMaxHeight } = useTableLayout(drawerVal);
    const columns = getColumns(translate);
    return (
        <Box sx={{
            position: 'relative',
            maxHeight: boxMaxHeight,
            maxWidth: boxMaxWidth,
            overflowX: 'auto',
            overflowY: 'auto',
            '& .MuiTableCell-root': {
                whiteSpace: 'nowrap',
            }
        }}>
            {columns.length > 0 &&
                <StickyDataTable
                    stickyRight={['opt']}
                    storeKey='taskPathTemplateMerge'
                    bulkActionButtons={false}
                    rowClick={false}
                    hiddenColumns={['id', 'createTime', 'createBy', 'memo', 'statusBool']}
                >
                    {columns
                        .map((column) => (
                            <DataTable.Col
                                key={column.key || column.props.source}
                                source={column.props.source}
                                label={column.props.label}
                                sx={column.props.sx}
                            >
                                {column}
                            </DataTable.Col>
                        ))
                    }
                </StickyDataTable>}
        </Box>
    )
}
export default TaskPathTemplateMergeList;