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:
katherinehhh 2024-05-08 10:16:39 +08:00 committed by GitHub
parent 1283312eb9
commit d787edfb47
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 536 additions and 218 deletions

View File

@ -1052,7 +1052,7 @@ export const useBulkDestroyActionProps = () => {
field.data.selectedRowKeys = []; field.data.selectedRowKeys = [];
const currentPage = service.params[0]?.page; const currentPage = service.params[0]?.page;
const totalPage = service.data?.meta?.totalPage; const totalPage = service.data?.meta?.totalPage;
if (currentPage === totalPage) { if (currentPage === totalPage && service.params[0]) {
service.params[0].page = currentPage - 1; service.params[0].page = currentPage - 1;
} }
if (callBack) { if (callBack) {

View File

@ -22,122 +22,266 @@ import {
SchemaComponent, SchemaComponent,
DataBlockInitializer, DataBlockInitializer,
SchemaComponentOptions, SchemaComponentOptions,
Collection,
CollectionFieldOptions,
} from '@nocobase/client'; } from '@nocobase/client';
import { createGanttBlockUISchema } from './createGanttBlockUISchema'; 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 { insert } = useSchemaInitializer();
const { t } = useTranslation(); const { t } = useTranslation();
const { getCollectionFields } = useCollectionManager_deprecated(); const { getCollectionFields } = useCollectionManager_deprecated();
const options = useContext(SchemaOptionsContext); const options = useContext(SchemaOptionsContext);
const { theme } = useGlobalTheme(); const { theme } = useGlobalTheme();
const itemConfig = useSchemaInitializerItem();
return ( const createGanttBlock = async ({ item }) => {
<DataBlockInitializer const collectionFields = getCollectionFields(item.name, item.dataSource);
{...itemConfig} const stringFields = collectionFields
componentType={'Gantt'} ?.filter((field) => field.type === 'string')
icon={<FormOutlined />} ?.map((field) => {
onCreateBlockSchema={async ({ item }) => { return {
const collectionFields = getCollectionFields(item.name, item.dataSource); label: field?.uiSchema?.title,
const stringFields = collectionFields value: field.name,
?.filter((field) => field.type === 'string') };
?.map((field) => { });
return { const dateFields = collectionFields
label: field?.uiSchema?.title, ?.filter((field) => field.type === 'date')
value: field.name, ?.map((field) => {
}; return {
}); label: field?.uiSchema?.title,
const dateFields = collectionFields value: field.name,
?.filter((field) => field.type === 'date') };
?.map((field) => { });
return { const numberFields = collectionFields
label: field?.uiSchema?.title, ?.filter((field) => field.type === 'float')
value: field.name, ?.map((field) => {
}; return {
}); label: field?.uiSchema?.title,
const numberFields = collectionFields value: field.name,
?.filter((field) => field.type === 'float') };
?.map((field) => { });
return { const values = await FormDialog(
label: field?.uiSchema?.title, t('Create gantt block'),
value: field.name, () => {
}; return (
}); <SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
const values = await FormDialog( <FormLayout layout={'vertical'}>
t('Create gantt block'), <SchemaComponent
() => { schema={{
return ( properties: {
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}> title: {
<FormLayout layout={'vertical'}> title: t('Title field'),
<SchemaComponent enum: stringFields,
schema={{ required: true,
properties: { 'x-component': 'Select',
title: { 'x-decorator': 'FormItem',
title: t('Title field'), },
enum: stringFields, start: {
required: true, title: t('Start date field'),
'x-component': 'Select', enum: dateFields,
'x-decorator': 'FormItem', required: true,
}, default: 'createdAt',
start: { 'x-component': 'Select',
title: t('Start date field'), 'x-decorator': 'FormItem',
enum: dateFields, },
required: true, end: {
default: 'createdAt', title: t('End date field'),
'x-component': 'Select', enum: dateFields,
'x-decorator': 'FormItem', required: true,
}, 'x-component': 'Select',
end: { 'x-decorator': 'FormItem',
title: t('End date field'), },
enum: dateFields, progress: {
required: true, title: t('Progress field'),
'x-component': 'Select', enum: numberFields,
'x-decorator': 'FormItem', 'x-component': 'Select',
}, 'x-decorator': 'FormItem',
progress: { },
title: t('Progress field'), range: {
enum: numberFields, title: t('Time scale'),
'x-component': 'Select', enum: [
'x-decorator': 'FormItem', { label: '{{t("Hour")}}', value: 'hour', color: 'orange' },
}, { label: '{{t("Quarter of day")}}', value: 'quarterDay', color: 'default' },
range: { { label: '{{t("Half of day")}}', value: 'halfDay', color: 'blue' },
title: t('Time scale'), { label: '{{t("Day")}}', value: 'day', color: 'yellow' },
enum: [ { label: '{{t("Week")}}', value: 'week', color: 'pule' },
{ label: '{{t("Hour")}}', value: 'hour', color: 'orange' }, { label: '{{t("Month")}}', value: 'month', color: 'green' },
{ label: '{{t("Quarter of day")}}', value: 'quarterDay', color: 'default' }, { label: '{{t("Year")}}', value: 'year', color: 'green' },
{ label: '{{t("Half of day")}}', value: 'halfDay', color: 'blue' }, { label: '{{t("QuarterYear")}}', value: 'quarterYear', color: 'red' },
{ label: '{{t("Day")}}', value: 'day', color: 'yellow' }, ],
{ label: '{{t("Week")}}', value: 'week', color: 'pule' }, default: 'day',
{ label: '{{t("Month")}}', value: 'month', color: 'green' }, 'x-component': 'Select',
{ label: '{{t("Year")}}', value: 'year', color: 'green' }, 'x-decorator': 'FormItem',
{ label: '{{t("QuarterYear")}}', value: 'quarterYear', color: 'red' }, },
], },
default: 'day', }}
'x-component': 'Select', />
'x-decorator': 'FormItem', </FormLayout>
}, </SchemaComponentOptions>
},
}}
/>
</FormLayout>
</SchemaComponentOptions>
);
},
theme,
).open({
initialValues: {},
});
insert(
createGanttBlockUISchema({
collectionName: item.name,
dataSource: item.dataSource,
fieldNames: {
...values,
},
}),
); );
}} },
/> 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 };
}

View File

@ -12,7 +12,6 @@ import React, { createContext, useContext, useEffect, useState } from 'react';
import { import {
useACLRoleContext, useACLRoleContext,
useCollection_deprecated, useCollection_deprecated,
BlockProvider,
useBlockRequestContext, useBlockRequestContext,
TableBlockProvider, TableBlockProvider,
useTableBlockContext, useTableBlockContext,
@ -95,11 +94,9 @@ export const GanttBlockProvider = (props) => {
return ( return (
<div aria-label="block-item-gantt" role="button"> <div aria-label="block-item-gantt" role="button">
<BlockProvider name="gantt" {...props} params={params}> <TableBlockProvider {...props} params={params}>
<TableBlockProvider {...props} params={params}> <InternalGanttBlockProvider {...props} />
<InternalGanttBlockProvider {...props} /> </TableBlockProvider>
</TableBlockProvider>
</BlockProvider>
</div> </div>
); );
}; };
@ -114,7 +111,7 @@ export const useGanttBlockProps = () => {
const { getPrimaryKey, name, template, writableView } = useCollection_deprecated(); const { getPrimaryKey, name, template, writableView } = useCollection_deprecated();
const { parseAction } = useACLRoleContext(); const { parseAction } = useACLRoleContext();
const ctxBlock = useTableBlockContext(); const ctxBlock = useTableBlockContext();
const [loading, setLoading] = useState(false);
const primaryKey = getPrimaryKey(); const primaryKey = getPrimaryKey();
const checkPermission = (record) => { const checkPermission = (record) => {
const actionPath = `${name}:update`; const actionPath = `${name}:update`;
@ -136,6 +133,7 @@ export const useGanttBlockProps = () => {
ctx.field.data = data; ctx.field.data = data;
}; };
useEffect(() => { useEffect(() => {
setLoading(true);
if (!ctx?.service?.loading) { if (!ctx?.service?.loading) {
const data = formatData( const data = formatData(
ctx.service.data?.data, ctx.service.data?.data,
@ -147,6 +145,7 @@ export const useGanttBlockProps = () => {
primaryKey, primaryKey,
); );
setTasks(data); setTasks(data);
setLoading(false);
ctx.field.data = data; ctx.field.data = data;
if (tasks.length > 0) { if (tasks.length > 0) {
ctxBlock.setExpandFlag(true); ctxBlock.setExpandFlag(true);
@ -159,5 +158,6 @@ export const useGanttBlockProps = () => {
onExpanderClick, onExpanderClick,
expandAndCollapseAll, expandAndCollapseAll,
tasks, tasks,
loading,
}; };
}; };

View File

@ -24,7 +24,7 @@ import {
withDynamicSchemaProps, withDynamicSchemaProps,
useDesignable, useDesignable,
} from '@nocobase/client'; } from '@nocobase/client';
import { message } from 'antd'; import { message, Spin } from 'antd';
import { debounce } from 'lodash'; import { debounce } from 'lodash';
import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react'; import React, { SyntheticEvent, useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -143,10 +143,11 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
tasks, tasks,
expandAndCollapseAll, expandAndCollapseAll,
fieldNames, fieldNames,
loading,
} = useProps(props); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema } = useProps(props); // 新版 UISchema1.0 之后)中已经废弃了 useProps这里之所以继续保留是为了兼容旧版的 UISchema
const { designable } = useDesignable(); const { designable } = useDesignable();
const headerHeight = currentTheme.includes('compact') ? 45 : designable ? 65 : 55; const headerHeight = currentTheme?.includes('compact') ? 45 : designable ? 65 : 55;
const rowHeight = currentTheme.includes('compact') ? 45 : 65; const rowHeight = currentTheme?.includes('compact') ? 45 : 65;
const ctx = useGanttBlockContext(); const ctx = useGanttBlockContext();
const appInfo = useCurrentAppInfo(); const appInfo = useCurrentAppInfo();
const { t } = useTranslation(); const { t } = useTranslation();
@ -158,6 +159,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
const wrapperRef = useRef<HTMLDivElement>(null); const wrapperRef = useRef<HTMLDivElement>(null);
const taskListRef = useRef<HTMLDivElement>(null); const taskListRef = useRef<HTMLDivElement>(null);
const verticalGanttContainerRef = useRef<HTMLDivElement>(null); const verticalGanttContainerRef = useRef<HTMLDivElement>(null);
const ganttRef = useRef<HTMLDivElement>(null);
const [dateSetup, setDateSetup] = useState<DateSetup>(() => { const [dateSetup, setDateSetup] = useState<DateSetup>(() => {
const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount); const [startDate, endDate] = ganttDateRange(tasks, viewMode, preStepsCount);
return { viewMode, dates: seedDates(startDate, endDate, viewMode) }; return { viewMode, dates: seedDates(startDate, endDate, viewMode) };
@ -521,6 +523,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
onDoubleClick, onDoubleClick,
onClick: handleBarClick, onClick: handleBarClick,
onDelete, onDelete,
loading,
}; };
return ( return (
@ -536,6 +539,7 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
height: ${headerHeight}px; height: ${headerHeight}px;
} }
`)} `)}
ref={ganttRef}
> >
<GanttRecordViewer visible={visible} setVisible={setVisible} record={record} /> <GanttRecordViewer visible={visible} setVisible={setVisible} record={record} />
<RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} /> <RecursionField name={'anctionBar'} schema={fieldSchema.properties.toolBar} />
@ -576,13 +580,15 @@ export const Gantt: any = withDynamicSchemaProps((props: any) => {
onScroll={handleScrollY} onScroll={handleScrollY}
rtl={rtl} rtl={rtl}
/> />
<HorizontalScroll <Spin spinning={loading} style={{ visibility: 'hidden' }}>
svgWidth={svgWidth} <HorizontalScroll
taskListWidth={taskListWidth} svgWidth={svgWidth}
scroll={scrollX} taskListWidth={taskListWidth}
rtl={rtl} scroll={scrollX}
onScroll={handleScrollX} rtl={rtl}
/> onScroll={handleScrollX}
/>
</Spin>
</div> </div>
</div> </div>
); );

View File

@ -37,6 +37,7 @@ export type TaskGanttContentProps = {
setGanttEvent: (value: GanttEvent) => void; setGanttEvent: (value: GanttEvent) => void;
setFailedTask: (value: BarTask | null) => void; setFailedTask: (value: BarTask | null) => void;
setSelectedTask: (taskId: string) => void; setSelectedTask: (taskId: string) => void;
loading?: boolean;
} & EventOption; } & EventOption;
export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({ export const TaskGanttContent: React.FC<TaskGanttContentProps> = ({

View File

@ -8,6 +8,7 @@
*/ */
import React, { forwardRef, useEffect, useRef } from 'react'; import React, { forwardRef, useEffect, useRef } from 'react';
import { Spin } from 'antd';
import { Calendar, CalendarProps } from '../calendar/calendar'; import { Calendar, CalendarProps } from '../calendar/calendar';
import { Grid, GridProps } from '../grid/grid'; import { Grid, GridProps } from '../grid/grid';
import { TaskGanttContent, TaskGanttContentProps } from './task-gantt-content'; import { TaskGanttContent, TaskGanttContentProps } from './task-gantt-content';
@ -28,7 +29,6 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
const horizontalContainerRef = useRef<HTMLDivElement>(null); const horizontalContainerRef = useRef<HTMLDivElement>(null);
const newBarProps = { ...barProps, svg: ganttSVGRef }; const newBarProps = { ...barProps, svg: ganttSVGRef };
const { styles } = useStyles(); const { styles } = useStyles();
useEffect(() => { useEffect(() => {
if (horizontalContainerRef.current) { if (horizontalContainerRef.current) {
horizontalContainerRef.current.scrollTop = scrollY; horizontalContainerRef.current.scrollTop = scrollY;
@ -40,7 +40,6 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
ref.current.scrollLeft = scrollX; ref.current.scrollLeft = scrollX;
} }
}, [scrollX]); }, [scrollX]);
return ( return (
<div className={styles.ganttverticalcontainer} ref={ref} dir="ltr"> <div className={styles.ganttverticalcontainer} ref={ref} dir="ltr">
<svg <svg
@ -52,23 +51,25 @@ export const TaskGantt: React.FC<TaskGanttProps> = forwardRef(
> >
<Calendar {...calendarProps} /> <Calendar {...calendarProps} />
</svg> </svg>
<div <Spin spinning={barProps?.loading}>
ref={horizontalContainerRef} <div
className={styles.horizontalcontainer} ref={horizontalContainerRef}
style={ganttHeight ? { maxHeight: ganttHeight, width: gridProps.svgWidth } : { width: gridProps.svgWidth }} 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"
> >
<Grid {...gridProps} /> <svg
<TaskGanttContent {...newBarProps} /> xmlns="http://www.w3.org/2000/svg"
</svg> width={gridProps.svgWidth}
</div> height={barProps.rowHeight * barProps.tasks.length || 166}
fontFamily={barProps.fontFamily}
ref={ganttSVGRef}
className="ganttBody"
>
<Grid {...gridProps} />
<TaskGanttContent {...newBarProps} />
</svg>
</div>
</Spin>
</div> </div>
); );
}, },

View File

@ -11,15 +11,16 @@ import { ISchema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
export const createGanttBlockUISchema = (options: { export const createGanttBlockUISchema = (options: {
collectionName: string;
fieldNames: object; fieldNames: object;
dataSource: string; dataSource: string;
association?: string;
collectionName?: string;
}): ISchema => { }): ISchema => {
const { collectionName, fieldNames, dataSource } = options; const { collectionName, fieldNames, dataSource, association } = options;
return { const schema = {
type: 'void', type: 'void',
'x-acl-action': `${collectionName}:list`, 'x-acl-action': `${association || collectionName}:list`,
'x-decorator': 'GanttBlockProvider', 'x-decorator': 'GanttBlockProvider',
'x-decorator-props': { 'x-decorator-props': {
collection: collectionName, collection: collectionName,
@ -135,4 +136,9 @@ export const createGanttBlockUISchema = (options: {
}, },
}, },
}; };
if (association) {
schema['x-decorator-props']['association'] = association;
}
return schema;
}; };

View File

@ -17,6 +17,7 @@ import { GanttBlockProvider, useGanttBlockProps } from './GanttBlockProvider';
import { Event } from './components/gantt/Event'; import { Event } from './components/gantt/Event';
import { Gantt } from './components/gantt/gantt'; import { Gantt } from './components/gantt/gantt';
import { ViewMode } from './types/public-types'; import { ViewMode } from './types/public-types';
import { useCreateAssociationGanttBlock, useCreateGanttBlock } from './GanttBlockInitializer';
Gantt.ActionBar = ActionBar; Gantt.ActionBar = ActionBar;
Gantt.ViewMode = ViewMode; Gantt.ViewMode = ViewMode;
@ -48,7 +49,32 @@ export class PluginGanttClient extends Plugin {
title: "{{t('Gantt')}}", title: "{{t('Gantt')}}",
Component: 'GanttBlockInitializer', 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({ this.app.addScopes({
useGanttBlockProps, useGanttBlockProps,
}); });

View File

@ -24,6 +24,8 @@ import {
useSchemaInitializer, useSchemaInitializer,
useSchemaInitializerItem, useSchemaInitializerItem,
useAPIClient, useAPIClient,
Collection,
CollectionFieldOptions,
} from '@nocobase/client'; } from '@nocobase/client';
import { createKanbanBlockUISchema } from './createKanbanBlockUISchema'; import { createKanbanBlockUISchema } from './createKanbanBlockUISchema';
import { CreateAndSelectSort } from './CreateAndSelectSort'; import { CreateAndSelectSort } from './CreateAndSelectSort';
@ -85,7 +87,7 @@ const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options,
field.groupField = field.form.values?.groupField; field.groupField = field.form.values?.groupField;
field.setComponentProps({ field.setComponentProps({
dataSource: item.dataSource, dataSource: item.dataSource,
collectionName: item.name, collectionName: item.collectionName || item.name,
collectionFields, collectionFields,
sortFields: sortFields, sortFields: sortFields,
}); });
@ -109,74 +111,170 @@ const CreateKanbanForm = ({ item, sortFields, collectionFields, fields, options,
); );
}; };
export const KanbanBlockInitializer = () => { export const KanbanBlockInitializer = ({
const { insert } = useSchemaInitializer(); filterCollections,
const { t } = useTranslation(); onlyCurrentDataSource,
const { getCollectionFields } = useCollectionManager_deprecated(); hideSearch,
const { theme } = useGlobalTheme(); createBlockSchema,
showAssociationFields,
}: {
filterCollections: (options: { collection?: Collection; associationField?: CollectionFieldOptions }) => boolean;
onlyCurrentDataSource: boolean;
hideSearch?: boolean;
createBlockSchema?: (options: any) => any;
showAssociationFields?: boolean;
}) => {
const itemConfig = useSchemaInitializerItem(); const itemConfig = useSchemaInitializerItem();
const options = useContext(SchemaOptionsContext); const { createKanbanBlock } = useCreateKanbanBlock();
const api = useAPIClient();
return ( return (
<DataBlockInitializer <DataBlockInitializer
{...itemConfig} {...itemConfig}
componentType={'Kanban'} componentType={'Calendar'}
icon={<FormOutlined />} icon={<FormOutlined />}
onCreateBlockSchema={async ({ item }) => { onCreateBlockSchema={async (options) => {
const { data } = await api.resource('collections.fields', item.name).list({ paginate: false }); if (createBlockSchema) {
const targetFields = getCollectionFields(item.name, item.dataSource); return createBlockSchema(options);
const collectionFields = item.dataSource === 'main' ? data.data : targetFields; }
const fields = collectionFields createKanbanBlock(options);
?.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],
},
}),
);
}} }}
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 };
}

View File

@ -120,7 +120,8 @@ const useAssociationNames = (collection) => {
export const KanbanBlockProvider = (props) => { export const KanbanBlockProvider = (props) => {
const params = { ...props.params }; 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')) { if (!Object.keys(params).includes('appends')) {
params['appends'] = appends; params['appends'] = appends;
} }

View File

@ -11,20 +11,22 @@ import { ISchema } from '@formily/react';
import { uid } from '@formily/shared'; import { uid } from '@formily/shared';
export const createKanbanBlockUISchema = (options: { export const createKanbanBlockUISchema = (options: {
collectionName: string;
groupField: string; groupField: string;
sortField: string; sortField: string;
dataSource: string; dataSource: string;
params?: Record<string, any>; params?: Record<string, any>;
collectionName?: string;
association?: string;
}): ISchema => { }): ISchema => {
const { collectionName, groupField, sortField, dataSource, params } = options; const { collectionName, groupField, sortField, dataSource, params, association } = options;
return { const schema = {
type: 'void', type: 'void',
'x-acl-action': `${collectionName}:list`, 'x-acl-action': `${association || collectionName}:list`,
'x-decorator': 'KanbanBlockProvider', 'x-decorator': 'KanbanBlockProvider',
'x-decorator-props': { 'x-decorator-props': {
collection: collectionName, collection: collectionName,
dataSource,
action: 'list', action: 'list',
groupField, groupField,
sortField, sortField,
@ -32,7 +34,6 @@ export const createKanbanBlockUISchema = (options: {
paginate: false, paginate: false,
...params, ...params,
}, },
dataSource,
}, },
// 'x-designer': 'Kanban.Designer', // 'x-designer': 'Kanban.Designer',
'x-toolbar': 'BlockSchemaToolbar', 'x-toolbar': 'BlockSchemaToolbar',
@ -122,4 +123,8 @@ export const createKanbanBlockUISchema = (options: {
}, },
}, },
}; };
if (association) {
schema['x-decorator-props']['association'] = association;
}
return schema;
}; };

View File

@ -16,7 +16,11 @@ import { KanbanCardViewer } from './Kanban.CardViewer';
import { KanbanDesigner } from './Kanban.Designer'; import { KanbanDesigner } from './Kanban.Designer';
import { kanbanSettings } from './Kanban.Settings'; import { kanbanSettings } from './Kanban.Settings';
import { kanbanActionInitializers, kanbanActionInitializers_deprecated } from './KanbanActionInitializers'; import { kanbanActionInitializers, kanbanActionInitializers_deprecated } from './KanbanActionInitializers';
import { KanbanBlockInitializer } from './KanbanBlockInitializer'; import {
KanbanBlockInitializer,
useCreateAssociationKanbanBlock,
useCreateKanbanBlock,
} from './KanbanBlockInitializer';
import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider'; import { KanbanBlockProvider, useKanbanBlockProps } from './KanbanBlockProvider';
Kanban.Card = KanbanCard; Kanban.Card = KanbanCard;
@ -53,6 +57,32 @@ class PluginKanbanClient extends Plugin {
title: '{{t("Kanban")}}', title: '{{t("Kanban")}}',
Component: 'KanbanBlockInitializer', 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,
};
},
});
} }
} }