tachybase_todo/packages/plugins/@tachybase/plugin-multi-app-share-collection/src/client/TableTransfer.tsx
sealday 943db605c8 perf: form item (#1171)
Co-authored-by: sealday <sealday@gmail.com>
Reviewed-on: daoyoucloud/tachybase#1171
2024-06-13 03:46:45 +08:00

400 lines
12 KiB
TypeScript

import React, { useCallback, useMemo, useState } from 'react';
import { css, useCollectionManager_deprecated, useRecord, useRequest, useToken } from '@tachybase/client';
import { connect } from '@tachybase/schema';
import { CollectionsGraph, lodash } from '@tachybase/utils/client';
import { App, Col, Input, Row, Select, Space, Spin, Table, Tag } from 'antd';
import { useTranslation } from 'react-i18next';
const excludeCollections = ['users', 'roles', 'applications'];
const useCollectionsGraph = ({ removed = [] }) => {
const { collections } = useCollectionManager_deprecated();
const findAddable = useCallback(
(name) => {
return CollectionsGraph.connectedNodes({
collections,
nodes: [name],
excludes: excludeCollections,
}).filter((name) => removed.includes(name));
},
[removed],
);
const findRemovable = useCallback(
(name) => {
return CollectionsGraph.connectedNodes({
collections,
nodes: [name],
excludes: excludeCollections,
direction: 'reverse',
}).filter((name) => !removed.includes(name));
},
[removed],
);
return {
findAddable,
findRemovable,
};
};
const useCollections = () => {
const record = useRecord();
const [selected, setSelected] = useState<any>([]);
const res1 = useRequest(
{
url: `applications/${record.name}/collectionBlacklist:list`,
params: {
paginate: false,
params: {
fields: ['name'],
},
},
},
{
onSuccess(data) {
setSelected(data.data?.map((data) => data.name));
},
},
);
const res2 = useRequest<{
data: any[];
}>({
url: `collections`,
params: {
fields: ['name', 'title', 'hidden', 'category.name', 'category.color', 'category.sort'],
sort: 'sort',
paginate: false,
},
});
const res3 = useRequest<{
data: any[];
}>({
url: `collectionCategories`,
params: {
sort: 'sort',
paginate: false,
},
});
return {
loading: res1.loading || res2.loading || res3.loading,
collections: (res2.data?.data || []).filter((item) => !item.hidden && !excludeCollections.includes(item.name)),
removed: selected,
setSelected,
categories: (res3.data?.data || []).map((cat) => ({ label: cat.name, value: cat.name })),
};
};
const includes = (text: string, s: string | string[]) => {
const values = Array.isArray(s) ? s : [s];
for (const val of values) {
if (text.toLowerCase().includes(val)) {
return true;
}
}
return false;
};
const useRemovedDataSource = ({ collections, removed }) => {
const [filter, setFilter] = useState({ name: '', category: [] });
const dataSource = useMemo(() => {
return collections.filter((collection) => {
const { name, title, category = [] } = collection;
const results = [removed.includes(collection.name)];
if (filter.name) {
results.push(includes(name, filter.name) || includes(title, filter.name));
}
if (filter.category.length > 0) {
results.push(category.some((item) => includes(item.name, filter.category)));
}
return !results.includes(false);
});
}, [collections, removed, filter]);
const setNameFilter = useMemo(
() =>
lodash.debounce((name) => {
setFilter({
...filter,
name,
});
}, 300),
[],
);
return {
dataSource,
setNameFilter,
setCategoryFilter: (category) => {
setFilter({
...filter,
category,
});
},
};
};
const useAddedDataSource = ({ collections, removed }) => {
const [filter, setFilter] = useState({ name: '', category: [] });
const dataSource = collections.filter((collection) => {
const { name, title, category = [] } = collection;
const results = [!removed.includes(collection.name)];
if (filter.name) {
results.push(includes(name, filter.name) || includes(title, filter.name));
}
if (filter.category.length > 0) {
results.push(category.some((item) => includes(item.name, filter.category)));
}
return !results.includes(false);
});
const setNameFilter = useMemo(
() =>
lodash.debounce((name) => {
setFilter({
...filter,
name,
});
}, 300),
[],
);
return {
dataSource,
setNameFilter,
setCategoryFilter: (category) => {
setFilter({
...filter,
category,
});
},
};
};
export const TableTransfer = connect((props) => {
const { onChange } = props;
const { loading, collections, categories, removed, setSelected } = useCollections();
const [selectedRowKeys1, setSelectedRowKeys1] = useState([]);
const [selectedRowKeys2, setSelectedRowKeys2] = useState([]);
const { findAddable, findRemovable } = useCollectionsGraph({ removed });
const addedDataSource = useAddedDataSource({ collections, removed });
const removedDataSource = useRemovedDataSource({ collections, removed });
const { t } = useTranslation('multi-app-share-collection');
const { modal } = App.useApp();
const { token } = useToken();
const columns = useMemo(
() => [
{
title: t('Collection display name'),
dataIndex: 'title',
},
{
title: t('Collection name'),
dataIndex: 'name',
},
{
title: t('Collection category'),
dataIndex: 'category',
render: (categories) =>
categories.map((category) => (
<Tag key={category.name} color={category.color}>
{category.name}
</Tag>
)),
},
],
[],
);
if (loading) {
return <Spin />;
}
return (
<div>
<Row
gutter={24}
className={css`
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #e6f7ff;
cursor: pointer;
}
`}
>
<Col span={12}>
<div
className={css`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: 8px;
`}
>
<strong style={{ fontSize: token.fontSizeLG, color: token.colorText }}>{t('Unshared collections')}</strong>
<Space.Compact style={{ width: 360 }}>
<Select
popupMatchSelectWidth={false}
onChange={(value) => {
removedDataSource.setCategoryFilter(value);
}}
mode={'multiple'}
style={{ width: '35%' }}
size={'middle'}
placeholder={t('All categories')}
options={categories}
allowClear
/>
<Input
onChange={(e) => removedDataSource.setNameFilter(e.target.value)}
style={{ width: '65%' }}
placeholder={t('Enter name or title...')}
allowClear
/>
</Space.Compact>
</div>
<Table
bordered
rowKey={'name'}
rowSelection={{
type: 'checkbox',
selectedRowKeys: selectedRowKeys1,
onChange(selectedRowKeys) {
const values = removed.filter((s) => !selectedRowKeys.includes(s));
setSelected(values);
onChange(values);
setSelectedRowKeys1([]);
},
}}
pagination={false}
size={'small'}
columns={columns}
// dataSource={collections.filter((collection) => removed.includes(collection.name))}
dataSource={removedDataSource.dataSource}
scroll={{ y: 'calc(100vh - 260px)' }}
onRow={({ name, disabled }: any) => ({
onClick: () => {
if (disabled) return;
const adding = findAddable(name);
const change = () => {
const values = removed.filter((s) => !adding.includes(s));
setSelected(values);
onChange(values);
};
if (adding.length === 1) {
return change();
}
modal.confirm({
title: t('Are you sure to add the following collections?'),
width: '60%',
content: (
<div>
<Table
size={'small'}
columns={columns}
dataSource={collections.filter((collection) => adding.includes(collection.name))}
pagination={false}
scroll={{ y: '60vh' }}
/>
</div>
),
onOk() {
change();
},
});
},
})}
/>
</Col>
<Col span={12}>
<div
className={css`
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
margin-bottom: 8px;
`}
>
<strong style={{ fontSize: token.fontSizeLG, color: token.colorText }}>{t('Shared collections')}</strong>
<Space.Compact style={{ width: 360 }}>
<Select
popupMatchSelectWidth={false}
onChange={(value) => {
addedDataSource.setCategoryFilter(value);
}}
mode={'multiple'}
style={{ width: '35%' }}
size={'middle'}
placeholder={t('All categories')}
options={categories}
allowClear
/>
<Input
onChange={(e) => addedDataSource.setNameFilter(e.target.value)}
style={{ width: '65%' }}
placeholder={t('Enter name or title...')}
allowClear
/>
</Space.Compact>
</div>
<Table
bordered
rowKey={'name'}
rowSelection={{
type: 'checkbox',
selectedRowKeys: selectedRowKeys2,
onChange(selectedRowKeys) {
const values = lodash.uniq(removed.concat(selectedRowKeys));
setSelected(values);
onChange(values);
setSelectedRowKeys2([]);
},
}}
pagination={false}
size={'small'}
columns={columns}
dataSource={addedDataSource.dataSource}
// dataSource={collections.filter((collection) => !selected.includes(collection.name))}
scroll={{ y: 'calc(100vh - 260px)' }}
onRow={({ name }) => ({
onClick: () => {
const removing = findRemovable(name);
const change = () => {
removed.push(...removing);
const values = lodash.uniq([...removed]);
setSelected(values);
onChange(values);
};
if (removing.length === 1) {
return change();
}
modal.confirm({
title: t('Are you sure to remove the following collections?'),
width: '60%',
content: (
<div>
<Table
size={'small'}
columns={columns}
dataSource={collections.filter((collection) => removing.includes(collection.name))}
pagination={false}
scroll={{ y: '60vh' }}
/>
</div>
),
onOk() {
change();
},
});
},
})}
/>
</Col>
</Row>
</div>
);
});
export default TableTransfer;