feat: kanban suport association field

This commit is contained in:
katherinehhh 2023-04-13 15:18:48 +08:00
parent 86de0733ad
commit 21612d170f
5 changed files with 194 additions and 129 deletions

View File

@ -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(),

View File

@ -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}

View File

@ -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}

View File

@ -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} />

View File

@ -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,
},
}),
);
}}
/>
);
};