mirror of
https://github.com/nocobase/nocobase
synced 2024-11-15 07:06:06 +00:00
feat: kanban suport association field
This commit is contained in:
parent
86de0733ad
commit
21612d170f
@ -1,34 +1,33 @@
|
||||
import { ArrayField } from '@formily/core';
|
||||
import { createForm } from '@formily/core';
|
||||
import { Schema, useField, useFieldSchema } from '@formily/react';
|
||||
import { Spin } from 'antd';
|
||||
import uniq from 'lodash/uniq';
|
||||
import React, { createContext, useContext, useEffect } from 'react';
|
||||
import React, { createContext, useContext, useEffect, useMemo } from 'react';
|
||||
import { useACLRoleContext } from '../acl';
|
||||
import { useRequest } from '../api-client';
|
||||
import { useCollection, useCollectionManager } from '../collection-manager';
|
||||
import { FixedBlockWrapper } from '../schema-component';
|
||||
import { toColumns } from '../schema-component/antd/kanban/Kanban';
|
||||
import { BlockProvider, useBlockRequestContext } from './BlockProvider';
|
||||
import { isAssocField } from '../filter-provider/utils';
|
||||
|
||||
export const KanbanBlockContext = createContext<any>({});
|
||||
|
||||
const useGroupField = (props) => {
|
||||
const { getField } = useCollection();
|
||||
const { groupField } = props;
|
||||
const { getCollectionField } = useCollectionManager();
|
||||
const { groupField, collection } = props;
|
||||
if (typeof groupField === 'string') {
|
||||
return getField(groupField);
|
||||
return getCollectionField(`${collection}.${groupField}`);
|
||||
}
|
||||
if (groupField?.name) {
|
||||
return getField(groupField?.name);
|
||||
return getCollectionField(groupField?.name);
|
||||
}
|
||||
};
|
||||
|
||||
const InternalKanbanBlockProvider = (props) => {
|
||||
const field = useField<any>();
|
||||
const { resource, service } = useBlockRequestContext();
|
||||
const groupField = useGroupField(props);
|
||||
if (!groupField) {
|
||||
return null;
|
||||
}
|
||||
if (service.loading && !field.loaded) {
|
||||
return <Spin />;
|
||||
}
|
||||
@ -37,13 +36,13 @@ const InternalKanbanBlockProvider = (props) => {
|
||||
<FixedBlockWrapper>
|
||||
<KanbanBlockContext.Provider
|
||||
value={{
|
||||
...props,
|
||||
props: {
|
||||
resource: props.resource,
|
||||
},
|
||||
field,
|
||||
service,
|
||||
resource,
|
||||
groupField,
|
||||
fixedBlock: field?.decoratorProps?.fixedBlock,
|
||||
}}
|
||||
>
|
||||
@ -104,12 +103,35 @@ const useAssociationNames = (collection) => {
|
||||
export const KanbanBlockProvider = (props) => {
|
||||
const params = { ...props.params };
|
||||
const appends = useAssociationNames(props.collection);
|
||||
const groupField: any = useGroupField(props);
|
||||
const isAssociationField = isAssocField(groupField);
|
||||
let assocService;
|
||||
if (!groupField) {
|
||||
return null;
|
||||
}
|
||||
if (isAssociationField) {
|
||||
const options = {
|
||||
url: `${groupField.target}:list`,
|
||||
params: {
|
||||
paginate: false,
|
||||
sort: ['sort'],
|
||||
},
|
||||
};
|
||||
assocService = useRequest(options);
|
||||
params['filter'] = {
|
||||
$and: [{ [groupField.name]: { id: { $notExists: true } } }],
|
||||
};
|
||||
} else {
|
||||
params['filter'] = {
|
||||
$and: [{ [groupField.name]: { $empty: true } }],
|
||||
};
|
||||
}
|
||||
if (!Object.keys(params).includes('appends')) {
|
||||
params['appends'] = appends;
|
||||
}
|
||||
return (
|
||||
<BlockProvider {...props} params={params}>
|
||||
<InternalKanbanBlockProvider {...props} params={params} />
|
||||
<BlockProvider {...props} params={params} assocService={assocService}>
|
||||
<InternalKanbanBlockProvider {...props} params={params} assocService={assocService} groupField={groupField} />
|
||||
</BlockProvider>
|
||||
);
|
||||
};
|
||||
@ -132,11 +154,12 @@ export const useKanbanBlockProps = () => {
|
||||
const field = useField<ArrayField>();
|
||||
const ctx = useKanbanBlockContext();
|
||||
useEffect(() => {
|
||||
if (!ctx?.service?.loading) {
|
||||
if (!ctx?.service?.loading && !ctx?.assocService?.loading) {
|
||||
ctx.groupField.dataSource = ctx?.assocService?.data?.data;
|
||||
field.value = toColumns(ctx.groupField, ctx?.service?.data?.data);
|
||||
}
|
||||
// field.loading = ctx?.service?.loading;
|
||||
}, [ctx?.service?.loading]);
|
||||
}, [ctx?.service?.loading, ctx?.assocService?.loading]);
|
||||
|
||||
return {
|
||||
groupField: ctx.groupField,
|
||||
disableCardDrag: useDisableCardDrag(),
|
||||
|
@ -133,8 +133,8 @@ function UncontrolledBoard({
|
||||
addCard: handleCardAdd.bind(null, column),
|
||||
}),
|
||||
})}
|
||||
renderCard={(column, card, dragging) => {
|
||||
if (renderCard) return renderCard(card, { removeCard: handleCardRemove.bind(null, column, card), dragging });
|
||||
renderCard={(column, card, dragging,index) => {
|
||||
if (renderCard) return renderCard(card, { removeCard: handleCardRemove.bind(null, column, card), dragging },index);
|
||||
return (
|
||||
<DefaultCard
|
||||
dragging={dragging}
|
||||
@ -200,8 +200,8 @@ function ControlledBoard({
|
||||
return <ColumnAdder onConfirm={(title) => onNewColumnConfirm({ title, cards: [] })} />;
|
||||
}}
|
||||
{...(renderColumnHeader && { renderColumnHeader: renderColumnHeader })}
|
||||
renderCard={(column, card, dragging) => {
|
||||
if (renderCard) return renderCard(card, { column, dragging });
|
||||
renderCard={(column, card, dragging,index) => {
|
||||
if (renderCard) return renderCard(card, { column, dragging },index);
|
||||
return (
|
||||
<DefaultCard
|
||||
dragging={dragging}
|
||||
|
@ -1,10 +1,13 @@
|
||||
import React, { forwardRef, useState } from 'react';
|
||||
import React, { forwardRef, useState, useEffect } from 'react';
|
||||
import { Draggable } from 'react-beautiful-dnd';
|
||||
import { useKanbanBlockContext } from '../block-provider';
|
||||
import Card from './Card';
|
||||
import CardAdder from './CardAdder';
|
||||
import { pickPropOut } from './utils';
|
||||
import withDroppable from './withDroppable';
|
||||
import { useAPIClient } from '../api-client';
|
||||
import { useCollection } from '../collection-manager/hooks';
|
||||
import { isAssocField } from '../filter-provider/utils';
|
||||
|
||||
const ColumnEmptyPlaceholder = forwardRef(
|
||||
(
|
||||
@ -34,8 +37,35 @@ function Column({
|
||||
allowAddCard,
|
||||
cardAdderPosition = 'top',
|
||||
}) {
|
||||
const { fixedBlock } = useKanbanBlockContext();
|
||||
const { fixedBlock, groupField } = useKanbanBlockContext();
|
||||
const { name } = useCollection();
|
||||
const [headerHeight, setHeaderHeight] = useState(0);
|
||||
const [cardData, setCardData] = useState(children.cards);
|
||||
const isAssociationField = isAssocField(groupField);
|
||||
|
||||
const api = useAPIClient();
|
||||
useEffect(() => {
|
||||
if (children.id !== '__unknown__') {
|
||||
const filter = isAssociationField
|
||||
? {
|
||||
$and: [{ [groupField.name]: { id: { $eq: children.id } } }],
|
||||
}
|
||||
: {
|
||||
$and: [{ [groupField.name]: { $eq: children.id } }],
|
||||
};
|
||||
api
|
||||
.resource(name)
|
||||
.list({
|
||||
filter: filter,
|
||||
})
|
||||
.then(({ data }) => {
|
||||
if (data) {
|
||||
setCardData(data?.data);
|
||||
children.cards = data?.data;
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [groupField, children.id]);
|
||||
return (
|
||||
<Draggable draggableId={`column-draggable-${children.id}`} index={columnIndex} isDragDisabled={disableColumnDrag}>
|
||||
{(columnProvided) => {
|
||||
@ -64,18 +94,21 @@ function Column({
|
||||
</div>
|
||||
{cardAdderPosition === 'top' && allowAddCard && renderCardAdder({ column: children, onConfirm: onCardNew })}
|
||||
<DroppableColumn droppableId={String(children.id)}>
|
||||
{children?.cards?.length ? (
|
||||
{cardData?.length ? (
|
||||
<div
|
||||
className="react-kanban-card-skeleton"
|
||||
style={{
|
||||
height: fixedBlock ? '100%' : undefined,
|
||||
minHeight: 50,
|
||||
}}
|
||||
>
|
||||
{children.cards.map((card, index) => (
|
||||
{cardData.map((card, index) => (
|
||||
<Card
|
||||
key={card.id}
|
||||
index={index}
|
||||
renderCard={(dragging) => renderCard(children, card, dragging)}
|
||||
renderCard={(dragging) => {
|
||||
return renderCard(children, new Proxy(card, {}), dragging, index);
|
||||
}}
|
||||
disableCardDrag={disableCardDrag}
|
||||
>
|
||||
{card}
|
||||
|
@ -30,25 +30,35 @@ export const toColumns = (groupField: any, dataSource: Array<any> = []) => {
|
||||
id: '__unknown__',
|
||||
title: 'Unknown',
|
||||
color: 'default',
|
||||
cards: [],
|
||||
cards: dataSource
|
||||
},
|
||||
};
|
||||
groupField.uiSchema.enum.forEach((item) => {
|
||||
columns[item.value] = {
|
||||
id: item.value,
|
||||
title: item.label,
|
||||
color: item.color,
|
||||
cards: [],
|
||||
};
|
||||
});
|
||||
dataSource.forEach((ds) => {
|
||||
const value = ds[groupField.name];
|
||||
if (value && columns[value]) {
|
||||
columns[value].cards.push(ds);
|
||||
} else {
|
||||
columns.__unknown__.cards.push(ds);
|
||||
}
|
||||
});
|
||||
if (groupField?.dataSource) {
|
||||
groupField?.dataSource?.forEach((item) => {
|
||||
columns[item.id] = {
|
||||
id: item.id,
|
||||
title: item.id,
|
||||
cards: [],
|
||||
};
|
||||
});
|
||||
} else {
|
||||
groupField.uiSchema.enum?.forEach((item) => {
|
||||
columns[item.value] = {
|
||||
id: item.value,
|
||||
title: item.label,
|
||||
color: item.color,
|
||||
cards: [],
|
||||
};
|
||||
});
|
||||
}
|
||||
// dataSource.forEach((ds) => {
|
||||
// const value = ds[groupField.name];
|
||||
// if (value && columns[value]) {
|
||||
// columns[value].cards.push(ds);
|
||||
// } else {
|
||||
// columns.__unknown__.cards.push(ds);
|
||||
// }
|
||||
// });
|
||||
if (columns.__unknown__.cards.length === 0) {
|
||||
delete columns.__unknown__;
|
||||
}
|
||||
@ -111,9 +121,8 @@ export const Kanban: any = observer((props: any) => {
|
||||
<Tag color={color}>{title}</Tag>
|
||||
</div>
|
||||
)}
|
||||
renderCard={(card, { column, dragging }) => {
|
||||
renderCard={(card, { column, dragging },index) => {
|
||||
const columnIndex = field.value?.indexOf(column);
|
||||
const cardIndex = column?.cards?.indexOf(card);
|
||||
return (
|
||||
schemas.card && (
|
||||
<RecordProvider record={card}>
|
||||
@ -126,7 +135,7 @@ export const Kanban: any = observer((props: any) => {
|
||||
column,
|
||||
dragging,
|
||||
columnIndex,
|
||||
cardIndex,
|
||||
cardIndex:index,
|
||||
}}
|
||||
>
|
||||
<RecursionField name={schemas.card.name} schema={schemas.card} />
|
||||
|
@ -1,91 +1,91 @@
|
||||
import React, { useContext } from "react";
|
||||
import { FormDialog, FormLayout } from "@formily/antd";
|
||||
import React, { useContext } from 'react';
|
||||
import { FormDialog, FormLayout } from '@formily/antd';
|
||||
import { FormOutlined } from '@ant-design/icons';
|
||||
import { SchemaOptionsContext } from "@formily/react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { SchemaOptionsContext } from '@formily/react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import { useAPIClient } from "../../api-client";
|
||||
import { useCollectionManager } from "../../collection-manager";
|
||||
import { createKanbanBlockSchema } from "../utils";
|
||||
import { DataBlockInitializer } from "./DataBlockInitializer";
|
||||
import { SchemaComponent, SchemaComponentOptions } from "../../schema-component";
|
||||
import { useAPIClient } from '../../api-client';
|
||||
import { useCollectionManager } from '../../collection-manager';
|
||||
import { createKanbanBlockSchema } from '../utils';
|
||||
import { DataBlockInitializer } from './DataBlockInitializer';
|
||||
import { SchemaComponent, SchemaComponentOptions } from '../../schema-component';
|
||||
|
||||
export const KanbanBlockInitializer = (props) => {
|
||||
const { insert } = props;
|
||||
const { t } = useTranslation();
|
||||
const { getCollectionFields,getCollection } = useCollectionManager();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const api = useAPIClient();
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...props}
|
||||
componentType={'Kanban'}
|
||||
icon={<FormOutlined />}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collectionFields = getCollectionFields(item.name);
|
||||
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 values = await FormDialog(t('Create kanban block'), () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
groupField: {
|
||||
title: t('Grouping field'),
|
||||
enum: fields,
|
||||
required: true,
|
||||
description: '{{t("Single select and radio fields can be used as the grouping field")}}',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
objectValue: true,
|
||||
fieldNames: { label: 'label', value: 'value' },
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}).open({
|
||||
initialValues: {},
|
||||
const { insert } = props;
|
||||
const { t } = useTranslation();
|
||||
const { getCollectionFields, getCollection } = useCollectionManager();
|
||||
const options = useContext(SchemaOptionsContext);
|
||||
const api = useAPIClient();
|
||||
return (
|
||||
<DataBlockInitializer
|
||||
{...props}
|
||||
componentType={'Kanban'}
|
||||
icon={<FormOutlined />}
|
||||
onCreateBlockSchema={async ({ item }) => {
|
||||
const collectionFields = getCollectionFields(item.name);
|
||||
const fields = collectionFields
|
||||
?.filter((field) => ['select', 'radioGroup', 'linkTo', 'm2m', 'm2o', 'o2m', 'o2o','oho'].includes(field.interface))
|
||||
?.map((field) => {
|
||||
return {
|
||||
label: field?.uiSchema?.title,
|
||||
value: field.name,
|
||||
uiSchema: {
|
||||
...field.uiSchema,
|
||||
name: field.name,
|
||||
},
|
||||
};
|
||||
});
|
||||
const sortName = `${values.groupField.value}_sort`;
|
||||
const exists = collectionFields?.find((field) => field.name === sortName);
|
||||
if (!exists) {
|
||||
await api.resource('collections.fields', item.name).create({
|
||||
values: {
|
||||
type: 'sort',
|
||||
name: sortName,
|
||||
hidden: true,
|
||||
scopeKey: values.groupField.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
insert(
|
||||
createKanbanBlockSchema({
|
||||
groupField: values.groupField.value,
|
||||
collection: item.name,
|
||||
params: {
|
||||
sort: [sortName],
|
||||
paginate: false,
|
||||
},
|
||||
}),
|
||||
const values = await FormDialog(t('Create kanban block'), () => {
|
||||
return (
|
||||
<SchemaComponentOptions scope={options.scope} components={{ ...options.components }}>
|
||||
<FormLayout layout={'vertical'}>
|
||||
<SchemaComponent
|
||||
schema={{
|
||||
properties: {
|
||||
groupField: {
|
||||
title: t('Grouping field'),
|
||||
enum: fields,
|
||||
required: true,
|
||||
description: '{{t("Single select and radio fields can be used as the grouping field")}}',
|
||||
'x-component': 'Select',
|
||||
'x-component-props': {
|
||||
objectValue: true,
|
||||
fieldNames: { label: 'label', value: 'value' },
|
||||
},
|
||||
'x-decorator': 'FormItem',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</FormLayout>
|
||||
</SchemaComponentOptions>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}).open({
|
||||
initialValues: {},
|
||||
});
|
||||
const sortName = `${values.groupField.value}_sort`;
|
||||
const exists = collectionFields?.find((field) => field.name === sortName);
|
||||
if (!exists) {
|
||||
await api.resource('collections.fields', item.name).create({
|
||||
values: {
|
||||
type: 'sort',
|
||||
name: sortName,
|
||||
hidden: true,
|
||||
scopeKey: values.groupField.value,
|
||||
},
|
||||
});
|
||||
}
|
||||
insert(
|
||||
createKanbanBlockSchema({
|
||||
groupField: values.groupField.value,
|
||||
collection: item.name,
|
||||
params: {
|
||||
sort: [sortName],
|
||||
paginate: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user