mirror of
https://gitee.com/nocobase/nocobase.git
synced 2025-05-05 13:39:24 +08:00
feat: add Gantt and Kanban blocks in pop ups/drawers (#4277)
* feat: add Gantt and Kanban blocks in pop ups/drawers * feat: add Gantt and Kanban blocks in pop ups/drawers * fix: bug * fix: bug * fix: bug * fix: bug
This commit is contained in:
parent
1283312eb9
commit
d787edfb47
@ -1052,7 +1052,7 @@ export const useBulkDestroyActionProps = () => {
|
||||
field.data.selectedRowKeys = [];
|
||||
const currentPage = service.params[0]?.page;
|
||||
const totalPage = service.data?.meta?.totalPage;
|
||||
if (currentPage === totalPage) {
|
||||
if (currentPage === totalPage && service.params[0]) {
|
||||
service.params[0].page = currentPage - 1;
|
||||
}
|
||||
if (callBack) {
|
||||
|
@ -22,122 +22,266 @@ import {
|
||||
SchemaComponent,
|
||||
DataBlockInitializer,
|
||||
SchemaComponentOptions,
|
||||
Collection,
|
||||
CollectionFieldOptions,
|
||||
} from '@nocobase/client';
|
||||
import { createGanttBlockUISchema } from './createGanttBlockUISchema';
|
||||
|
||||
export const GanttBlockInitializer = () => {
|
||||
export const GanttBlockInitializer = ({
|
||||
filterCollections,
|
||||
onlyCurrentDataSource,
|
||||
hideSearch,
|
||||
createBlockSchema,
|
||||
showAssociationFields,
|
||||
}: {
|
||||
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
||||
onlyCurrentDataSource: boolean;
|
||||
hideSearch?: boolean;
|
||||
createBlockSchema?: (options: any) => any;
|
||||
showAssociationFields?: boolean;
|
||||
}) => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const { createGanttBlock } = useCreateGanttBlock();
|
||||
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
componentType={'Calendar'}
|
||||
icon={<FormOutlined />}
|
||||
onCreateBlockSchema={async (options) => {
|
||||
if (createBlockSchema) {
|
||||
return createBlockSchema(options);
|
||||
}
|
||||
createGanttBlock(options);
|
||||
}}
|
||||
onlyCurrentDataSource={onlyCurrentDataSource}
|
||||
hideSearch={hideSearch}
|
||||
filter={filterCollections}
|
||||
showAssociationFields={showAssociationFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCreateGanttBlock = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { theme } = useGlobalTheme();
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
componentType={'Gantt'}
|
||||
icon={<FormOutlined />}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collectionFields = getCollectionFields(item.name, item.dataSource);
|
||||
const stringFields = collectionFields
|
||||
?.filter((field) => field.type === 'string')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const dateFields = collectionFields
|
||||
?.filter((field) => field.type === 'date')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const numberFields = collectionFields
|
||||
?.filter((field) => field.type === 'float')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(
|
||||
t('Create gantt block'),
|
||||
() => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Title field'),
|
||||
enum: stringFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
start: {
|
||||
title: t('Start date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
default: 'createdAt',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
end: {
|
||||
title: t('End date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
progress: {
|
||||
title: t('Progress field'),
|
||||
enum: numberFields,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
range: {
|
||||
title: t('Time scale'),
|
||||
enum: [
|
||||
{ label: '{{t("Hour")}}', value: 'hour', color: 'orange' },
|
||||
{ label: '{{t("Quarter of day")}}', value: 'quarterDay', color: 'default' },
|
||||
{ label: '{{t("Half of day")}}', value: 'halfDay', color: 'blue' },
|
||||
{ label: '{{t("Day")}}', value: 'day', color: 'yellow' },
|
||||
{ label: '{{t("Week")}}', value: 'week', color: 'pule' },
|
||||
{ label: '{{t("Month")}}', value: 'month', color: 'green' },
|
||||
{ label: '{{t("Year")}}', value: 'year', color: 'green' },
|
||||
{ label: '{{t("QuarterYear")}}', value: 'quarterYear', color: 'red' },
|
||||
],
|
||||
default: 'day',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createGanttBlockUISchema({
|
||||
collectionName: item.name,
|
||||
dataSource: item.dataSource,
|
||||
fieldNames: {
|
||||
...values,
|
||||
},
|
||||
}),
|
||||
const createGanttBlock = async ({ item }) => {
|
||||
const collectionFields = getCollectionFields(item.name, item.dataSource);
|
||||
const stringFields = collectionFields
|
||||
?.filter((field) => field.type === 'string')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const dateFields = collectionFields
|
||||
?.filter((field) => field.type === 'date')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const numberFields = collectionFields
|
||||
?.filter((field) => field.type === 'float')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(
|
||||
t('Create gantt block'),
|
||||
() => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Title field'),
|
||||
enum: stringFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
start: {
|
||||
title: t('Start date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
default: 'createdAt',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
end: {
|
||||
title: t('End date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
progress: {
|
||||
title: t('Progress field'),
|
||||
enum: numberFields,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
range: {
|
||||
title: t('Time scale'),
|
||||
enum: [
|
||||
{ label: '{{t("Hour")}}', value: 'hour', color: 'orange' },
|
||||
{ label: '{{t("Quarter of day")}}', value: 'quarterDay', color: 'default' },
|
||||
{ label: '{{t("Half of day")}}', value: 'halfDay', color: 'blue' },
|
||||
{ label: '{{t("Day")}}', value: 'day', color: 'yellow' },
|
||||
{ label: '{{t("Week")}}', value: 'week', color: 'pule' },
|
||||
{ label: '{{t("Month")}}', value: 'month', color: 'green' },
|
||||
{ label: '{{t("Year")}}', value: 'year', color: 'green' },
|
||||
{ label: '{{t("QuarterYear")}}', value: 'quarterYear', color: 'red' },
|
||||
],
|
||||
default: 'day',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createGanttBlockUISchema({
|
||||
collectionName: item.name,
|
||||
dataSource: item.dataSource,
|
||||
fieldNames: {
|
||||
...values,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return { createGanttBlock };
|
||||
};
|
||||
|
||||
export function useCreateAssociationGanttBlock() {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { theme } = useGlobalTheme();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
|
||||
const createAssociationGanttBlock = async ({ item }) => {
|
||||
const field = item.associationField;
|
||||
|
||||
const collectionFields = getCollectionFields(item.name, item.dataSource);
|
||||
const stringFields = collectionFields
|
||||
?.filter((field) => field.type === 'string')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const dateFields = collectionFields
|
||||
?.filter((field) => field.type === 'date')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const numberFields = collectionFields
|
||||
?.filter((field) => field.type === 'float')
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(
|
||||
t('Create gantt block'),
|
||||
() => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
title: {
|
||||
title: t('Title field'),
|
||||
enum: stringFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
start: {
|
||||
title: t('Start date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
default: 'createdAt',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
end: {
|
||||
title: t('End date field'),
|
||||
enum: dateFields,
|
||||
required: true,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
progress: {
|
||||
title: t('Progress field'),
|
||||
enum: numberFields,
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
range: {
|
||||
title: t('Time scale'),
|
||||
enum: [
|
||||
{ label: '{{t("Hour")}}', value: 'hour', color: 'orange' },
|
||||
{ label: '{{t("Quarter of day")}}', value: 'quarterDay', color: 'default' },
|
||||
{ label: '{{t("Half of day")}}', value: 'halfDay', color: 'blue' },
|
||||
{ label: '{{t("Day")}}', value: 'day', color: 'yellow' },
|
||||
{ label: '{{t("Week")}}', value: 'week', color: 'pule' },
|
||||
{ label: '{{t("Month")}}', value: 'month', color: 'green' },
|
||||
{ label: '{{t("Year")}}', value: 'year', color: 'green' },
|
||||
{ label: '{{t("QuarterYear")}}', value: 'quarterYear', color: 'red' },
|
||||
],
|
||||
default: 'day',
|
||||
'x-component': 'Select',
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
},
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createGanttBlockUISchema({
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
dataSource: item.dataSource,
|
||||
fieldNames: {
|
||||
...values,
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return { createAssociationGanttBlock };
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
useACLRoleContext,
|
||||
useCollection_deprecated,
|
||||
BlockProvider,
|
||||
useBlockRequestContext,
|
||||
TableBlockProvider,
|
||||
useTableBlockContext,
|
||||
@ -95,11 +94,9 @@ export const GanttBlockProvider = (props) => {
|
||||
|
||||
return (
|
||||
<div aria-label="block-item-gantt" role="button">
|
||||
<BlockProvider name="gantt" {...props} params={params}>
|
||||
<TableBlockProvider {...props} params={params}>
|
||||
<InternalGanttBlockProvider {...props} />
|
||||
</TableBlockProvider>
|
||||
</BlockProvider>
|
||||
<TableBlockProvider {...props} params={params}>
|
||||
<InternalGanttBlockProvider {...props} />
|
||||
</TableBlockProvider>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@ -114,7 +111,7 @@ export const useGanttBlockProps = () => {
|
||||
const { getPrimaryKey, name, template, writableView } = useCollection_deprecated();
|
||||
const { parseAction } = useACLRoleContext();
|
||||
const ctxBlock = useTableBlockContext();
|
||||
|
||||
const [loading, setLoading] = useState(false);
|
||||
const primaryKey = getPrimaryKey();
|
||||
const checkPermission = (record) => {
|
||||
const actionPath = `${name}:update`;
|
||||
@ -136,6 +133,7 @@ export const useGanttBlockProps = () => {
|
||||
ctx.field.data = data;
|
||||
};
|
||||
useEffect(() => {
|
||||
setLoading(true);
|
||||
if (!ctx?.service?.loading) {
|
||||
const data = formatData(
|
||||
ctx.service.data?.data,
|
||||
@ -147,6 +145,7 @@ export const useGanttBlockProps = () => {
|
||||
primaryKey,
|
||||
);
|
||||
setTasks(data);
|
||||
setLoading(false);
|
||||
ctx.field.data = data;
|
||||
if (tasks.length > 0) {
|
||||
ctxBlock.setExpandFlag(true);
|
||||
@ -159,5 +158,6 @@ export const useGanttBlockProps = () => {
|
||||
onExpanderClick,
|
||||
expandAndCollapseAll,
|
||||
tasks,
|
||||
loading,
|
||||
};
|
||||
};
|
||||
|
@ -24,7 +24,7 @@ import {
|
||||
withDynamicSchemaProps,
|
||||
useDesignable,
|
||||
} from '@nocobase/client';
|
||||
import { message } from 'antd';
|
||||
import { message, Spin } from 'antd';
|
||||
import { debounce } from 'lodash';
|
||||
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -143,10 +143,11 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
tasks,
|
||||
expandAndCollapseAll,
|
||||
fieldNames,
|
||||
loading,
|
||||
} = useProps(props); // 新版 UISchema(1.0 之后)中已经废弃了 useProps,这里之所以继续保留是为了兼容旧版的 UISchema
|
||||
const { designable } = useDesignable();
|
||||
const headerHeight = currentTheme.includes('compact') ? 45 : designable ? 65 : 55;
|
||||
const rowHeight = currentTheme.includes('compact') ? 45 : 65;
|
||||
const headerHeight = currentTheme?.includes('compact') ? 45 : designable ? 65 : 55;
|
||||
const rowHeight = currentTheme?.includes('compact') ? 45 : 65;
|
||||
const ctx = useGanttBlockContext();
|
||||
const appInfo = useCurrentAppInfo();
|
||||
const { t } = useTranslation();
|
||||
@ -158,6 +159,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
const wrapperRef = useRef<HTMLDivElement>(null);
|
||||
const taskListRef = useRef<HTMLDivElement>(null);
|
||||
const verticalGanttContainerRef = useRef<HTMLDivElement>(null);
|
||||
const ganttRef = useRef<HTMLDivElement>(null);
|
||||
const [dateSetup, setDateSetup] = useState<DateSetup>(() => {
|
||||
const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount);
|
||||
return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
|
||||
@ -521,6 +523,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
onDoubleClick,
|
||||
onClick: handleBarClick,
|
||||
onDelete,
|
||||
loading,
|
||||
};
|
||||
|
||||
return (
|
||||
@ -536,6 +539,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
height: ${headerHeight}px;
|
||||
}
|
||||
`)}
|
||||
ref={ganttRef}
|
||||
>
|
||||
<GanttRecordViewer visible={visible} setVisible={setVisible} record={record} />
|
||||
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
|
||||
@ -576,13 +580,15 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
|
||||
onScroll={handleScrollY}
|
||||
rtl={rtl}
|
||||
/>
|
||||
<HorizontalScroll
|
||||
svgWidth={svgWidth}
|
||||
taskListWidth={taskListWidth}
|
||||
scroll={scrollX}
|
||||
rtl={rtl}
|
||||
onScroll={handleScrollX}
|
||||
/>
|
||||
<Spin spinning={loading} style={{ visibility: 'hidden' }}>
|
||||
<HorizontalScroll
|
||||
svgWidth={svgWidth}
|
||||
taskListWidth={taskListWidth}
|
||||
scroll={scrollX}
|
||||
rtl={rtl}
|
||||
onScroll={handleScrollX}
|
||||
/>
|
||||
</Spin>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -37,6 +37,7 @@ export type TaskGanttContentProps = {
|
||||
setGanttEvent: (value: GanttEvent) => void;
|
||||
setFailedTask: (value: BarTask | null) => void;
|
||||
setSelectedTask: (taskId: string) => void;
|
||||
loading?: boolean;
|
||||
} & EventOption;
|
||||
|
||||
export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({
|
||||
|
@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import React, { forwardRef, useEffect, useRef } from 'react';
|
||||
import { Spin } from 'antd';
|
||||
import { Calendar, CalendarProps } from '../calendar/calendar';
|
||||
import { Grid, GridProps } from '../grid/grid';
|
||||
import { TaskGanttContent, TaskGanttContentProps } from './task-gantt-content';
|
||||
@ -28,7 +29,6 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
|
||||
const horizontalContainerRef = useRef<HTMLDivElement>(null);
|
||||
const newBarProps = { ...barProps, svg: ganttSVGRef };
|
||||
const { styles } = useStyles();
|
||||
|
||||
useEffect(() => {
|
||||
if (horizontalContainerRef.current) {
|
||||
horizontalContainerRef.current.scrollTop = scrollY;
|
||||
@ -40,7 +40,6 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
|
||||
ref.current.scrollLeft = scrollX;
|
||||
}
|
||||
}, [scrollX]);
|
||||
|
||||
return (
|
||||
<div className={styles.ganttverticalcontainer} ref={ref} dir="ltr">
|
||||
<svg
|
||||
@ -52,23 +51,25 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
|
||||
>
|
||||
<Calendar {...calendarProps} />
|
||||
</svg>
|
||||
<div
|
||||
ref={horizontalContainerRef}
|
||||
className={styles.horizontalcontainer}
|
||||
style={ganttHeight ? { maxHeight: ganttHeight, width: gridProps.svgWidth } : { width: gridProps.svgWidth }}
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={gridProps.svgWidth}
|
||||
height={barProps.rowHeight * (barProps.tasks.length || 3)}
|
||||
fontFamily={barProps.fontFamily}
|
||||
ref={ganttSVGRef}
|
||||
className="ganttBody"
|
||||
<Spin spinning={barProps?.loading}>
|
||||
<div
|
||||
ref={horizontalContainerRef}
|
||||
className={styles.horizontalcontainer}
|
||||
style={ganttHeight ? { maxHeight: ganttHeight, width: gridProps.svgWidth } : { width: gridProps.svgWidth }}
|
||||
>
|
||||
<Grid {...gridProps} />
|
||||
<TaskGanttContent {...newBarProps} />
|
||||
</svg>
|
||||
</div>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
width={gridProps.svgWidth}
|
||||
height={barProps.rowHeight * barProps.tasks.length || 166}
|
||||
fontFamily={barProps.fontFamily}
|
||||
ref={ganttSVGRef}
|
||||
className="ganttBody"
|
||||
>
|
||||
<Grid {...gridProps} />
|
||||
<TaskGanttContent {...newBarProps} />
|
||||
</svg>
|
||||
</div>
|
||||
</Spin>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
|
@ -11,15 +11,16 @@ import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export const createGanttBlockUISchema = (options: {
|
||||
collectionName: string;
|
||||
fieldNames: object;
|
||||
dataSource: string;
|
||||
association?: string;
|
||||
collectionName?: string;
|
||||
}): ISchema => {
|
||||
const { collectionName, fieldNames, dataSource } = options;
|
||||
const { collectionName, fieldNames, dataSource, association } = options;
|
||||
|
||||
return {
|
||||
const schema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${collectionName}:list`,
|
||||
'x-acl-action': `${association || collectionName}:list`,
|
||||
'x-decorator': 'GanttBlockProvider',
|
||||
'x-decorator-props': {
|
||||
collection: collectionName,
|
||||
@ -135,4 +136,9 @@ export const createGanttBlockUISchema = (options: {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (association) {
|
||||
schema['x-decorator-props']['association'] = association;
|
||||
}
|
||||
return schema;
|
||||
};
|
||||
|
@ -17,6 +17,7 @@ import { GanttBlockProvider, useGanttBlockProps } from './GanttBlockProvider';
|
||||
import { Event } from './components/gantt/Event';
|
||||
import { Gantt } from './components/gantt/gantt';
|
||||
import { ViewMode } from './types/public-types';
|
||||
import { useCreateAssociationGanttBlock, useCreateGanttBlock } from './GanttBlockInitializer';
|
||||
|
||||
Gantt.ActionBar = ActionBar;
|
||||
Gantt.ViewMode = ViewMode;
|
||||
@ -48,7 +49,32 @@ export class PluginGanttClient extends Plugin {
|
||||
title: "{{t('Gantt')}}",
|
||||
Component: 'GanttBlockInitializer',
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('popup:common:addBlock', 'dataBlocks.gantt', {
|
||||
title: "{{t('Gantt')}}",
|
||||
Component: 'GanttBlockInitializer',
|
||||
useComponentProps() {
|
||||
const { createAssociationGanttBlock } = useCreateAssociationGanttBlock();
|
||||
const { createGanttBlock } = useCreateGanttBlock();
|
||||
|
||||
return {
|
||||
onlyCurrentDataSource: true,
|
||||
filterCollections({ associationField }) {
|
||||
if (associationField) {
|
||||
return ['hasMany', 'belongsToMany'].includes(associationField.type);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
createBlockSchema: ({ item, fromOthersInPopup }) => {
|
||||
if (fromOthersInPopup) {
|
||||
return createGanttBlock({ item });
|
||||
}
|
||||
createAssociationGanttBlock({ item });
|
||||
},
|
||||
showAssociationFields: true,
|
||||
hideSearch: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
this.app.addScopes({
|
||||
useGanttBlockProps,
|
||||
});
|
||||
|
@ -24,6 +24,8 @@ import {
|
||||
useSchemaInitializer,
|
||||
useSchemaInitializerItem,
|
||||
useAPIClient,
|
||||
Collection,
|
||||
CollectionFieldOptions,
|
||||
} from '@nocobase/client';
|
||||
import { createKanbanBlockUISchema } from './createKanbanBlockUISchema';
|
||||
import { CreateAndSelectSort } from './CreateAndSelectSort';
|
||||
@ -85,7 +87,7 @@ const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options,
|
||||
field.groupField = field.form.values?.groupField;
|
||||
field.setComponentProps({
|
||||
dataSource: item.dataSource,
|
||||
collectionName: item.name,
|
||||
collectionName: item.collectionName || item.name,
|
||||
collectionFields,
|
||||
sortFields: sortFields,
|
||||
});
|
||||
@ -109,74 +111,170 @@ const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options,
|
||||
);
|
||||
};
|
||||
|
||||
export const KanbanBlockInitializer = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
const { theme } = useGlobalTheme();
|
||||
export const KanbanBlockInitializer = ({
|
||||
filterCollections,
|
||||
onlyCurrentDataSource,
|
||||
hideSearch,
|
||||
createBlockSchema,
|
||||
showAssociationFields,
|
||||
}: {
|
||||
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
|
||||
onlyCurrentDataSource: boolean;
|
||||
hideSearch?: boolean;
|
||||
createBlockSchema?: (options: any) => any;
|
||||
showAssociationFields?: boolean;
|
||||
}) => {
|
||||
const itemConfig = useSchemaInitializerItem();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const api = useAPIClient();
|
||||
const { createKanbanBlock } = useCreateKanbanBlock();
|
||||
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...itemConfig}
|
||||
componentType={'Kanban'}
|
||||
componentType={'Calendar'}
|
||||
icon={<FormOutlined />}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const { data } = await api.resource('collections.fields', item.name).list({ paginate: false });
|
||||
const targetFields = getCollectionFields(item.name, item.dataSource);
|
||||
const collectionFields = item.dataSource === 'main' ? data.data : targetFields;
|
||||
const fields = collectionFields
|
||||
?.filter((field) => ['select', 'radioGroup'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const sortFields = collectionFields
|
||||
?.filter((field) => ['sort'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
scopeKey: field.scopeKey,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(
|
||||
t('Create kanban block'),
|
||||
<CreateKanbanForm
|
||||
item={item}
|
||||
sortFields={sortFields}
|
||||
collectionFields={collectionFields}
|
||||
fields={fields}
|
||||
options={options}
|
||||
api={api}
|
||||
/>,
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createKanbanBlockUISchema({
|
||||
sortField: values.dragSortBy,
|
||||
groupField: values.groupField.value,
|
||||
collectionName: item.name,
|
||||
dataSource: item.dataSource,
|
||||
params: {
|
||||
sort: [values.dragSortBy],
|
||||
},
|
||||
}),
|
||||
);
|
||||
onCreateBlockSchema={async (options) => {
|
||||
if (createBlockSchema) {
|
||||
return createBlockSchema(options);
|
||||
}
|
||||
createKanbanBlock(options);
|
||||
}}
|
||||
onlyCurrentDataSource={onlyCurrentDataSource}
|
||||
hideSearch={hideSearch}
|
||||
filter={filterCollections}
|
||||
showAssociationFields={showAssociationFields}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const useCreateKanbanBlock = () => {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { theme } = useGlobalTheme();
|
||||
const api = useAPIClient();
|
||||
const createKanbanBlock = async ({ item }) => {
|
||||
console.log(item);
|
||||
const collectionFields = getCollectionFields(item.name, item.dataSource);
|
||||
const fields = collectionFields
|
||||
?.filter((field) => ['select', 'radioGroup'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const sortFields = collectionFields
|
||||
?.filter((field) => ['sort'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
scopeKey: field.scopeKey,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
const values = await FormDialog(
|
||||
t('Create kanban block'),
|
||||
<CreateKanbanForm
|
||||
item={item}
|
||||
sortFields={sortFields}
|
||||
collectionFields={collectionFields}
|
||||
fields={fields}
|
||||
options={options}
|
||||
api={api}
|
||||
/>,
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createKanbanBlockUISchema({
|
||||
sortField: values.dragSortBy,
|
||||
groupField: values.groupField.value,
|
||||
collectionName: item.name,
|
||||
dataSource: item.dataSource,
|
||||
params: {
|
||||
sort: [values.dragSortBy],
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return { createKanbanBlock };
|
||||
};
|
||||
|
||||
export function useCreateAssociationKanbanBlock() {
|
||||
const { insert } = useSchemaInitializer();
|
||||
const { t } = useTranslation();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const { theme } = useGlobalTheme();
|
||||
const { getCollectionFields } = useCollectionManager_deprecated();
|
||||
const api = useAPIClient();
|
||||
|
||||
const createAssociationKanbanBlock = async ({ item }) => {
|
||||
console.log(item);
|
||||
const field = item.associationField;
|
||||
const collectionFields = getCollectionFields(item.name, item.dataSource);
|
||||
const fields = collectionFields
|
||||
?.filter((field) => ['select', 'radioGroup'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const sortFields = collectionFields
|
||||
?.filter((field) => ['sort'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
scopeKey: field.scopeKey,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const values = await FormDialog(
|
||||
t('Create kanban block'),
|
||||
<CreateKanbanForm
|
||||
item={item}
|
||||
sortFields={sortFields}
|
||||
collectionFields={collectionFields}
|
||||
fields={fields}
|
||||
options={options}
|
||||
api={api}
|
||||
/>,
|
||||
theme,
|
||||
).open({
|
||||
initialValues: {},
|
||||
});
|
||||
insert(
|
||||
createKanbanBlockUISchema({
|
||||
sortField: values.dragSortBy,
|
||||
groupField: values.groupField.value,
|
||||
association: `${field.collectionName}.${field.name}`,
|
||||
dataSource: item.dataSource,
|
||||
params: {
|
||||
sort: [values.dragSortBy],
|
||||
},
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
return { createAssociationKanbanBlock };
|
||||
}
|
||||
|
@ -120,7 +120,8 @@ const useAssociationNames = (collection) => {
|
||||
|
||||
export const KanbanBlockProvider = (props) => {
|
||||
const params = { ...props.params };
|
||||
const appends = useAssociationNames(props.collection);
|
||||
console.log(props);
|
||||
const appends = useAssociationNames(props.association || props.collection);
|
||||
if (!Object.keys(params).includes('appends')) {
|
||||
params['appends'] = appends;
|
||||
}
|
||||
|
@ -11,20 +11,22 @@ import { ISchema } from '@formily/react';
|
||||
import { uid } from '@formily/shared';
|
||||
|
||||
export const createKanbanBlockUISchema = (options: {
|
||||
collectionName: string;
|
||||
groupField: string;
|
||||
sortField: string;
|
||||
dataSource: string;
|
||||
params?: Record<string, any>;
|
||||
collectionName?: string;
|
||||
association?: string;
|
||||
}): ISchema => {
|
||||
const { collectionName, groupField, sortField, dataSource, params } = options;
|
||||
const { collectionName, groupField, sortField, dataSource, params, association } = options;
|
||||
|
||||
return {
|
||||
const schema = {
|
||||
type: 'void',
|
||||
'x-acl-action': `${collectionName}:list`,
|
||||
'x-acl-action': `${association || collectionName}:list`,
|
||||
'x-decorator': 'KanbanBlockProvider',
|
||||
'x-decorator-props': {
|
||||
collection: collectionName,
|
||||
dataSource,
|
||||
action: 'list',
|
||||
groupField,
|
||||
sortField,
|
||||
@ -32,7 +34,6 @@ export const createKanbanBlockUISchema = (options: {
|
||||
paginate: false,
|
||||
...params,
|
||||
},
|
||||
dataSource,
|
||||
},
|
||||
// 'x-designer': 'Kanban.Designer',
|
||||
'x-toolbar': 'BlockSchemaToolbar',
|
||||
@ -122,4 +123,8 @@ export const createKanbanBlockUISchema = (options: {
|
||||
},
|
||||
},
|
||||
};
|
||||
if (association) {
|
||||
schema['x-decorator-props']['association'] = association;
|
||||
}
|
||||
return schema;
|
||||
};
|
||||
|
@ -16,7 +16,11 @@ import { KanbanCardViewer } from './Kanban.CardViewer';
|
||||
import { KanbanDesigner } from './Kanban.Designer';
|
||||
import { kanbanSettings } from './Kanban.Settings';
|
||||
import { kanbanActionInitializers, kanbanActionInitializers_deprecated } from './KanbanActionInitializers';
|
||||
import { KanbanBlockInitializer } from './KanbanBlockInitializer';
|
||||
import {
|
||||
KanbanBlockInitializer,
|
||||
useCreateAssociationKanbanBlock,
|
||||
useCreateKanbanBlock,
|
||||
} from './KanbanBlockInitializer';
|
||||
import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider';
|
||||
|
||||
Kanban.Card = KanbanCard;
|
||||
@ -53,6 +57,32 @@ class PluginKanbanClient extends Plugin {
|
||||
title: '{{t("Kanban")}}',
|
||||
Component: 'KanbanBlockInitializer',
|
||||
});
|
||||
this.app.schemaInitializerManager.addItem('popup:common:addBlock', 'dataBlocks.kanban', {
|
||||
title: '{{t("Kanban")}}',
|
||||
Component: 'KanbanBlockInitializer',
|
||||
useComponentProps() {
|
||||
const { createAssociationKanbanBlock } = useCreateAssociationKanbanBlock();
|
||||
const { createKanbanBlock } = useCreateKanbanBlock();
|
||||
|
||||
return {
|
||||
onlyCurrentDataSource: true,
|
||||
filterCollections({ associationField }) {
|
||||
if (associationField) {
|
||||
return ['hasMany', 'belongsToMany'].includes(associationField.type);
|
||||
}
|
||||
return false;
|
||||
},
|
||||
createBlockSchema: ({ item, fromOthersInPopup }) => {
|
||||
if (fromOthersInPopup) {
|
||||
return createKanbanBlock({ item });
|
||||
}
|
||||
createAssociationKanbanBlock({ item });
|
||||
},
|
||||
showAssociationFields: true,
|
||||
hideSearch: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user