refactor(department): dev (#1492)

Reviewed-on: daoyoucloud/tachybase#1492
This commit is contained in:
bai.zixv 2024-09-03 18:20:15 +08:00
parent a654933ab2
commit bf1f6349f1
102 changed files with 1974 additions and 1833 deletions

View File

@ -0,0 +1,58 @@
import { uid } from '@tachybase/schema';
export const getSchemaDepartmentTable = (params) => {
const { useDataSource } = params;
return {
type: 'void',
properties: {
[uid()]: {
type: 'void',
'x-component': 'RequestProvider',
'x-component-props': {
useDataSource,
},
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
filter: {
type: 'void',
title: '{{ t("Filter") }}',
default: {
$and: [
{
title: {
$includes: '',
},
},
],
},
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
},
},
departments: {
type: 'array',
'x-component': 'InternalDepartmentTable',
'x-component-props': {
useDisabled: '{{ useDisabled }}',
},
},
},
},
},
};
};

View File

@ -0,0 +1,24 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { getSchemaDepartmentTable } from './DepartmentTable.schema';
import { InternalDepartmentTable } from './InternalDepartmentTable';
import { ProviderRequest } from './Request.povider';
import { useFilterActionProps } from './scopes/useFilterActionProps';
export const ViewDepartmentTable = ({ useDataSource, useDisabled }) => {
const schema = getSchemaDepartmentTable({ useDataSource });
return (
<SchemaComponent
schema={schema}
components={{
RequestProvider: ProviderRequest,
InternalDepartmentTable,
}}
scope={{
useDisabled,
useFilterActionProps,
}}
/>
);
};

View File

@ -0,0 +1,16 @@
import React from 'react';
interface FilterKeysContextProps {
expandedKeys: any[];
setExpandedKeys: any;
hasFilter: any;
setHasFilter: any;
}
const ContextFilterKeys = React.createContext<Partial<FilterKeysContextProps>>({});
export const ProviderContextFilterKeys = ContextFilterKeys.Provider;
export function useContextFilterKeys() {
return React.useContext(ContextFilterKeys);
}

View File

@ -0,0 +1,86 @@
import React, { useContext, useEffect } from 'react';
import { useResourceActionContext } from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { Table } from 'antd';
import { useTranslation } from '../../../locale';
import { useGetDepTree } from '../main-tab/hooks/useGetDepTree';
import { getDepartmentStr } from '../utils/getDepartmentStr';
import { useContextFilterKeys } from './FilterKeys.context';
const useDisabledDefault = () => ({
disabled: () => false,
});
export const InternalDepartmentTable = ({ useDisabled = useDisabledDefault }) => {
const { t } = useTranslation();
const service = useResourceActionContext();
const { run, data, loading, defaultRequest } = service;
const { resource, resourceOf, params } = defaultRequest || {};
const { treeData, initData, loadData } = useGetDepTree({ resource, resourceOf, params });
const field: any = useField();
const { disabled: disabled } = useDisabled();
const { hasFilter, expandedKeys, setExpandedKeys } = useContextFilterKeys();
useEffect(() => {
if (!hasFilter) {
initData(data?.data);
}
}, [data, initData, loading, hasFilter]);
const { count, page, pageSize } = data?.meta || {};
const paginationParams: any = {
defaultPageSize: params?.pageSize,
total: count,
current: page,
pageSize: pageSize,
};
return (
<Table
rowKey={'id'}
columns={[
{
dataIndex: 'title',
title: t('Department name'),
render: (text, record) => (hasFilter ? getDepartmentStr(record) : text),
},
]}
rowSelection={{
selectedRowKeys: (field?.value || []).map((fieldValue) => fieldValue.id),
onChange: (selectedRowKeys, selectedRows) => {
return field?.setValue?.(selectedRows);
},
getCheckboxProps: () => ({ disabled: disabled() }),
}}
pagination={{
...paginationParams,
showSizeChanger: true,
onChange(f, S) {
let O;
run({
...(service?.params?.[0] || {}),
page: f,
pageSize: S,
});
},
}}
dataSource={hasFilter ? data?.data || [] : treeData}
expandable={{
onExpand: (expanded, record) => {
loadData({
key: record.id,
children: record.children,
});
},
expandedRowKeys: expandedKeys,
onExpandedRowsChange: (expandedRows) => setExpandedKeys(expandedRows),
}}
/>
);
};

View File

@ -1,10 +1,10 @@
import React, { useEffect, useState } from 'react';
import { CollectionProvider_deprecated, ResourceActionContext } from '@tachybase/client';
import { collectionDepartments } from '../collections/departments.collection';
import { FilterKeysContext } from '../context/FilterKeysContext';
import { collectionDepartments } from '../main-tab/collections/departments.collection';
import { ProviderContextFilterKeys } from './FilterKeys.context';
export const RequestProvider = (prop) => {
export const ProviderRequest = (prop) => {
const [expandedKeys, setExpandedKeys] = useState([]);
const [hasFilter, setHasFilter] = useState(false);
const { useDataSource } = prop;
@ -15,9 +15,9 @@ export const RequestProvider = (prop) => {
return (
<ResourceActionContext.Provider value={service}>
<CollectionProvider_deprecated collection={collectionDepartments}>
<FilterKeysContext.Provider value={{ expandedKeys, setExpandedKeys, hasFilter, setHasFilter }}>
<ProviderContextFilterKeys value={{ expandedKeys, setExpandedKeys, hasFilter, setHasFilter }}>
{prop.children}
</FilterKeysContext.Provider>
</ProviderContextFilterKeys>
</CollectionProvider_deprecated>
</ResourceActionContext.Provider>
);

View File

@ -4,9 +4,12 @@ export const useDepartmentFilterActionProps = () => {
const collection = useCollection();
const options = useFilterFieldOptions(collection.fields);
const service = useResourceActionContext();
return useFilterFieldProps({
const result = useFilterFieldProps({
options,
params: service.state?.params?.[0] || service.params,
service,
});
return result;
};

View File

@ -8,11 +8,11 @@ import {
} from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { useTranslation } from '../../../locale';
import { FilterKeysContext } from '../context/FilterKeysContext';
import { useTranslation } from '../../../../locale';
import { useContextFilterKeys } from '../FilterKeys.context';
export const useFilterActionProps = () => {
const { setHasFilter, setExpandedKeys } = useContext(FilterKeysContext);
const { setHasFilter, setExpandedKeys } = useContextFilterKeys();
const { t } = useTranslation();
const collection = useContext(CollectionContext);
const options = useFilterFieldOptions(collection.fields);

View File

@ -1,45 +0,0 @@
import React, { useContext, useRef } from 'react';
import { SchemaComponent, useAPIClient, useResourceActionContext } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { schemaAddMembers } from '../schema/addMembers.schema';
export const AddMembers = () => {
const { department, useAddMembersAction, handleSelect } = useAction();
return (
<SchemaComponent
scope={{
useAddMembersAction,
department,
handleSelect,
}}
schema={schemaAddMembers}
/>
);
};
function useAction() {
const { department } = useContext(DepartmentsContext);
const { refresh } = useResourceActionContext();
const ref = useRef([]);
const api = useAPIClient();
const useAddMembersAction = () => ({
async run() {
const x = ref.current;
if (x?.length) {
await api.resource('departments.members', department.id).add({ values: x }), (ref.current = []);
refresh();
}
},
});
const handleSelect = (val) => {
ref.current = val;
};
return {
department,
useAddMembersAction,
handleSelect,
};
}

View File

@ -1,169 +0,0 @@
import React, { useContext, useState } from 'react';
import { createStyles, useAPIClient, useRequest } from '@tachybase/client';
import { Button, Dropdown, Empty, Input, theme } from 'antd';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useTranslation } from '../../../locale';
import { DepartmentsContext } from '../context/DepartmentsContext';
const useStyles = createStyles(({ css }) => ({
searchDropdown: css`
.ant-dropdown-menu {
max-height: 500px;
overflow-y: scroll;
}
`,
}));
export const DepartmentsTree = () => {
const { t } = useTranslation();
const { token } = theme.useToken();
const { setDepartment, setUser } = useContext(DepartmentsContext);
const [open, setOpen] = useState(false);
const [keyword, x] = useState('');
const [m, g] = useState([]);
const [d, A] = useState([]);
const [b, h] = useState(true);
const [F, C] = useState(true);
const { styles } = useStyles();
const limit = 10;
const api = useAPIClient();
const data = useRequest(
(params) =>
api
.resource('departments')
.aggregateSearch(params)
.then((result) => result?.data?.data),
{
manual: true,
onSuccess: (data, params) => {
const {
values: { type: q },
} = params[0] || {};
if (data) {
if (!q || (q === 'user' && data.users.length < limit)) {
h(false);
}
if (!q || (q === 'department' && data.departments.length < limit)) {
C(false);
}
g((se) => [...se, ...data.users]);
A((se) => [...se, ...data.departments]);
}
},
},
);
const { run } = data;
const onSearch = (keyword) => {
x(keyword);
g([]);
A([]);
h(true);
C(true);
if (keyword) {
run({ values: { keyword, limit } });
setOpen(true);
}
};
const onChange = (event) => {
if (!event.target.value) {
setUser(null);
x('');
setOpen(false);
data.mutate({});
g([]);
A([]);
}
};
const NodeLabel = (node) => {
const title = node.title;
const parent = node.parent;
return parent ? NodeLabel(parent) + ' / ' + title : title;
};
const LinkButton = (params) => (
<Button
type="link"
style={{ padding: '0 8px' }}
onClick={(P) => {
setOpen(true), run({ values: { keyword, limit, ...params } });
}}
>
{t('Load more')}
</Button>
);
const J = () => {
const M = [];
return !m.length && !d.length
? [
{
key: '0',
label: <Empty description={t('No results')} image={Empty.PRESENTED_IMAGE_SIMPLE} />,
disabled: true,
},
]
: (m.length &&
(M.push({
key: '0',
type: 'group',
label: t('Users'),
children: m.map((P) => ({
key: P.username,
label: jsxs('div', {
onClick: () => setUser(P),
children: [
jsx('div', { children: P.nickname || P.username }),
jsx('div', {
style: { fontSize: token.fontSizeSM, color: token.colorTextDescription },
children: `${P.username}${P.phone ? ' | ' + P.phone : ''}${P.email ? ' | ' + P.email : ''}`,
}),
],
}),
})),
}),
b &&
M.push({
type: 'group',
key: '0-loadMore',
label: jsx(LinkButton, { type: 'user', last: m[m.length - 1].id }),
})),
d.length &&
(M.push({
key: '1',
type: 'group',
label: t('Departments'),
children: d.map((P) => ({
key: P.id,
label: jsx('div', { onClick: () => setDepartment(P), children: NodeLabel(P) }),
})),
}),
F &&
M.push({
type: 'group',
key: '1-loadMore',
label: <LinkButton type="department" last={d[d.length - 1].id} />,
})),
M);
};
return (
<Dropdown
menu={{ items: J() }}
overlayClassName={styles.searchDropdown}
trigger={['click']}
open={open}
onOpenChange={(open) => setOpen(open)}
>
<Input.Search
allowClear
onClick={() => {
keyword || setOpen(false);
}}
onFocus={() => setDepartment(null)}
onSearch={onSearch}
onChange={onChange}
placeholder={t('Search for departments, users')}
style={{ marginBottom: '20px' }}
/>
</Dropdown>
);
};

View File

@ -1,163 +0,0 @@
import { useContext, useEffect } from 'react';
import { css, useAPIClient, useResourceActionContext } from '@tachybase/client';
import { MoreOutlined } from '@ant-design/icons';
import { App, Dropdown, Empty, Tree } from 'antd';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useTranslation } from '../../../locale';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { DepartmentsExpandedContext } from '../context/DepartmentsExpandedContext';
import { k } from '../others/k';
import { schemaHhe } from '../schema/schemaHhe';
import { schemaYye } from '../schema/schemaYye';
export const ComponentX = () => {
const { data: e, loading: t } = useResourceActionContext();
const { department, setDepartment, setUser } = useContext(DepartmentsContext);
const { treeData, nodeMap, loadData, loadedKeys, setLoadedKeys, initData, expandedKeys, setExpandedKeys } =
useContext(DepartmentsExpandedContext);
const h = (v) => {
if (!v.length) return;
const l = nodeMap[v[0]];
setDepartment(l);
setUser(null);
};
const F = (v) => {
setExpandedKeys(v);
};
const C = (v) => {
setLoadedKeys(v);
};
useEffect(() => {
initData(e == null ? void 0 : e.data);
}, [e, initData, t]);
useEffect(() => {
if (!department) return;
const v = (u) => (u.parent ? [u.parent.id, ...v(u.parent)] : []),
l = v(department);
setExpandedKeys((u) => Array.from(new Set([...u, ...l])));
}, [department, setExpandedKeys]);
return jsx('div', {
className: css`
height: 57vh;
overflow: auto;
.ant-tree-node-content-wrapper {
overflow: hidden;
}
`,
children:
treeData != null && treeData.length
? jsx(Tree.DirectoryTree, {
loadData: loadData,
treeData: treeData,
loadedKeys: loadedKeys,
onSelect: h,
selectedKeys: [department == null ? void 0 : department.id],
onExpand: F,
onLoad: C,
expandedKeys: expandedKeys,
expandAction: false,
showIcon: false,
fieldNames: { key: 'id' },
})
: jsx(Empty, { image: Empty.PRESENTED_IMAGE_SIMPLE }),
});
// return (
// useEffect(() => {
// initData(e == null ? void 0 : e.data);
// }, [e, initData, t]),
// useEffect(() => {
// if (!department) return;
// const v = (u) => (u.parent ? [u.parent.id, ...v(u.parent)] : []),
// l = v(department);
// setExpandedKeys((u) => Array.from(new Set([...u, ...l])));
// }, [department, setExpandedKeys]),
// jsx('div', {
// className: css`
// height: 57vh;
// overflow: auto;
// .ant-tree-node-content-wrapper {
// overflow: hidden;
// }
// `,
// children:
// treeData != null && treeData.length
// ? jsx(Tree.DirectoryTree, {
// loadData: loadData,
// treeData: treeData,
// loadedKeys: loadedKeys,
// onSelect: h,
// selectedKeys: [department == null ? void 0 : department.id],
// onExpand: F,
// onLoad: C,
// expandedKeys: expandedKeys,
// expandAction: false,
// showIcon: false,
// fieldNames: { key: 'id' },
// })
// : jsx(Empty, { image: Empty.PRESENTED_IMAGE_SIMPLE }),
// })
// );
};
ComponentX.Item = function ({ node: t, setVisible: o, setDrawer: a }) {
const { t: r } = useTranslation(),
{ refreshAsync: c } = useResourceActionContext(),
{ setLoadedKeys: i, expandedKeys: x, setExpandedKeys: m } = useContext(DepartmentsExpandedContext),
{ modal: g, message: d } = App.useApp(),
A = useAPIClient(),
b = () => {
g.confirm({
title: r('Delete'),
content: r('Are you sure you want to delete it?'),
onOk: () =>
k(this, null, function* () {
yield A.resource('departments').destroy({ filterByTk: t.id }),
d.success(r('Deleted successfully')),
m((v) => v.filter((l) => l !== t.id));
const C = [...x];
i([]), m([]), yield c(), m(C);
}),
});
},
h = (C) => {
a({ schema: C, node: t }), o(true);
},
F = ({ key: C, domEvent: v }) => {
switch ((v.stopPropagation(), C)) {
case 'new-sub':
h(schemaYye);
break;
case 'edit':
h(schemaHhe);
break;
case 'delete':
b();
}
};
return jsxs('div', {
style: { display: 'flex', justifyContent: 'space-between', overflow: 'hidden' },
children: [
jsx('div', {
style: { overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' },
children: t.title,
}),
jsx(Dropdown, {
menu: {
items: [
{ label: r('New sub department'), key: 'new-sub' },
{ label: r('Edit department'), key: 'edit' },
{ label: r('Delete department'), key: 'delete' },
],
onClick: F,
},
children: jsx('div', {
style: { marginLeft: '15px' },
children: jsx(MoreOutlined, {}),
}),
}),
],
});
};

View File

@ -1,34 +0,0 @@
import React from 'react';
import { SchemaComponentOptions } from '@tachybase/client';
import { Col, Row } from 'antd';
import { useDepartmentFilterActionProps } from '../scopes/useDepartmentFilterActionProps';
import { DepartmentsBlock } from './DepartmentsBlock';
import { DepartmentSelect } from './DepartmentSelect';
import { DepartmentsResourceProvider } from './DepartmentsResourceProvider';
import { DepartmentsUsersBlock } from './DepartmentsUsersBlock';
import { SuperiorDepartmentSelect } from './SuperiorDepartmentSelect';
import { UserResourceProvider } from './UserResourceProvider';
export const DepartmentManagement = () => {
return (
<SchemaComponentOptions
components={{ SuperiorDepartmentSelect, DepartmentSelect }}
scope={{ useFilterActionProps: useDepartmentFilterActionProps }}
>
<Row gutter={48} style={{ flexWrap: 'nowrap' }}>
<Col span={6} style={{ borderRight: '1px solid #eee', minWidth: '300px' }}>
<DepartmentsResourceProvider>
<DepartmentsBlock />
</DepartmentsResourceProvider>
</Col>
<Col flex="auto" style={{ overflow: 'hidden' }}>
<UserResourceProvider>
<DepartmentsUsersBlock />
</UserResourceProvider>
</Col>
</Row>
</SchemaComponentOptions>
);
};

View File

@ -1,23 +0,0 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { uid } from '@tachybase/schema';
import { DepartmentManagement } from './DepartmentManagement';
export const DepartmentManagementComponent = () => {
return (
<SchemaComponent
components={{ DepartmentManagement }}
schema={{
type: 'void',
properties: {
[uid()]: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'DepartmentManagement',
},
},
}}
/>
);
};

View File

@ -1,73 +0,0 @@
import { useEffect, useRef, useState } from 'react';
import {
ActionContextProvider,
ResourceActionProvider,
SchemaComponent,
useActionContext,
useRecord,
} from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { Select } from 'antd';
import { jsx, jsxs } from 'react/jsx-runtime';
import { schemaFfe } from '../schema/schemaFfe';
export const DepartmentOwnersField = () => {
const [e, t] = useState(false),
o = useRecord(),
a = useField(),
[r, c] = useState([]),
i = useRef([]),
x = (d, A) => {
i.current = A;
},
m = () => {
const { setVisible: d } = useActionContext();
return {
run() {
const A = a.value || [];
a.setValue([...A, ...i.current]), (i.current = []), d(false);
},
};
};
useEffect(() => {
a.value && c(a.value.map((d) => ({ value: d.id, label: d.nickname || d.username })));
}, [a.value]);
const g = (d) => {
var A;
return jsx(ResourceActionProvider, {
collection: 'users',
request: {
resource: `departments/${o.id}/members`,
action: 'list',
params: { filter: (A = a.value) != null && A.length ? { id: { $notIn: a.value.map((b) => b.id) } } : {} },
},
children: d.children,
});
};
return jsxs(ActionContextProvider, {
value: { visible: e, setVisible: t },
children: [
jsx(Select, {
open: false,
onChange: (d) => {
if (!d) {
a.setValue([]);
return;
}
a.setValue(d.map(({ label: A, value: b }) => ({ id: b, nickname: A })));
},
mode: 'multiple',
value: r,
labelInValue: true,
onDropdownVisibleChange: (d) => t(d),
}),
jsx(SchemaComponent, {
schema: schemaFfe,
components: { RequestProvider: g },
scope: { department: o, handleSelect: x, useSelectOwners: m },
}),
],
});
};

View File

@ -1,14 +0,0 @@
import React, { useContext } from 'react';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { useDepTree2 } from '../hooks/useDepTree2';
import { InternalSuperiorDepartmentSelect } from './InternalSuperiorDepartmentSelect';
export const DepartmentSelect = () => {
const depTree = useDepTree2();
const { departmentsResource } = useContext(DepartmentsContext);
const {
service: { data },
} = departmentsResource || {};
return <InternalSuperiorDepartmentSelect {...{ ...depTree, originData: data?.data }} />;
};

View File

@ -1,54 +0,0 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { uid } from '@tachybase/schema';
import { useFilterActionProps } from '../scopes/useFilterActionProps';
import { InternalDepartmentTable } from './InternalDepartmentTable';
import { RequestProvider } from './RequestProvider';
export const DepartmentTable = ({ useDataSource, useDisabled }) => (
<SchemaComponent
scope={{
useDisabled,
useFilterActionProps,
}}
components={{
InternalDepartmentTable,
RequestProvider,
}}
schema={{
type: 'void',
properties: {
[uid()]: {
type: 'void',
'x-component': 'RequestProvider',
'x-component-props': { useDataSource },
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': { style: { marginBottom: 16 } },
properties: {
filter: {
type: 'void',
title: '{{ t("Filter") }}',
default: { $and: [{ title: { $includes: '' } }] },
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': { icon: 'FilterOutlined' },
'x-align': 'left',
},
},
},
departments: {
type: 'array',
'x-component': 'InternalDepartmentTable',
'x-component-props': { useDisabled: '{{ useDisabled }}' },
},
},
},
},
}}
/>
);

View File

@ -1,12 +0,0 @@
import React from 'react';
import { useRecord } from '@tachybase/client';
export const DepartmentTitle = () => {
const record = useRecord();
const title = (dep) => {
const title = dep.title;
const parent = dep.parent;
return parent ? title(parent) + ' / ' + title : title;
};
return <>{title(record)}</>;
};

View File

@ -1,51 +0,0 @@
import React, { useContext, useMemo } from 'react';
import { CollectionProvider_deprecated, ResourceActionContext, SchemaComponent, useRequest } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
import { useTranslation } from '../../../locale';
import { collectionDepartments } from '../collections/departments.collection';
import { useAddDepartments } from '../hooks/useAddDepartments';
import { useBulkRemoveDepartmentsYyt } from '../hooks/useBulkRemoveDepartmentsYyt';
import { useDataSource2 } from '../hooks/useDataSource2';
import { useDisabledVvt } from '../hooks/useDisabledVvt';
import { useRemoveDepartmentXxt } from '../hooks/useRemoveDepartmentXxt';
import { getSchemaDdt } from '../schema/getSchemaDdt';
import { useDepartmentFilterActionProps } from '../scopes/useDepartmentFilterActionProps';
import { DepartmentTable } from './DepartmentTable';
import { DepartmentTitle } from './DepartmentTitle';
export const Departments = () => {
const { t } = useTranslation();
const { role } = useContext(RolesManagerContext);
const resource = useRequest(
{
resource: `roles/${role?.name}/departments`,
action: 'list',
params: { appends: ['parent', 'parent.parent(recursively=true)'] },
},
{ ready: !!role, refreshDeps: [role] },
);
const schema = useMemo(() => getSchemaDdt(), [role]);
return (
<ResourceActionContext.Provider value={resource}>
<CollectionProvider_deprecated collection={collectionDepartments}>
<SchemaComponent
schema={schema}
components={{
DepartmentTable: DepartmentTable,
DepartmentTitle: DepartmentTitle,
}}
scope={{
useFilterActionProps: useDepartmentFilterActionProps,
t,
useRemoveDepartment: useRemoveDepartmentXxt,
useBulkRemoveDepartments: useBulkRemoveDepartmentsYyt,
useDataSource: useDataSource2,
useDisabled: useDisabledVvt,
useAddDepartments,
}}
></SchemaComponent>
</CollectionProvider_deprecated>
</ResourceActionContext.Provider>
);
};

View File

@ -1,43 +0,0 @@
import React, { useState } from 'react';
import { useRequest } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
export const DepartmentsProvider = ({ children }) => {
const [user, setUser] = useState(null);
const [department, setDepartment] = useState(null);
const usersRequest = useRequest(
{
resource: 'users',
action: 'list',
params: {
appends: ['departments', 'departments.parent(recursively=true)'],
filter: department ? { 'departments.id': department.id } : {},
pageSize: 50,
},
},
{
manual: true,
refreshDeps: [department],
},
);
const departmentsRequest = useRequest({
resource: 'departments',
action: 'list',
params: { pagination: false, filter: { parentId: null } },
});
return (
<DepartmentsContext.Provider
value={{
user,
setUser,
department,
setDepartment,
usersResource: { service: usersRequest },
departmentsResource: { service: departmentsRequest },
}}
>
{children}
</DepartmentsContext.Provider>
);
};

View File

@ -1,66 +0,0 @@
import React, { useContext, useEffect, useMemo } from 'react';
import { SchemaComponent, useResourceActionContext } from '@tachybase/client';
import { useTranslation } from '../../../locale';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { useBulkRemoveMembersAction } from '../hooks/useBulkRemoveMembersAction';
import { useMembersDataSource } from '../hooks/useMembersDataSource';
import { useRemoveMemberAction } from '../hooks/useRemoveMemberAction';
import { useShowTotal } from '../hooks/useShowTotal';
import { getSchemaHe } from '../schema/getSchemaHe';
import { schemaWe } from '../schema/schemaWe';
import { schemaZe } from '../schema/schemaZe';
import { useDepartmentFilterActionProps } from '../scopes/useDepartmentFilterActionProps';
import { AddMembers } from './AddMembers';
import { DepartmentField } from './DepartmentField';
import { IsOwnerField } from './IsOwnerField';
import { UserDepartmentsField } from './UserDepartmentsField';
export const DepartmentsUsersBlock = () => {
const { t } = useTranslation();
const { department, user } = useContext(DepartmentsContext);
const { data, setState } = useResourceActionContext();
const MemberActions = () => (department ? <SchemaComponent schema={schemaZe} /> : null);
const RowRemoveAction = () =>
department ? (
<SchemaComponent
scope={{
useRemoveMemberAction: useRemoveMemberAction,
}}
schema={schemaWe}
/>
) : null;
const schema = useMemo(() => getSchemaHe(department, user), [department, user]);
useEffect(() => {
setState?.({
selectedRowKeys: [],
});
}, [data, setState]);
return (
<>
<h2>{user ? t('Search results') : t(department?.title ?? 'All users')}</h2>
<SchemaComponent
scope={{
useBulkRemoveMembersAction,
useMembersDataSource,
t,
useShowTotal,
useFilterActionProps: useDepartmentFilterActionProps,
}}
components={{
MemberActions,
AddMembers,
RowRemoveAction,
DepartmentField,
IsOwnerField,
UserDepartmentsField,
}}
schema={schema}
/>
</>
);
};

View File

@ -1,60 +0,0 @@
import { useContext, useEffect } from 'react';
import { useResourceActionContext } from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { Table } from 'antd';
import { jsx } from 'react/jsx-runtime';
import { useTranslation } from '../../../locale';
import { FilterKeysContext } from '../context/FilterKeysContext';
import { useDepTree2 } from '../hooks/useDepTree2';
import { T } from '../others/T';
import { y } from '../others/y';
import { getDepartmentStr } from '../utils/getDepartmentStr';
const Ze = () => ({ disabled: () => false });
export const InternalDepartmentTable = ({ useDisabled: e = Ze }) => {
const { t } = useTranslation(),
o = useResourceActionContext();
console.log(o);
const { run: a, data: r, loading: c, defaultRequest: i } = o,
{ resource: x, resourceOf: m, params: g } = i || {},
{ treeData: d, initData: A, loadData: b } = useDepTree2({ resource: x, resourceOf: m, params: g }),
h = useField(),
{ disabled: F } = e(),
{ hasFilter: C, expandedKeys: v, setExpandedKeys: l } = useContext(FilterKeysContext);
useEffect(() => {
C || A(r == null ? void 0 : r.data);
}, [r, A, c, C]);
const u = {};
if ((g != null && g.pageSize && (u.defaultPageSize = g.pageSize), !u.total && r != null && r.meta)) {
const { count: f, page: S, pageSize: O } = r.meta;
(u.total = f), (u.current = S), (u.pageSize = O);
}
return jsx(Table, {
rowKey: 'id',
columns: [{ dataIndex: 'title', title: t('Department name'), render: (f, S) => (C ? getDepartmentStr(S) : f) }],
rowSelection: {
selectedRowKeys: ((h == null ? void 0 : h.value) || []).map((f) => f.id),
onChange: (f, S) => {
let O;
return (O = h == null ? void 0 : h.setValue) == null ? void 0 : O.call(h, S);
},
getCheckboxProps: (f) => ({ disabled: F(f) }),
},
pagination: T(y({ showSizeChanger: true }, u), {
onChange(f, S) {
let O;
a(T(y({}, ((O = o == null ? void 0 : o.params) == null ? void 0 : O[0]) || {}), { page: f, pageSize: S }));
},
}),
dataSource: C ? (r == null ? void 0 : r.data) || [] : d,
expandable: {
onExpand: (f, S) => {
b({ key: S.id, children: S.children });
},
expandedRowKeys: v,
onExpandedRowsChange: (f) => l(f),
},
});
};

View File

@ -1,62 +0,0 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { useTranslation } from '../../../locale';
export const NewDepartment = () => {
const { t } = useTranslation();
return (
<SchemaComponent
scope={{ t }}
schema={{
type: 'void',
properties: {
newDepartment: {
type: 'void',
title: t('New department'),
'x-component': 'Action',
'x-component-props': { type: 'text', icon: 'PlusOutlined', style: { width: '100%', textAlign: 'left' } },
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: t('New department'),
properties: {
title: { 'x-component': 'CollectionField', 'x-decorator': 'FormItem', required: true },
parent: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.parent',
'x-component-props': { component: 'DepartmentSelect' },
},
roles: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.roles',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
},
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useCreateDepartment }}' },
},
},
},
},
},
},
},
},
}}
></SchemaComponent>
);
};

View File

@ -1,172 +0,0 @@
import React, { Fragment, useState } from 'react';
import {
ActionContextProvider,
SchemaComponent,
useAPIClient,
useRecord,
useRequest,
useResourceActionContext,
} from '@tachybase/client';
import { Field, useField, useForm } from '@tachybase/schema';
import { MoreOutlined, PlusOutlined } from '@ant-design/icons';
import { App, Button, Dropdown, Tag } from 'antd';
import { jsx, jsxs } from 'react/jsx-runtime';
import { useTranslation } from '../../../locale';
import { useDataSource } from '../hooks/useDataSource';
import { schemaJe } from '../schema/schemaJe';
import { getDepartmentStr } from '../utils/getDepartmentStr';
import { DepartmentTable } from './DepartmentTable';
export const UserDepartmentsField = () => {
const { modal, message } = App.useApp();
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const user = useRecord();
const field = useField<Field>();
const { refresh } = useResourceActionContext();
const m = (l) =>
l != null && l.length
? l.map((u) => {
return {
...u,
isMain: u.departmentsUsers?.isMain,
isOwner: u.departmentsUsers?.isOwner,
title: getDepartmentStr(u),
};
})
: [];
const api = useAPIClient();
useRequest(
() =>
api
.resource('users.departments', user.id)
.list({ appends: ['parent(recursively=true)'], pagination: false })
.then((result) => {
const u = m(result?.data?.data);
field.setValue(u);
}),
{ ready: user.id },
);
const useAddDepartments = () => {
const api = useAPIClient();
const form = useForm();
const { departments } = form.values || {};
return {
async run() {
await api.resource('users.departments', user.id).add({ values: departments.map((O) => O.id) });
form.reset();
field.setValue([
...field.value,
...departments.map((department, index) => ({
...department,
isMain: index === 0 && field.value.length === 0,
title: getDepartmentStr(department),
})),
]);
setVisible(false);
refresh();
},
};
};
const A = (department) => {
modal.confirm({
title: t('Remove department'),
content: t('Are you sure you want to remove it?'),
onOk: async () => {
await api.resource('users.departments', user.id).remove({ values: [department.id] });
message.success(t('Deleted successfully'));
field.setValue(
field.value
.filter((dep) => dep.id !== department.id)
.map((dep, index) => ({ ...dep, isMain: (department.isMain && index === 0) || dep.isMain })),
);
refresh();
},
});
};
const b = async (l) => {
await api.resource('users').setMainDepartment({ values: { userId: user.id, departmentId: l.id } });
message.success(t('Set successfully'));
field.setValue(field.value.map((u) => ({ ...u, isMain: u.id === l.id })));
refresh();
};
const h = async (l) => {
await api.resource('departments').setOwner({ values: { userId: user.id, departmentId: l.id } });
message.success(t('Set successfully'));
field.setValue(field.value.map((u) => ({ ...u, isOwner: u.id === l.id ? true : u.isOwner })));
refresh();
};
const F = async (department) => {
await api.resource('departments').removeOwner({ values: { userId: user.id, departmentId: department.id } });
message.success(t('Set successfully'));
field.setValue(field.value.map((dep) => ({ ...dep, isOwner: dep.id === department.id ? false : dep.isOwner })));
refresh();
};
const C = (l, u) => {
switch (l) {
case 'setMain':
b(u);
break;
case 'setOwner':
h(u);
break;
case 'removeOwner':
F(u);
break;
case 'remove':
A(u);
}
};
const useDisabled = () => ({ disabled: (l) => field.value.some((u) => u.id === l.id) });
return jsxs(ActionContextProvider, {
value: { visible: visible, setVisible: setVisible },
children: [
jsxs(Fragment, {
children: [
(field?.value || []).map((l) =>
jsxs(
Tag,
{
style: { padding: '5px 8px', background: 'transparent', marginBottom: '5px' },
children: [
jsx('span', { style: { marginRight: '5px' }, children: l.title }),
l.isMain ? jsx(Tag, { color: 'processing', bordered: false, children: t('Main') }) : '',
jsx(Dropdown, {
menu: {
items: [
...(l.isMain ? [] : [{ label: t('Set as main department'), key: 'setMain' }]),
{ label: t('Remove'), key: 'remove' },
],
onClick: ({ key: u }) => C(u, l),
},
children: jsx('div', {
style: { float: 'right' },
children: jsx(MoreOutlined, {}),
}),
}),
],
},
l.id,
),
),
<Button key={1} icon={<PlusOutlined />} onClick={() => setVisible(true)} />,
],
}),
<SchemaComponent
key={2}
schema={schemaJe}
components={{
DepartmentTable: DepartmentTable,
}}
scope={{
user,
useDataSource,
useAddDepartments,
useDisabled,
}}
/>,
],
});
};

View File

@ -1,10 +0,0 @@
import React from 'react';
export interface FilterKeysContextProps {
expandedKeys: any[];
setExpandedKeys: any;
hasFilter: any;
setHasFilter: any;
}
export const FilterKeysContext = React.createContext<Partial<FilterKeysContextProps>>({});

View File

@ -1,30 +0,0 @@
import { useContext } from 'react';
import { useAPIClient, useResourceActionContext } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
import { App } from 'antd';
import { useTranslation } from '../../../locale';
import { k } from '../others/k';
export const useBulkRemoveDepartmentsYyt = () => {
const { t: e } = useTranslation(),
{ message: t } = App.useApp(),
o = useAPIClient(),
{ state: a, setState: r, refresh: c } = useResourceActionContext(),
{ role: i } = useContext(RolesManagerContext);
return {
run() {
return k(this, null, function* () {
const m = a == null ? void 0 : a.selectedRowKeys;
if (!(m != null && m.length)) {
t.warning(e('Please select departments'));
return;
}
yield o.resource(`roles/${i == null ? void 0 : i.name}/departments`).remove({ values: m }),
r == null || r({ selectedRowKeys: [] }),
c();
});
},
};
};

View File

@ -1,30 +0,0 @@
import { useContext } from 'react';
import { useAPIClient, useResourceActionContext } from '@tachybase/client';
import { App } from 'antd';
import { useTranslation } from '../../../locale';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { k } from '../others/k';
export const useBulkRemoveMembersAction = () => {
const { t: e } = useTranslation(),
{ message: t } = App.useApp(),
o = useAPIClient(),
{ state: a, setState: r, refresh: c } = useResourceActionContext(),
{ department: i } = useContext(DepartmentsContext);
return {
run() {
return k(this, null, function* () {
const m = a == null ? void 0 : a.selectedRowKeys;
if (!(m != null && m.length)) {
t.warning(e('Please select members'));
return;
}
yield o.resource('departments.members', i.id).remove({ values: m }),
r == null || r({ selectedRowKeys: [] }),
c();
});
},
};
};

View File

@ -1,14 +0,0 @@
import { useContext } from 'react';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
export const useDisabledVvt = () => {
const { role: e } = useContext(RolesManagerContext);
return {
disabled: (t) => {
let o;
return (o = t == null ? void 0 : t.roles) == null
? void 0
: o.some((a) => a.name === (e == null ? void 0 : e.name));
},
};
};

View File

@ -1,19 +0,0 @@
import { useContext, useEffect } from 'react';
import { useResourceActionContext } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
export const useMembersDataSource = (e) => {
const { user: t } = useContext(DepartmentsContext),
o = useResourceActionContext();
return (
useEffect(() => {
if (t) {
e == null || e.onSuccess({ data: [t] });
return;
}
o.loading || e == null || e.onSuccess(o.data);
}, [t, o.loading]),
o
);
};

View File

@ -1,19 +0,0 @@
import { useContext } from 'react';
import { useAPIClient, useRecord, useResourceActionContext } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
import { k } from '../others/k';
export const useRemoveDepartmentXxt = () => {
const e = useAPIClient(),
{ role: t } = useContext(RolesManagerContext),
{ data: o } = useRecord(),
{ refresh: a } = useResourceActionContext();
return {
run() {
return k(this, null, function* () {
yield e.resource(`roles/${t == null ? void 0 : t.name}/departments`).remove({ values: [o.id] }), a();
});
},
};
};

View File

@ -1,19 +0,0 @@
import { useContext } from 'react';
import { useAPIClient, useRecord, useResourceActionContext } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { k } from '../others/k';
export const useRemoveMemberAction = () => {
const e = useAPIClient(),
{ department: t } = useContext(DepartmentsContext),
{ id: o } = useRecord(),
{ refresh: a } = useResourceActionContext();
return {
run() {
return k(this, null, function* () {
yield e.resource('departments.members', t.id).remove({ values: [o] }), a();
});
},
};
};

View File

@ -1,10 +0,0 @@
import { useResourceActionContext } from '@tachybase/client';
import { useTranslation } from '../../../locale';
export const useShowTotal = () => {
var o;
const { data: e } = useResourceActionContext(),
{ t } = useTranslation();
return t('Total {{count}} members', { count: (o = e == null ? void 0 : e.meta) == null ? void 0 : o.count });
};

View File

@ -1,45 +1,26 @@
import React from 'react';
import { Plugin, SchemaComponentContext, useSchemaComponentContext } from '@tachybase/client';
import { PluginACLClient } from '@tachybase/plugin-acl/client';
import { Plugin } from '@tachybase/client';
import { tval } from '../../locale';
import { DepartmentManagementComponent } from './components/DepartmentManagementComponent';
import { Departments } from './components/Departments';
import { DepartmentsProvider } from './components/DepartmentsProvider';
import { UserDepartmentsFieldNotSupport } from './components/UserDepartmentsFieldNotSupport';
import { UserDepartmentsFieldNotSupport } from './common/UserDepartmentsFieldNotSupport';
import { KitMainTabDepartments } from './main-tab/kit';
import { KitRoleAuth } from './role-auth/kit';
import { DepartmentOwnersFieldSetting } from './settings/DepartmentOwnersFieldSetting';
import { UserDepartmentsFieldSetting } from './settings/UserDepartmentsFieldSetting';
import { UserMainDepartmentFieldSetting } from './settings/UserMainDepartmentFieldSetting';
export class DepartmentsPlugin extends Plugin {
async afterAdd() {
this.pm.add(KitMainTabDepartments);
this.pm.add(KitRoleAuth);
}
async load() {
this.app.addComponents({
UserDepartmentsField: UserDepartmentsFieldNotSupport,
UserMainDepartmentField: UserDepartmentsFieldNotSupport,
DepartmentOwnersField: UserDepartmentsFieldNotSupport,
});
this.app.schemaSettingsManager.add(UserDepartmentsFieldSetting);
this.app.schemaSettingsManager.add(UserMainDepartmentFieldSetting);
this.app.schemaSettingsManager.add(DepartmentOwnersFieldSetting);
this.app.pluginSettingsManager.add('users-permissions.departments', {
icon: 'ApartmentOutlined',
title: tval('Departments'),
Component: () => {
const context = useSchemaComponentContext();
return (
<SchemaComponentContext.Provider value={{ ...context, designable: false }}>
<DepartmentsProvider>
<DepartmentManagementComponent />
</DepartmentsProvider>
</SchemaComponentContext.Provider>
);
},
sort: 2,
aclSnippet: 'pm.departments',
});
this.app.pm.get(PluginACLClient).rolesManager.add('departments', {
title: tval('Departments'),
Component: Departments,
});
}
}

View File

@ -0,0 +1,53 @@
import React, { useState } from 'react';
import { useRequest } from '@tachybase/client';
import { ProviderContextDepartments } from './context/Department.context';
export const ProviderDepartmentIndex = ({ children }) => {
const [user, setUser] = useState(null);
const [department, setDepartment] = useState(null);
const usersRequest = useRequest(
{
resource: 'users',
action: 'list',
params: {
appends: ['departments', 'departments.parent(recursively=true)'],
filter: department
? {
'departments.id': department.id,
}
: {},
pageSize: 50,
},
},
{
manual: true,
refreshDeps: [department],
},
);
const departmentsRequest = useRequest({
resource: 'departments',
action: 'list',
params: {
pagination: false,
filter: {
parentId: null,
},
},
});
const ctxValue = {
user,
setUser,
department,
setDepartment,
usersResource: {
service: usersRequest,
},
departmentsResource: {
service: departmentsRequest,
},
};
return <ProviderContextDepartments value={ctxValue}>{children}</ProviderContextDepartments>;
};

View File

@ -0,0 +1,17 @@
import React from 'react';
import { SchemaComponentContext, useSchemaComponentContext } from '@tachybase/client';
import { ViewDepartmentManagement } from './DepartmentManagement.view';
import { ProviderDepartmentIndex } from './DepartmentIndex.provider';
// TODO: 名称有待重新确认
export const DepartmentIndex = () => {
const context = useSchemaComponentContext();
return (
<SchemaComponentContext.Provider value={{ ...context, designable: false }}>
<ProviderDepartmentIndex>
<ViewDepartmentManagement />
</ProviderDepartmentIndex>
</SchemaComponentContext.Provider>
);
};

View File

@ -0,0 +1,53 @@
import React from 'react';
import { SchemaComponentOptions } from '@tachybase/client';
import { Col, Row } from 'antd';
import { useDepartmentFilterActionProps } from '../common/scopes/useDepartmentFilterActionProps';
import { DepartmentSelect } from './components/DepartmentSelect';
import { SuperiorDepartmentSelect } from './components/SuperiorDepartmentSelect';
import { DepartmentsBlock } from './departments-block/DepartmentsBlock';
import { ViewDepartmentsUsersBlock } from './departments-users-block/DepartmentsUsersBlock';
import { ProviderDepartmentsResource } from './providers/DepartmentsResource.provider';
import { ProviderUserResource } from './providers/UserResource.provider';
export const DepartmentManagement = () => (
<SchemaComponentOptions
components={{
SuperiorDepartmentSelect,
DepartmentSelect,
}}
scope={{
useFilterActionProps: useDepartmentFilterActionProps,
}}
>
<Row
gutter={48}
style={{
flexWrap: 'nowrap',
}}
>
<Col
span={6}
style={{
borderRight: '1px solid #eee',
minWidth: '300px',
}}
>
<ProviderDepartmentsResource>
<DepartmentsBlock />
</ProviderDepartmentsResource>
</Col>
<Col
flex="auto"
style={{
overflow: 'hidden',
}}
>
<ProviderUserResource>
<ViewDepartmentsUsersBlock />
</ProviderUserResource>
</Col>
</Row>
</SchemaComponentOptions>
);

View File

@ -0,0 +1,12 @@
import { uid } from '@tachybase/schema';
export const schemaDepartmentManagement = {
type: 'void',
properties: {
[uid()]: {
type: 'void',
'x-decorator': 'CardItem',
'x-component': 'DepartmentManagement',
},
},
};

View File

@ -0,0 +1,16 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { DepartmentManagement } from './DepartmentManagement.component';
import { schemaDepartmentManagement as schema } from './DepartmentManagement.schema';
export const ViewDepartmentManagement = () => {
return (
<SchemaComponent
schema={schema}
components={{
DepartmentManagement,
}}
/>
);
};

View File

@ -0,0 +1,14 @@
import React, { useContext } from 'react';
import { ContextDepartments } from '../context/Department.context';
import { useGetDepTree } from '../hooks/useGetDepTree';
import { InternalSuperiorDepartmentSelect } from './InternalSuperiorDepartmentSelect';
export const DepartmentSelect = () => {
const depTree = useGetDepTree();
const { departmentsResource } = useContext(ContextDepartments);
const { service } = departmentsResource || {};
const { data } = service || {};
return <InternalSuperiorDepartmentSelect {...depTree} originData={data?.data} />;
};

View File

@ -3,7 +3,7 @@ import { useField } from '@tachybase/schema';
import { TreeSelect } from 'antd';
import { getDepartmentStr } from '../utils/getDepartmentStr';
import { getDepartmentStr } from '../../utils/getDepartmentStr';
interface tempField {
value?: any;
@ -19,23 +19,30 @@ interface IState {
export const InternalSuperiorDepartmentSelect = (props) => {
const field = useField<tempField>();
const [value, setValue] = useState<IState>({ label: null, value: null });
const { treeData, initData, getByKeyword, loadData, loadedKeys, setLoadedKeys, originData } = props;
const onSearch = async (h) => {
if (!h) {
const onSearch = async (keyword) => {
if (!keyword) {
initData(originData);
return;
}
await getByKeyword(h);
await getByKeyword(keyword);
};
useEffect(() => {
initData(originData);
}, [originData, initData]);
useEffect(() => {
if (!field.value) {
setValue({ label: null, value: null });
setValue({
label: null,
value: null,
});
return;
}
setValue({
label: getDepartmentStr(field.value) || field.value.label,
value: field.value.id,
@ -45,16 +52,21 @@ export const InternalSuperiorDepartmentSelect = (props) => {
return (
<TreeSelect
value={value}
onSelect={(h, currValue) => {
onSelect={(value, currValue) => {
field.setValue(currValue);
}}
onChange={(h) => {
h || field.setValue(null);
onChange={(value) => {
value || field.setValue(null);
}}
treeData={treeData}
treeLoadedKeys={loadedKeys}
onTreeLoad={(keys) => setLoadedKeys(keys)}
loadData={(value) => loadData({ key: value.id, children: value.children })}
loadData={(value) =>
loadData({
key: value.id,
children: value.children,
})
}
fieldNames={{ value: 'id' }}
showSearch={true}
allowClear={true}

View File

@ -1,27 +1,33 @@
import React, { useContext, useEffect } from 'react';
import { useRecord } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { useDepTree2 } from '../hooks/useDepTree2';
import { ContextDepartments } from '../context/Department.context';
import { useGetDepTree } from '../hooks/useGetDepTree';
import { InternalSuperiorDepartmentSelect } from './InternalSuperiorDepartmentSelect';
export const SuperiorDepartmentSelect = () => {
const depTree = useDepTree2();
const depTree = useGetDepTree();
const { setTreeData, getChildrenIds } = depTree;
const record = useRecord();
const { departmentsResource } = useContext(DepartmentsContext);
const { departmentsResource } = useContext(ContextDepartments);
const {
service: { data },
} = departmentsResource || {};
useEffect(() => {
if (!record.id) return;
const ids = getChildrenIds(record.id);
ids.push(record.id);
setTreeData((x) => {
const m = (g) =>
g.map((d) => (ids.includes(d.id) && (d.disabled = true), d.children && (d.children = m(d.children)), d));
return m(x);
});
}, [setTreeData, record.id, getChildrenIds]);
return <InternalSuperiorDepartmentSelect {...{ ...depTree, originData: data?.data }} />;
};

View File

@ -20,13 +20,13 @@ interface contextType {
};
}
export const DepartmentsContext = React.createContext<contextType>({
export const ContextDepartments = React.createContext<contextType>({
user: {},
department: {},
});
export const DepartmentsContextProvider = DepartmentsContext.Provider;
export const ProviderContextDepartments = ContextDepartments.Provider;
export function useDepartments() {
return useContext(DepartmentsContext);
export function useContextDepartments() {
return useContext(ContextDepartments);
}

View File

@ -1,6 +1,6 @@
import React, { useContext } from 'react';
export interface DepartmentsExpandedProps {
interface DepartmentsExpandedProps {
initData: (C: any) => void;
treeData: any[];
setTreeData: React.Dispatch<React.SetStateAction<any[]>>;
@ -14,10 +14,10 @@ export interface DepartmentsExpandedProps {
setExpandedKeys: React.Dispatch<React.SetStateAction<any[]>>;
}
export const DepartmentsExpandedContext = React.createContext<Partial<DepartmentsExpandedProps>>({});
const ContextDepartmentsExpanded = React.createContext<Partial<DepartmentsExpandedProps>>({});
export const DepartmentsExpandedContextProvider = DepartmentsExpandedContext.Provider;
export const ProviderContextDepartmentsExpanded = ContextDepartmentsExpanded.Provider;
export function useDepartmentsExpanded() {
return useContext(DepartmentsExpandedContext);
export function useContextDepartmentsExpanded() {
return useContext(ContextDepartmentsExpanded);
}

View File

@ -0,0 +1,53 @@
import { tval } from '../../../../locale';
export const getSchemaAddNewDepartment = () => {
return {
type: 'void',
properties: {
newDepartment: {
type: 'void',
title: tval('New department'),
'x-component': 'Action',
'x-component-props': { type: 'text', icon: 'PlusOutlined', style: { width: '100%', textAlign: 'left' } },
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
title: tval('New department'),
properties: {
title: { 'x-component': 'CollectionField', 'x-decorator': 'FormItem', required: true },
parent: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.parent',
'x-component-props': { component: 'DepartmentSelect' },
},
roles: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.roles',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
properties: {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
},
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useCreateDepartment }}' },
},
},
},
},
},
},
},
},
};
};

View File

@ -0,0 +1,9 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { getSchemaAddNewDepartment } from './AddNewDepartment.schema';
export const AddNewDepartment = () => {
const schema = getSchemaAddNewDepartment();
return <SchemaComponent schema={schema}></SchemaComponent>;
};

View File

@ -2,9 +2,7 @@ import { useEffect } from 'react';
import { useActionContext, useAPIClient, useRecord, useRequest } from '@tachybase/client';
import { uid } from '@tachybase/schema';
import { T } from '../others/T';
export const schemaHhe = {
export const schemaDepartmentEdit = {
type: 'object',
properties: {
[uid()]: {
@ -12,24 +10,28 @@ export const schemaHhe = {
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues(e) {
const t = useAPIClient(),
o = useActionContext(),
a = useRecord(),
r = useRequest(
() =>
t
.resource('departments')
.get({ filterByTk: a.id, appends: ['parent(recursively=true)', 'roles', 'owners'] })
.then((c) => (c == null ? void 0 : c.data)),
T({ ...e }, { manual: true }),
);
return (
useEffect(() => {
o.visible && r.run();
}, [o.visible]),
r
useValues(options) {
const API = useAPIClient();
const ctx = useActionContext();
const record = useRecord();
const result = useRequest(
() =>
API.resource('departments')
.get({
filterByTk: record.id,
appends: ['parent(recursively=true)', 'roles', 'owners'],
})
.then((res) => res?.data),
{
...options,
manual: true,
},
);
useEffect(() => {
ctx.visible && result.run();
}, [ctx.visible]);
return result;
},
},
title: '{{t("Edit department")}}',
@ -39,14 +41,20 @@ export const schemaHhe = {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.parent',
'x-component-props': { component: 'SuperiorDepartmentSelect' },
'x-component-props': {
component: 'SuperiorDepartmentSelect',
},
},
roles: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.roles',
},
owners: { title: '{{t("Owners")}}', 'x-component': 'DepartmentOwnersField', 'x-decorator': 'FormItem' },
owners: {
title: '{{t("Owners")}}',
'x-component': 'DepartmentOwnersField',
'x-decorator': 'FormItem',
},
footer: {
type: 'void',
'x-component': 'Action.Drawer.Footer',
@ -54,12 +62,17 @@ export const schemaHhe = {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useUpdateDepartment }}' },
'x-component-props': {
type: 'primary',
useAction: '{{ useUpdateDepartment }}',
},
},
},
},

View File

@ -1,33 +1,44 @@
import { useActionContext, useRecord, useRequest } from '@tachybase/client';
import { uid } from '@tachybase/schema';
import { T } from '../others/T';
export const schemaYye = {
export const schemaDepartmentNewSub = {
type: 'object',
properties: {
[uid()]: {
title: '{{t("New sub department")}}',
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'Form',
'x-decorator-props': {
useValues(e) {
const t = useActionContext(),
o = useRecord();
useValues(options) {
const ctx = useActionContext();
const record = useRecord();
return useRequest(
() => Promise.resolve({ data: { parent: { ...o } } }),
T({ ...e }, { refreshDeps: [t.visible] }),
() =>
Promise.resolve({
data: {
parent: { ...record },
},
}),
{
...options,
refreshDeps: [ctx.visible],
},
);
},
},
title: '{{t("New sub department")}}',
properties: {
title: { 'x-component': 'CollectionField', 'x-decorator': 'FormItem' },
title: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
},
parent: {
'x-component': 'CollectionField',
'x-decorator': 'FormItem',
'x-collection-field': 'departments.parent',
'x-component-props': { component: 'DepartmentSelect' },
'x-component-props': {
component: 'DepartmentSelect',
},
},
roles: {
'x-component': 'CollectionField',
@ -41,12 +52,17 @@ export const schemaYye = {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useCreateDepartment }}' },
'x-component-props': {
type: 'primary',
useAction: '{{ useCreateDepartment }}',
},
},
},
},

View File

@ -0,0 +1,67 @@
import React, { useEffect, useRef, useState } from 'react';
import { ActionContextProvider, useActionContext, useRecord } from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { Select } from 'antd';
import { ViewUnKnownOwerns } from './UnknownOwerns.view';
export const DepartmentOwnersField = () => {
const [visible, setVisible] = useState(false);
const record = useRecord();
const field: any = useField();
const [value, setValue] = useState([]);
const ref = useRef([]);
const handleSelect = (d, currValue) => {
ref.current = currValue;
};
const useSelectOwners = () => {
const { setVisible: setVisible } = useActionContext();
return {
run() {
const fieldValue = field.value || [];
field.setValue([...fieldValue, ...ref.current]);
ref.current = [];
setVisible(false);
},
};
};
useEffect(() => {
if (field.value) {
const fieldValue = field.value.map((item) => ({
value: item.id,
label: item.nickname || item.username,
}));
setValue(fieldValue);
}
}, [field.value]);
return (
<ActionContextProvider value={{ visible, setVisible }}>
<Select
open={false}
mode={'multiple'}
value={value}
labelInValue={true}
onChange={(params) => {
if (!params) {
field.setValue([]);
return;
} else {
const values = params.map(({ label, value }) => ({
id: value,
nickname: label,
}));
field.setValue(values);
}
}}
onDropdownVisibleChange={(visible) => setVisible(visible)}
/>
<ViewUnKnownOwerns record={record} field={field} handleSelect={handleSelect} useSelectOwners={useSelectOwners} />
</ActionContextProvider>
);
};

View File

@ -4,34 +4,36 @@ import { ActionContextProvider, RecordProvider, SchemaComponent, SchemaComponent
import { UserOutlined } from '@ant-design/icons';
import { Button, Divider, Row, theme } from 'antd';
import { useTranslation } from '../../../locale';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { DepartmentsExpandedContextProvider } from '../context/DepartmentsExpandedContext';
import { useCreateDepartment } from '../hooks/useCreateDepartment';
import { useDepTree2 } from '../hooks/useDepTree2';
import { useUpdateDepartment } from '../hooks/useUpdateDepartment';
import { DepartmentsTree } from './ComponentSe';
import { ComponentX } from './ComponentX';
import { DepartmentOwnersField } from './DepartmentOwnersField';
import { NewDepartment } from './NewDepartment';
import { useTranslation } from '../../../../locale';
import { ContextDepartments } from '../context/Department.context';
import { ProviderContextDepartmentsExpanded } from '../context/DepartmentsExpanded.context';
import { useGetDepTree } from '../hooks/useGetDepTree';
import { AddNewDepartment } from './AddNewDepartment.view';
import { DepartmentOwnersField } from './DepartmentOwnersField.component';
import { DepartmentsSearch } from './DepartmentsSearch.component';
import { DepartmentsTree } from './DepartmentsTree.component';
import { useCreateDepartment } from './useCreateDepartment';
import { useUpdateDepartment } from './useUpdateDepartment';
interface drawerState {
node?: object;
schema?: object;
}
// NOTE: 部门左边-部门列表部分
export const DepartmentsBlock = () => {
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const [drawer, setDrawer] = useState<drawerState>({});
const { department, setDepartment } = useContext(DepartmentsContext);
const { department, setDepartment } = useContext(ContextDepartments);
const { token } = theme.useToken();
const m = useDepTree2({
label: ({ node }) => <ComponentX.Item node={node} setVisible={setVisible} setDrawer={setDrawer} />,
const value = useGetDepTree({
label: ({ node }) => <DepartmentsTree.Item node={node} setVisible={setVisible} setDrawer={setDrawer} />,
});
const schema = drawer.schema || {};
return (
<SchemaComponentOptions
scope={{
@ -39,9 +41,9 @@ export const DepartmentsBlock = () => {
useUpdateDepartment,
}}
>
<DepartmentsExpandedContextProvider value={m}>
<ProviderContextDepartmentsExpanded value={value}>
<Row>
<DepartmentsTree />
<DepartmentsSearch />
<Button
type="text"
icon={<UserOutlined />}
@ -57,10 +59,10 @@ export const DepartmentsBlock = () => {
>
{t('All users')}
</Button>
<NewDepartment />
<AddNewDepartment />
</Row>
<Divider style={{ margin: '12px 0' }} />
<ComponentX />
<DepartmentsTree />
<ActionContextProvider
value={{
visible,
@ -69,17 +71,14 @@ export const DepartmentsBlock = () => {
>
<RecordProvider record={drawer.node || {}}>
<SchemaComponent
scope={{
t,
}}
schema={schema}
components={{
DepartmentOwnersField,
}}
schema={drawer.schema || {}}
/>
</RecordProvider>
</ActionContextProvider>
</DepartmentsExpandedContextProvider>
</ProviderContextDepartmentsExpanded>
</SchemaComponentOptions>
);
};

View File

@ -0,0 +1,190 @@
import React, { useContext, useState } from 'react';
import { createStyles, useAPIClient, useRequest } from '@tachybase/client';
import { Button, Dropdown, Empty, Input, theme } from 'antd';
import { useTranslation } from '../../../../locale';
import { ContextDepartments } from '../context/Department.context';
const useStyles = createStyles(({ css }) => ({
searchDropdown: css`
.ant-dropdown-menu {
max-height: 500px;
overflow-y: scroll;
}
`,
}));
// NOTE: 左边部门搜索组件
export const DepartmentsSearch = () => {
const { t } = useTranslation();
const { token } = theme.useToken();
const { styles } = useStyles();
const limit = 10;
const api = useAPIClient();
const { setDepartment, setUser } = useContext(ContextDepartments);
const [open, setOpen] = useState(false);
const [keyword, setKeyword] = useState('');
const [users, setUsers] = useState([]);
const [departments, setDepartments] = useState([]);
const [isUsersBeyond, setIsUsersBeyond] = useState(true);
const [isDepartmentBeyond, setIsDepartmentBeyond] = useState(true);
const data = useRequest(
(params) =>
api
.resource('departments')
.aggregateSearch(params)
.then((result) => result?.data?.data),
{
manual: true,
onSuccess: (data, params) => {
const { values } = params[0] || {};
const { type } = values || {};
if (data) {
if (!type || (type === 'user' && data.users.length < limit)) {
setIsUsersBeyond(false);
}
if (!type || (type === 'department' && data.departments.length < limit)) {
setIsDepartmentBeyond(false);
}
setUsers((prev) => [...prev, ...data.users]);
setDepartments((prev) => [...prev, ...data.departments]);
}
},
},
);
const { run } = data;
const onSearch = (keyword) => {
setKeyword(keyword);
setUsers([]);
setDepartments([]);
setIsUsersBeyond(true);
setIsDepartmentBeyond(true);
if (keyword) {
run({ values: { keyword, limit } });
setOpen(true);
}
};
const onChange = (event) => {
if (!event.target.value) {
setUser(null);
setKeyword('');
setOpen(false);
data.mutate({});
setUsers([]);
setDepartments([]);
}
};
const NodeLabel = (node) => {
const title = node.title;
const parent = node.parent;
return parent ? NodeLabel(parent) + ' / ' + title : title;
};
const LinkButton = (params) => (
<Button
type="link"
style={{ padding: '0 8px' }}
onClick={(P) => {
setOpen(true);
run({
values: {
keyword,
limit,
...params,
},
});
}}
>
{t('Load more')}
</Button>
);
const getItems = () => {
const resultItems = [];
return !users.length && !departments.length
? [
{
key: '0',
label: <Empty description={t('No results')} image={Empty.PRESENTED_IMAGE_SIMPLE} />,
disabled: true,
},
]
: (users.length &&
(resultItems.push({
key: '0',
type: 'group',
label: t('Users'),
children: users.map((userInfo) => ({
key: userInfo.username,
label: (
<div onClick={() => {}}>
<div>{userInfo.nickname || userInfo.username}</div>,
<div
style={{
fontSize: token.fontSizeSM,
color: token.colorTextDescription,
}}
>
{`${userInfo.username}${userInfo.phone ? ' | ' + userInfo.phone : ''}${userInfo.email ? ' | ' + userInfo.email : ''}`}
</div>
</div>
),
})),
}),
isUsersBeyond &&
resultItems.push({
type: 'group',
key: '0-loadMore',
label: <LinkButton type="user" last={users[users.length - 1].id} />,
})),
departments.length &&
(resultItems.push({
key: '1',
type: 'group',
label: t('Departments'),
children: departments.map((departmentInfo) => ({
key: departmentInfo.id,
label: (
<div
onClick={() => {
setDepartment(departmentInfo);
}}
>
{NodeLabel(departmentInfo)}
</div>
),
})),
}),
isDepartmentBeyond &&
resultItems.push({
type: 'group',
key: '1-loadMore',
label: <LinkButton type="department" last={departments[departments.length - 1].id} />,
})),
resultItems);
};
return (
<Dropdown
menu={{
items: getItems(),
}}
overlayClassName={styles.searchDropdown}
trigger={['click']}
open={open}
onOpenChange={(open) => setOpen(open)}
>
<Input.Search
allowClear
onClick={() => {
keyword || setOpen(false);
}}
onFocus={() => setDepartment(null)}
onSearch={onSearch}
onChange={onChange}
placeholder={t('Search for departments, users')}
style={{ marginBottom: '20px' }}
/>
</Dropdown>
);
};

View File

@ -0,0 +1,164 @@
import React, { useContext, useEffect } from 'react';
import { css, useAPIClient, useResourceActionContext } from '@tachybase/client';
import { MoreOutlined } from '@ant-design/icons';
import { App, Dropdown, Empty, Tree } from 'antd';
import { useTranslation } from '../../../../locale';
import { schemaDepartmentEdit } from './DepartmentEdit.schema';
import { schemaDepartmentNewSub } from './DepartmentNewSub.schema';
import { ContextDepartments } from '../context/Department.context';
import { useContextDepartmentsExpanded } from '../context/DepartmentsExpanded.context';
// 部门左边-部门列表
export const DepartmentsTree = () => {
const { data, loading } = useResourceActionContext();
const { department, setDepartment, setUser } = useContext(ContextDepartments);
const { treeData, nodeMap, loadData, loadedKeys, setLoadedKeys, initData, expandedKeys, setExpandedKeys } =
useContextDepartmentsExpanded() as any;
const onSelect = (keys) => {
if (!keys.length) {
return;
}
const department = nodeMap[keys[0]];
setDepartment(department);
setUser(null);
};
const onExpand = (key) => {
setExpandedKeys(key);
};
const onLoad = (key) => {
setLoadedKeys(key);
};
useEffect(() => {
initData(data?.data);
}, [data, initData, loading]);
useEffect(() => {
if (!department) {
return;
}
const getDepartmentIds = (department) =>
department.parent ? [department.parent.id, ...getDepartmentIds(department.parent)] : [];
const departmentIds = getDepartmentIds(department);
setExpandedKeys((keys) => Array.from(new Set([...keys, ...departmentIds])));
}, [department, setExpandedKeys]);
return (
<div
className={css`
height: 57vh;
overflow: auto;
.ant-tree-node-content-wrapper {
overflow: hidden;
}
`}
>
{treeData?.length ? (
<Tree.DirectoryTree
loadData={loadData}
treeData={treeData}
loadedKeys={loadedKeys}
onSelect={onSelect}
selectedKeys={[department?.id]}
onExpand={onExpand}
onLoad={onLoad}
expandedKeys={expandedKeys}
expandAction={false}
showIcon={false}
fieldNames={{ key: 'id' }}
/>
) : (
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
)}
</div>
);
};
DepartmentsTree.Item = ({ node, setVisible, setDrawer }) => {
const { t } = useTranslation();
const { refreshAsync } = useResourceActionContext();
const { setLoadedKeys, expandedKeys, setExpandedKeys } = useContextDepartmentsExpanded();
const { modal, message } = App.useApp();
const API = useAPIClient();
const showModalDelete = () => {
modal.confirm({
title: t('Delete'),
content: t('Are you sure you want to delete it?'),
onOk: async () => {
await API.resource('departments').destroy({ filterByTk: node.id });
message.success(t('Deleted successfully'));
setExpandedKeys((keys) => keys.filter((target) => target !== node.id));
const newExpandedKeys = [...expandedKeys];
setLoadedKeys([]);
setExpandedKeys([]);
await refreshAsync();
setExpandedKeys(newExpandedKeys);
},
});
};
const setSchema = (schema) => {
setDrawer({ schema, node });
setVisible(true);
};
const onClick: any = ({ key, domeEvent }) => {
domeEvent.stopPropagation();
switch (key) {
case 'new-sub':
setSchema(schemaDepartmentNewSub);
break;
case 'edit':
setSchema(schemaDepartmentEdit);
break;
case 'delete':
showModalDelete();
}
};
return (
<div
style={{
display: 'flex',
justifyContent: 'space-between',
overflow: 'hidden',
}}
>
<div
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{node.title}
</div>
<Dropdown
menu={{
items: [
{ label: t('New sub department'), key: 'new-sub' },
{ label: t('Edit department'), key: 'edit' },
{ label: t('Delete department'), key: 'delete' },
],
onClick,
}}
>
<div style={{ marginLeft: '15px' }}>
<MoreOutlined />
</div>
</Dropdown>
</div>
);
};

View File

@ -0,0 +1,27 @@
import React from 'react';
import { ResourceActionProvider } from '@tachybase/client';
export const ProviderRequest = (props) => {
const { field, record, children } = props;
return (
// @ts-ignore
<ResourceActionProvider
collection={'users'}
request={{
resource: `departments/${record.id}/members`,
action: 'list',
params: {
filter: field.value.length
? {
id: {
$notIn: field.value.map((fieldValue) => fieldValue.id),
},
}
: {},
},
}}
>
{children}
</ResourceActionProvider>
);
};

View File

@ -1,4 +1,4 @@
export const schemaFfe = {
export const schemaUnknownOwerns = {
type: 'void',
properties: {
drawer: {
@ -13,16 +13,35 @@ export const schemaFfe = {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': { style: { marginBottom: 16 } },
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
filter: {
type: 'void',
title: '{{ t("Filter") }}',
default: { $and: [{ username: { $includes: '' } }, { nickname: { $includes: '' } }] },
default: {
$and: [
{
username: {
$includes: '',
},
},
{
nickname: {
$includes: '',
},
},
],
},
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': { icon: 'FilterOutlined' },
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
},
@ -32,7 +51,10 @@ export const schemaFfe = {
'x-component': 'Table.Void',
'x-component-props': {
rowKey: 'id',
rowSelection: { type: 'checkbox', onChange: '{{ handleSelect }}' },
rowSelection: {
type: 'checkbox',
onChange: '{{ handleSelect }}',
},
useDataSource: '{{ cm.useDataSourceFromRAC }}',
},
properties: {
@ -41,7 +63,11 @@ export const schemaFfe = {
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
username: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true },
username: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
nickname: {
@ -49,20 +75,36 @@ export const schemaFfe = {
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
nickname: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true },
nickname: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
phone: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: { phone: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
properties: {
phone: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
email: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: { email: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
properties: {
email: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
},
},
@ -75,12 +117,17 @@ export const schemaFfe = {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
confirm: {
title: '{{t("Confirm")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useSelectOwners }}' },
'x-component-props': {
type: 'primary',
useAction: '{{ useSelectOwners }}',
},
},
},
},

View File

@ -0,0 +1,30 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { schemaUnknownOwerns as schema } from './UnknownOwerns.schema';
import { ProviderRequest } from './Request.provider';
// TODO: 有待重新命名组件名称
export const ViewUnKnownOwerns = (props) => {
const { record, field, handleSelect, useSelectOwners } = props;
const RequestProvider = ({ children }) => (
<ProviderRequest field={field} record={record}>
{children}
</ProviderRequest>
);
return (
<SchemaComponent
schema={schema}
components={{
RequestProvider,
}}
scope={{
department: record,
handleSelect,
useSelectOwners,
}}
/>
);
};

View File

@ -1,8 +1,7 @@
import { useContext } from 'react';
import { useActionContext, useAPIClient, useResourceActionContext } from '@tachybase/client';
import { useField, useForm } from '@tachybase/schema';
import { DepartmentsExpandedContext } from '../context/DepartmentsExpandedContext';
import { useContextDepartmentsExpanded } from '../context/DepartmentsExpanded.context';
export const useCreateDepartment = () => {
const form = useForm();
@ -10,7 +9,7 @@ export const useCreateDepartment = () => {
const { setVisible } = useActionContext();
const { refreshAsync } = useResourceActionContext();
const api = useAPIClient();
const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContext(DepartmentsExpandedContext);
const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContextDepartmentsExpanded();
return {
async run() {
try {

View File

@ -1,9 +1,8 @@
import { useContext } from 'react';
import { useActionContext, useAPIClient, useRecord, useResourceActionContext } from '@tachybase/client';
import { useField, useForm } from '@tachybase/schema';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { DepartmentsExpandedContext } from '../context/DepartmentsExpandedContext';
import { useContextDepartments } from '../context/Department.context';
import { useContextDepartmentsExpanded } from '../context/DepartmentsExpanded.context';
export const useUpdateDepartment = () => {
const field = useField();
@ -12,8 +11,8 @@ export const useUpdateDepartment = () => {
const { refreshAsync } = useResourceActionContext();
const api = useAPIClient();
const { id } = useRecord();
const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContext(DepartmentsExpandedContext);
const { department, setDepartment } = useContext(DepartmentsContext);
const { expandedKeys, setLoadedKeys, setExpandedKeys } = useContextDepartmentsExpanded();
const { department, setDepartment } = useContextDepartments();
return {
async run() {
await form.submit();

View File

@ -0,0 +1,19 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { schemaAddMembers as schema } from './AddMembers.schema';
import { usePropsAddMembers } from './scopes/usePropsAddMember';
export const ViewAddMembers = () => {
const { department, useAddMembersAction, handleSelect } = usePropsAddMembers();
return (
<SchemaComponent
schema={schema}
scope={{
useAddMembersAction,
department,
handleSelect,
}}
/>
);
};

View File

@ -2,11 +2,11 @@ import React, { useContext } from 'react';
import { EllipsisWithTooltip } from '@tachybase/client';
import { useField } from '@tachybase/schema';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { getDepartmentStr } from '../utils/getDepartmentStr';
import { getDepartmentStr } from '../../utils/getDepartmentStr';
import { ContextDepartments } from '../context/Department.context';
export const DepartmentField = () => {
const { setDepartment } = useContext(DepartmentsContext);
const { setDepartment } = useContext(ContextDepartments);
const field = useField<{ value: Array<any> }>();
const fieldValues = field.value || [];
@ -27,7 +27,18 @@ export const DepartmentField = () => {
>
{getDepartmentStr(currFieldValue)}
</a>
{index !== fieldValues.length - 1 ? <span style={{ marginRight: 4, color: '#aaa' }}>,</span> : ''}
{index !== fieldValues.length - 1 ? (
<span
style={{
marginRight: 4,
color: '#aaa',
}}
>
,
</span>
) : (
''
)}
</span>
))}
</EllipsisWithTooltip>

View File

@ -0,0 +1,176 @@
import { uid } from '@tachybase/schema';
export const getSchemaDepartmentsUsersBlock = (department, user) => {
const schemaNotUser = user
? {}
: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
[uid()]: {
type: 'void',
title: '{{ t("Filter") }}',
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
actions: {
type: 'void',
'x-component': 'MemberActions',
},
},
},
};
const tableSchemaDepartMent = department
? {
isOwner: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
'x-component-props': { style: { minWidth: 100 } },
title: '{{t("Owner")}}',
properties: {
isOwner: {
type: 'boolean',
'x-component': 'IsOwnerField',
},
},
},
}
: {};
const schemaActions = department
? {
remove: {
type: 'void',
'x-component': 'RowRemoveAction',
},
}
: {};
return {
type: 'void',
properties: {
...schemaNotUser,
table: {
type: 'void',
'x-component': 'Table.Void',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useMembersDataSource }}',
pagination: {
showTotal: '{{ useShowTotal }}',
},
},
properties: {
nickname: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
nickname: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
username: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
username: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
departments: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
departments: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true },
},
},
...tableSchemaDepartMent,
phone: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
phone: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
email: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
email: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
actions: {
type: 'void',
title: '{{t("Actions")}}',
'x-component': 'Table.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': { split: '|' },
properties: {
update: {
type: 'void',
title: '{{t("Configure")}}',
'x-component': 'Action.Link',
'x-component-props': { type: 'primary' },
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'FormV2',
title: '{{t("Configure")}}',
properties: {
departments: {
title: '{{t("Departments")}}',
'x-decorator': 'FormItem',
'x-component': 'UserDepartmentsField',
},
},
},
},
},
...schemaActions,
},
},
},
},
},
},
},
};
};

View File

@ -0,0 +1,58 @@
import React, { useContext, useEffect, useMemo } from 'react';
import { SchemaComponent, useResourceActionContext } from '@tachybase/client';
import { useTranslation } from '../../../../locale';
import { useDepartmentFilterActionProps } from '../../common/scopes/useDepartmentFilterActionProps';
import { ContextDepartments } from '../context/Department.context';
import { ViewAddMembers } from './AddMembers.view';
import { DepartmentField } from './DepartmentField.component';
import { getSchemaDepartmentsUsersBlock } from './DepartmentsUsersBlock.schema';
import { IsOwnerField } from './IsOwnerField.component';
import { ViewMemberActions } from './MemberActions.view';
import { ViewRowRemoveAction } from './RowRemoveAction.view';
import { useBulkRemoveMembersAction } from './scopes/useBulkRemoveMembersAction';
import { useMembersDataSource } from './scopes/useMembersDataSource';
import { useShowTotal } from './scopes/useShowTotal';
import { UserDepartmentsField } from './UserDepartmentsField.component';
// 部门右边-用户列表部分
export const ViewDepartmentsUsersBlock = () => {
const { t } = useTranslation();
const { department, user } = useContext(ContextDepartments);
const { data, setState } = useResourceActionContext();
const MemberActions = () => <ViewMemberActions department={department} />;
const RowRemoveAction = () => <ViewRowRemoveAction department={department} />;
const schema = useMemo(() => getSchemaDepartmentsUsersBlock(department, user), [department, user]);
useEffect(() => {
setState?.({
selectedRowKeys: [],
});
}, [data, setState]);
return (
<>
<h2>{user ? t('Search results') : t(department?.title ?? 'All users')}</h2>
<SchemaComponent
schema={schema}
components={{
MemberActions,
AddMembers: ViewAddMembers,
RowRemoveAction,
DepartmentField,
IsOwnerField,
UserDepartmentsField,
}}
scope={{
useBulkRemoveMembersAction,
useMembersDataSource,
useShowTotal,
useFilterActionProps: useDepartmentFilterActionProps,
}}
/>
</>
);
};

View File

@ -1,10 +1,10 @@
import React, { useContext } from 'react';
import React from 'react';
import { Checkbox, useRecord } from '@tachybase/client';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { useContextDepartments } from '../context/Department.context';
export const IsOwnerField = () => {
const { department } = useContext(DepartmentsContext);
const { department } = useContextDepartments();
const dep = useRecord().departments?.find((d) => d?.id === department?.id);
return <Checkbox.ReadPretty value={dep?.departmentsUsers.isOwner} />;
};

View File

@ -1,4 +1,4 @@
export const schemaZe = {
export const schemaMemberActions = {
type: 'void',
'x-component': 'Space',
properties: {
@ -12,7 +12,9 @@ export const schemaZe = {
title: "{{t('Remove members')}}",
content: "{{t('Are you sure you want to remove these members?')}}",
},
style: { marginRight: 8 },
style: {
marginRight: 8,
},
useAction: '{{ useBulkRemoveMembersAction }}',
},
},
@ -20,8 +22,16 @@ export const schemaZe = {
type: 'void',
title: '{{t("Add members")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', icon: 'UserAddOutlined' },
properties: { drawer: { type: 'void', 'x-component': 'AddMembers' } },
'x-component-props': {
type: 'primary',
icon: 'UserAddOutlined',
},
properties: {
drawer: {
type: 'void',
'x-component': 'AddMembers',
},
},
},
},
};

View File

@ -0,0 +1,9 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { schemaMemberActions } from './MemberActions.schema';
export const ViewMemberActions = (props) => {
const { department } = props;
return department ? <SchemaComponent schema={schemaMemberActions} /> : null;
};

View File

@ -1,11 +1,14 @@
export const schemaWe = {
export const schemaRowRemoveAction = {
type: 'void',
properties: {
remove: {
title: '{{ t("Remove") }}',
'x-component': 'Action.Link',
'x-component-props': {
confirm: { title: "{{t('Remove member')}}", content: "{{t('Are you sure you want to remove it?')}}" },
confirm: {
title: "{{t('Remove member')}}",
content: "{{t('Are you sure you want to remove it?')}}",
},
useAction: '{{ useRemoveMemberAction }}',
},
},

View File

@ -0,0 +1,18 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { schemaRowRemoveAction as schema } from './RowRemoveAction.schema';
import { useRemoveMemberAction } from './scopes/useRemoveMemberAction';
export const ViewRowRemoveAction = (props) => {
const { department } = props;
return department ? (
<SchemaComponent
schema={schema}
scope={{
useRemoveMemberAction: useRemoveMemberAction,
}}
/>
) : null;
};

View File

@ -1,4 +1,4 @@
export const schemaJe = {
export const schemaUnknownUserDepartment = {
type: 'void',
properties: {
drawer: {
@ -10,7 +10,10 @@ export const schemaJe = {
type: 'void',
'x-decorator': 'FormItem',
'x-component': 'DepartmentTable',
'x-component-props': { useDataSource: '{{ useDataSource }}', useDisabled: '{{ useDisabled }}' },
'x-component-props': {
useDataSource: '{{ useDataSource }}',
useDisabled: '{{ useDisabled }}',
},
},
footer: {
type: 'void',
@ -19,12 +22,17 @@ export const schemaJe = {
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
confirm: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useAddDepartments }}' },
'x-component-props': {
type: 'primary',
useAction: '{{ useAddDepartments }}',
},
},
},
},

View File

@ -0,0 +1,26 @@
import React from 'react';
import { SchemaComponent } from '@tachybase/client';
import { ViewDepartmentTable } from '../../common/DepartmentTable.view';
import { useDataSource } from './scopes/useDataSource';
import { schemaUnknownUserDepartment as schema } from './UnknownUserDepartment.schema';
// TODO: 有待重新命名组件
export const ViewUnknownUserDepartment = (props) => {
const { user, useAddDepartments, useDisabled } = props;
return (
<SchemaComponent
key={2}
schema={schema}
components={{
DepartmentTable: ViewDepartmentTable,
}}
scope={{
user,
useDataSource,
useAddDepartments,
useDisabled,
}}
/>
);
};

View File

@ -0,0 +1,167 @@
import React, { Fragment, useState } from 'react';
import {
ActionContextProvider,
useAPIClient,
useRecord,
useRequest,
useResourceActionContext,
} from '@tachybase/client';
import { Field, useField, useForm } from '@tachybase/schema';
import { MoreOutlined, PlusOutlined } from '@ant-design/icons';
import { App, Button, Dropdown, Tag } from 'antd';
import { useTranslation } from '../../../../locale';
import { getDepartmentStr } from '../../utils/getDepartmentStr';
import { ViewUnknownUserDepartment } from './UnknownUserDepartment.view';
export const UserDepartmentsField = () => {
const { modal, message } = App.useApp();
const API = useAPIClient();
const { t } = useTranslation();
const [visible, setVisible] = useState(false);
const user = useRecord();
const field = useField<Field>();
const { refresh } = useResourceActionContext();
const mapDepartment = (value = []) => {
return value.map((item) => ({
...item,
title: getDepartmentStr(item),
isMain: item.departmentsUsers?.isMain,
isOwner: item.departmentsUsers?.isOwner,
}));
};
useRequest(
() =>
API.resource('users.departments', user.id)
.list({
appends: ['parent(recursively=true)'],
pagination: false,
})
.then((result) => {
const value = mapDepartment(result?.data?.data);
field.setValue(value);
}),
{ ready: user.id },
);
const useAddDepartments = () => {
const api = useAPIClient();
const form = useForm();
const { departments } = form.values || {};
return {
async run() {
await api.resource('users.departments', user.id).add({ values: departments.map((O) => O.id) });
form.reset();
field.setValue([
...field.value,
...departments.map((department, index) => ({
...department,
isMain: index === 0 && field.value.length === 0,
title: getDepartmentStr(department),
})),
]);
setVisible(false);
refresh();
},
};
};
const showModalRemove = (department) => {
modal.confirm({
title: t('Remove department'),
content: t('Are you sure you want to remove it?'),
onOk: async () => {
await API.resource('users.departments', user.id).remove({ values: [department.id] });
message.success(t('Deleted successfully'));
field.setValue(
field.value
.filter((dep) => dep.id !== department.id)
.map((dep, index) => ({ ...dep, isMain: (department.isMain && index === 0) || dep.isMain })),
);
refresh();
},
});
};
const setMain = async (l) => {
await API.resource('users').setMainDepartment({
values: {
userId: user.id,
departmentId: l.id,
},
});
message.success(t('Set successfully'));
field.setValue(field.value.map((u) => ({ ...u, isMain: u.id === l.id })));
refresh();
};
const setOwner = async (l) => {
await API.resource('departments').setOwner({ values: { userId: user.id, departmentId: l.id } });
message.success(t('Set successfully'));
field.setValue(field.value.map((u) => ({ ...u, isOwner: u.id === l.id ? true : u.isOwner })));
refresh();
};
const removeOwner = async (department) => {
await API.resource('departments').removeOwner({ values: { userId: user.id, departmentId: department.id } });
message.success(t('Set successfully'));
field.setValue(field.value.map((dep) => ({ ...dep, isOwner: dep.id === department.id ? false : dep.isOwner })));
refresh();
};
const C = (l, u) => {
switch (l) {
case 'setMain':
setMain(u);
break;
case 'setOwner':
setOwner(u);
break;
case 'removeOwner':
removeOwner(u);
break;
case 'remove':
showModalRemove(u);
}
};
const useDisabled = () => ({ disabled: (l) => field.value.some((u) => u.id === l.id) });
return (
<ActionContextProvider value={{ visible: visible, setVisible: setVisible }}>
<Fragment>
{(field?.value || []).map((val) => (
<Tag key={val.id} style={{ padding: '5px 8px', background: 'transparent', marginBottom: '5px' }}>
<span style={{ marginRight: '5px' }}>{val.title}</span>
{val.isMain ? (
<Tag color={'processing'} bordered={false}>
{t('Main')}
</Tag>
) : (
''
)}
<Dropdown
menu={{
items: [
...(val.isMain ? [] : [{ label: t('Set as main department'), key: 'setMain' }]),
{ label: t('Remove'), key: 'remove' },
],
onClick: ({ key }) => C(key, val),
}}
>
<div style={{ float: 'right' }}>
{' '}
<MoreOutlined />
</div>
</Dropdown>
</Tag>
))}
<Button key={1} icon={<PlusOutlined />} onClick={() => setVisible(true)} />,
</Fragment>
<ViewUnknownUserDepartment user={user} useAddDepartments={useAddDepartments} useDisabled={useDisabled} />
</ActionContextProvider>
);
};

View File

@ -0,0 +1,29 @@
import { useAPIClient, useResourceActionContext } from '@tachybase/client';
import { App } from 'antd';
import { useTranslation } from '../../../../../locale';
import { useContextDepartments } from '../../context/Department.context';
export const useBulkRemoveMembersAction = () => {
const { t } = useTranslation();
const { message } = App.useApp();
const API = useAPIClient();
const { state, setState, refresh } = useResourceActionContext();
const { department } = useContextDepartments();
return {
async run() {
const selectedRowKeys = state?.selectedRowKeys;
if (!selectedRowKeys?.length) {
message.warning(t('Please select members'));
return;
}
await API.resource('departments.members', department.id).remove({ values: selectedRowKeys });
setState({ selectedRowKeys: [] });
refresh();
},
};
};

View File

@ -4,7 +4,10 @@ export const useDataSource = (props) => {
const params = {
resource: 'departments',
action: 'list',
params: { appends: ['parent(recursively=true)'], sort: ['createdAt'] },
params: {
appends: ['parent(recursively=true)'],
sort: ['createdAt'],
},
};
const service = useRequest(params, props);
return { ...service, defaultRequest: params };

View File

@ -0,0 +1,22 @@
import { useContext, useEffect } from 'react';
import { useResourceActionContext } from '@tachybase/client';
import { ContextDepartments } from '../../context/Department.context';
export const useMembersDataSource = (props) => {
const { user } = useContext(ContextDepartments);
const ctx = useResourceActionContext();
useEffect(() => {
if (user) {
props?.onSuccess({ data: [user] });
return;
}
if (!ctx.loading) {
props?.onSuccess(ctx.data);
}
}, [user, ctx.loading]);
return ctx;
};

View File

@ -0,0 +1,35 @@
import { useRef } from 'react';
import { useAPIClient, useResourceActionContext } from '@tachybase/client';
import { useContextDepartments } from '../../context/Department.context';
export function usePropsAddMembers(): any {
const { department } = useContextDepartments();
const { refresh } = useResourceActionContext();
const ref = useRef([]);
const api = useAPIClient();
const useAddMembersAction = () => ({
async run() {
const currMembers = ref.current;
if (currMembers?.length) {
await api.resource('departments.members', department.id).add({
values: currMembers,
});
ref.current = [];
refresh();
}
},
});
const handleSelect = (val) => {
ref.current = val;
};
return {
department,
useAddMembersAction,
handleSelect,
};
}

View File

@ -0,0 +1,17 @@
import { useAPIClient, useRecord, useResourceActionContext } from '@tachybase/client';
import { useContextDepartments } from '../../context/Department.context';
export const useRemoveMemberAction = () => {
const API = useAPIClient();
const { department } = useContextDepartments();
const { id } = useRecord();
const { refresh } = useResourceActionContext();
return {
async run() {
await API.resource('departments.members', department.id).remove({ values: [id] });
refresh();
},
};
};

View File

@ -0,0 +1,11 @@
import { useResourceActionContext } from '@tachybase/client';
import { useTranslation } from '../../../../../locale';
export const useShowTotal = () => {
const service = useResourceActionContext();
const { data } = service;
const { t } = useTranslation();
return t('Total {{count}} members', { count: data?.meta?.count });
};

View File

@ -1,31 +1,33 @@
import { useAPIClient } from '@tachybase/client';
import { useDepTree } from './useHooksDe';
import { useDepTree } from './useDeepTree';
export const useDepTree2 = (props?) => {
export const useGetDepTree = (props?) => {
const { resource = 'departments', resourceOf, params = {} } = props || {};
const service = useAPIClient().resource(resource, resourceOf);
const depTree = useDepTree(props);
const { setTreeData, updateTreeData, initData } = depTree;
const loadData =
(_) =>
async ({ key, children }) => {
if (children != null && children.length) return;
const { data } = await service.list({
...params,
pagination: false,
appends: ['parent(recursively=true)'],
filter: { parentId: key },
});
if (data?.data?.length) {
setTreeData(updateTreeData(key, data?.data));
}
};
const loadData = async ({ key, children }) => {
if (children != null && children.length) return;
const { data } = await service.list({
...params,
pagination: false,
appends: ['parent(recursively=true)'],
filter: { parentId: key },
});
if (data?.data?.length) {
setTreeData(updateTreeData(key, data?.data));
}
};
const getByKeyword = async (keyword) => {
const { data } = await service.list({
...params,
pagination: false,
filter: { title: { $includes: keyword } },
filter: {
title: {
$includes: keyword,
},
},
appends: ['parent(recursively=true)'],
pageSize: 100,
});

View File

@ -0,0 +1,17 @@
import { Plugin } from '@tachybase/client';
import { tval } from '../../../locale';
import { DepartmentIndex } from './DepartmentIndex';
export class KitMainTabDepartments extends Plugin {
async load() {
// 用户-部门-角色和权限
this.app.pluginSettingsManager.add('users-permissions.departments', {
icon: 'ApartmentOutlined',
title: tval('Departments'),
sort: 2,
aclSnippet: 'pm.departments',
Component: DepartmentIndex,
});
}
}

View File

@ -2,10 +2,10 @@ import React from 'react';
import { CollectionProvider_deprecated, ResourceActionContext } from '@tachybase/client';
import { collectionDepartments } from '../collections/departments.collection';
import { useDepartments } from '../context/DepartmentsContext';
import { useContextDepartments } from '../context/Department.context';
export const DepartmentsResourceProvider = ({ children }) => {
const context = useDepartments();
export const ProviderDepartmentsResource = ({ children }) => {
const context = useContextDepartments();
const { departmentsResource } = context;
const { service } = departmentsResource || {};

View File

@ -2,10 +2,10 @@ import React, { useContext } from 'react';
import { CollectionProvider_deprecated, ResourceActionContext } from '@tachybase/client';
import { collectionUsers } from '../collections/users.collection';
import { DepartmentsContext } from '../context/DepartmentsContext';
import { ContextDepartments } from '../context/Department.context';
export const UserResourceProvider = ({ children }) => {
const { usersResource } = useContext(DepartmentsContext);
export const ProviderUserResource = ({ children }) => {
const { usersResource } = useContext(ContextDepartments);
const { service } = usersResource || {};
return (

View File

@ -1,4 +0,0 @@
// NOTE: ohters 文件里都是工具函数, 替换后可删除
import { Ft, Mt } from './wt';
export var T = (w, s) => Ft(w, Mt(s));

View File

@ -1,4 +0,0 @@
import { wt } from './wt';
export var de = (w, s, n) =>
s in w ? wt(w, s, { enumerable: true, configurable: true, writable: true, value: n }) : (w[s] = n);

View File

@ -1,19 +0,0 @@
export var k = (w, s, n) =>
new Promise((D, p) => {
var j = (V) => {
try {
N(n.next(V));
} catch (I) {
p(I);
}
},
Y = (V) => {
try {
N(n.throw(V));
} catch (I) {
p(I);
}
},
N = (V) => (V.done ? D(V.value) : Promise.resolve(V.value).then(j, Y));
N((n = n.apply(w, s)).next());
});

View File

@ -1,6 +0,0 @@
export var wt = Object.defineProperty;
export var Ft = Object.defineProperties;
export var Mt = Object.getOwnPropertyDescriptors;
export var me = Object.getOwnPropertySymbols;
export var Tt = Object.prototype.hasOwnProperty;
export var It = Object.prototype.propertyIsEnumerable;

View File

@ -1,8 +0,0 @@
import { de } from './de';
import { It, me, Tt } from './wt';
export var y = (w, s) => {
for (var n in s || (s = {})) Tt.call(s, n) && de(w, n, s[n]);
if (me) for (var n of me(s)) It.call(s, n) && de(w, n, s[n]);
return w;
};

View File

@ -0,0 +1,19 @@
import React from 'react';
import { useRecord } from '@tachybase/client';
export const DepartmentTitle = () => {
const record = useRecord();
const title = getTitle(record);
return <>{title}</>;
};
// utils
function getTitle(record) {
const { title, parent } = record;
if (parent) {
return getTitle(parent) + ' /' + title;
} else {
return title;
}
}

View File

@ -1,12 +1,16 @@
import { uid } from '@tachybase/schema';
export const getSchemaDdt = () => ({
export const getSchemaDepartments = () => ({
type: 'void',
properties: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': { style: { marginBottom: 16 } },
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
[uid()]: {
type: 'void',
@ -14,7 +18,9 @@ export const getSchemaDdt = () => ({
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': { icon: 'FilterOutlined' },
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
actions: {
@ -39,7 +45,10 @@ export const getSchemaDdt = () => ({
type: 'void',
title: '{{t("Add departments")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', icon: 'PlusOutlined' },
'x-component-props': {
type: 'primary',
icon: 'PlusOutlined',
},
properties: {
drawer: {
type: 'void',
@ -63,12 +72,17 @@ export const getSchemaDdt = () => ({
cancel: {
title: '{{t("Cancel")}}',
'x-component': 'Action',
'x-component-props': { useAction: '{{ cm.useCancelAction }}' },
'x-component-props': {
useAction: '{{ cm.useCancelAction }}',
},
},
submit: {
title: '{{t("Submit")}}',
'x-component': 'Action',
'x-component-props': { type: 'primary', useAction: '{{ useAddDepartments }}' },
'x-component-props': {
type: 'primary',
useAction: '{{ useAddDepartments }}',
},
},
},
},
@ -94,7 +108,12 @@ export const getSchemaDdt = () => ({
title: '{{t("Department name")}}',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: { title: { type: 'string', 'x-component': 'DepartmentTitle' } },
properties: {
title: {
type: 'string',
'x-component': 'DepartmentTitle',
},
},
},
actions: {
type: 'void',
@ -104,7 +123,9 @@ export const getSchemaDdt = () => ({
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': { split: '|' },
'x-component-props': {
split: '|',
},
properties: {
remove: {
type: 'void',

View File

@ -0,0 +1,49 @@
import React, { useContext, useMemo } from 'react';
import { CollectionProvider_deprecated, ResourceActionContext, SchemaComponent, useRequest } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
import { ViewDepartmentTable } from '../common/DepartmentTable.view';
import { useDepartmentFilterActionProps } from '../common/scopes/useDepartmentFilterActionProps';
import { collectionDepartments } from '../main-tab/collections/departments.collection';
import { getSchemaDepartments } from './Departments.schema';
import { DepartmentTitle } from './DepartmentTitle.component';
import { useAddDepartments } from './scopes/useAddDepartments';
import { useBulkRemoveDepartments } from './scopes/useBulkRemoveDepartments';
import { useDataSource } from './scopes/useDataSource';
import { useDisabled } from './scopes/useDisabled';
import { useRemoveDepartment } from './scopes/useRemoveDepartment';
export const Departments = () => {
const { role } = useContext(RolesManagerContext);
const resourceData = useRequest(
{
resource: `roles/${role?.name}/departments`,
action: 'list',
params: { appends: ['parent', 'parent.parent(recursively=true)'] },
},
{ ready: !!role, refreshDeps: [role] },
);
const schema = useMemo(() => getSchemaDepartments(), [role]);
return (
<ResourceActionContext.Provider value={resourceData}>
<CollectionProvider_deprecated collection={collectionDepartments}>
<SchemaComponent
schema={schema}
components={{
DepartmentTable: ViewDepartmentTable,
DepartmentTitle,
}}
scope={{
useFilterActionProps: useDepartmentFilterActionProps,
useRemoveDepartment,
useBulkRemoveDepartments,
useDataSource,
useDisabled,
useAddDepartments,
}}
></SchemaComponent>
</CollectionProvider_deprecated>
</ResourceActionContext.Provider>
);
};

View File

@ -0,0 +1,2 @@
## 简介
1. 用户和权限/角色和权限下的, 部门部分

View File

@ -0,0 +1,15 @@
import { Plugin } from '@tachybase/client';
import PluginACLClient from '@tachybase/plugin-acl/client';
import { tval } from '../../../locale';
import { Departments } from './Departments';
export class KitRoleAuth extends Plugin {
async load() {
// 角色和权限: 部门管理
this.app.pm.get(PluginACLClient).rolesManager.add('departments', {
title: tval('Departments'),
Component: Departments,
});
}
}

View File

@ -12,9 +12,12 @@ export const useAddDepartments = () => {
const { departments: departments } = form.values || {};
return {
async run() {
await api.resource('roles.departments', role.name).add({ values: departments.map((dep) => dep.id) });
const apiResource = api.resource('roles.departments', role.name);
await apiResource.add({ values: departments.map((dep) => dep.id) });
form.reset();
setVisible(false);
refresh();
},
};

View File

@ -0,0 +1,35 @@
import { useContext } from 'react';
import { useAPIClient, useResourceActionContext } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
import { App } from 'antd';
import { useTranslation } from '../../../../locale';
export const useBulkRemoveDepartments = () => {
const API = useAPIClient();
const { t } = useTranslation();
const { message } = App.useApp();
const { state, setState, refresh } = useResourceActionContext();
const { role } = useContext(RolesManagerContext);
return {
async run() {
const selectedRowKeys = state?.selectedRowKeys;
if (!selectedRowKeys?.length) {
message.warning(t('Please select departments'));
return;
}
const apiResource = API.resource(`roles/${role == null ? void 0 : role.name}/departments`);
await apiResource.remove({ values: selectedRowKeys });
setState?.({
selectedRowKeys: [],
});
refresh();
},
};
};

View File

@ -1,10 +1,13 @@
import { useRequest } from '@tachybase/client';
export const useDataSource2 = (props) => {
export const useDataSource = (props) => {
const params = {
resource: 'departments',
action: 'list',
params: { appends: ['roles', 'parent(recursively=true)'], sort: ['createdAt'] },
params: {
appends: ['roles', 'parent(recursively=true)'],
sort: ['createdAt'],
},
};
const service = useRequest(params, props);
return { ...service, defaultRequest: params };

View File

@ -0,0 +1,11 @@
import { useContext } from 'react';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
export const useDisabled = () => {
const { role } = useContext(RolesManagerContext);
return {
disabled: (params) => {
return params?.roles?.some((itemRole) => itemRole.name === role?.name);
},
};
};

View File

@ -0,0 +1,20 @@
import { useContext } from 'react';
import { useAPIClient, useRecord, useResourceActionContext } from '@tachybase/client';
import { RolesManagerContext } from '@tachybase/plugin-acl/client';
export const useRemoveDepartment = () => {
const API = useAPIClient();
const { role } = useContext(RolesManagerContext);
const { data } = useRecord();
const { refresh } = useResourceActionContext();
return {
async run() {
const apiResource = API.resource(`roles/${role == null ? void 0 : role.name}/departments`);
await apiResource.remove({ values: [data.id] });
refresh();
},
};
};

View File

@ -1,322 +0,0 @@
import React from 'react';
import { uid } from '@tachybase/schema';
export const getSchemaHe = (department, user) => {
const schemaNotUser = user
? {}
: {
actions: {
type: 'void',
'x-component': 'ActionBar',
'x-component-props': {
style: {
marginBottom: 16,
},
},
properties: {
[uid()]: {
type: 'void',
title: '{{ t("Filter") }}',
'x-action': 'filter',
'x-component': 'Filter.Action',
'x-use-component-props': 'useFilterActionProps',
'x-component-props': {
icon: 'FilterOutlined',
},
'x-align': 'left',
},
actions: {
type: 'void',
'x-component': 'MemberActions',
},
},
},
};
const tableSchemaDepartMent = department
? {
isOwner: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
'x-component-props': { style: { minWidth: 100 } },
title: '{{t("Owner")}}',
properties: {
isOwner: {
type: 'boolean',
'x-component': 'IsOwnerField',
},
},
},
}
: {};
const schemaActions = department
? {
remove: {
type: 'void',
'x-component': 'RowRemoveAction',
},
}
: {};
return {
type: 'void',
properties: {
...schemaNotUser,
table: {
type: 'void',
'x-component': 'Table.Void',
'x-component-props': {
rowKey: 'id',
rowSelection: {
type: 'checkbox',
},
useDataSource: '{{ useMembersDataSource }}',
pagination: {
showTotal: '{{ useShowTotal }}',
},
},
properties: {
nickname: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
nickname: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
username: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
username: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
departments: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
departments: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true },
},
},
...tableSchemaDepartMent,
phone: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
phone: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
email: {
type: 'void',
'x-decorator': 'Table.Column.Decorator',
'x-component': 'Table.Column',
properties: {
email: {
type: 'string',
'x-component': 'CollectionField',
'x-read-pretty': true,
},
},
},
actions: {
type: 'void',
title: '{{t("Actions")}}',
'x-component': 'Table.Column',
properties: {
actions: {
type: 'void',
'x-component': 'Space',
'x-component-props': { split: '|' },
properties: {
update: {
type: 'void',
title: '{{t("Configure")}}',
'x-component': 'Action.Link',
'x-component-props': { type: 'primary' },
properties: {
drawer: {
type: 'void',
'x-component': 'Action.Drawer',
'x-decorator': 'FormV2',
title: '{{t("Configure")}}',
properties: {
departments: {
title: '{{t("Departments")}}',
'x-decorator': 'FormItem',
'x-component': 'UserDepartmentsField',
},
},
},
},
},
...schemaActions,
},
},
},
},
},
},
},
};
// return {
// type: 'void',
// properties: T(
// y(
// {},
// user
// ? {}
// : {
// actions: {
// type: 'void',
// 'x-component': 'ActionBar',
// 'x-component-props': {
// style: {
// marginBottom: 16,
// },
// },
// properties: {
// [uid()]: {
// type: 'void',
// title: '{{ t("Filter") }}',
// 'x-action': 'filter',
// 'x-component': 'Filter.Action',
// 'x-use-component-props': 'useFilterActionProps',
// 'x-component-props': {
// icon: 'FilterOutlined',
// },
// 'x-align': 'left',
// },
// actions: {
// type: 'void',
// 'x-component': 'MemberActions',
// },
// },
// },
// },
// ),
// {
// table: {
// type: 'void',
// 'x-component': 'Table.Void',
// 'x-component-props': {
// rowKey: 'id',
// rowSelection: { type: 'checkbox' },
// useDataSource: '{{ useMembersDataSource }}',
// pagination: { showTotal: '{{ useShowTotal }}' },
// },
// properties: T(
// y(
// {
// nickname: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// properties: { nickname: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
// },
// username: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// properties: { username: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
// },
// departments: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// properties: {
// departments: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true },
// },
// },
// },
// department
// ? {
// isOwner: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// 'x-component-props': { style: { minWidth: 100 } },
// title: '{{t("Owner")}}',
// properties: { isOwner: { type: 'boolean', 'x-component': 'IsOwnerField' } },
// },
// }
// : {},
// ),
// {
// phone: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// properties: { phone: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
// },
// email: {
// type: 'void',
// 'x-decorator': 'Table.Column.Decorator',
// 'x-component': 'Table.Column',
// properties: { email: { type: 'string', 'x-component': 'CollectionField', 'x-read-pretty': true } },
// },
// actions: {
// type: 'void',
// title: '{{t("Actions")}}',
// 'x-component': 'Table.Column',
// properties: {
// actions: {
// type: 'void',
// 'x-component': 'Space',
// 'x-component-props': { split: '|' },
// properties: y(
// {
// update: {
// type: 'void',
// title: '{{t("Configure")}}',
// 'x-component': 'Action.Link',
// 'x-component-props': { type: 'primary' },
// properties: {
// drawer: {
// type: 'void',
// 'x-component': 'Action.Drawer',
// 'x-decorator': 'FormV2',
// title: '{{t("Configure")}}',
// properties: {
// departments: {
// title: '{{t("Departments")}}',
// 'x-decorator': 'FormItem',
// 'x-component': 'UserDepartmentsField',
// },
// },
// },
// },
// },
// },
// department ? { remove: { type: 'void', 'x-component': 'RowRemoveAction' } } : {},
// ),
// },
// },
// },
// },
// ),
// },
// },
// ),
// // properties: {
// // }
// };
};

View File

@ -1,19 +0,0 @@
import { Ee, Fe, L } from './tools';
import { Ve } from './Ve';
export function B(e, t, o) {
(o = o || {}),
(o.arrayMerge = o.arrayMerge || Ee),
(o.isMergeableObject = o.isMergeableObject || Fe),
(o.cloneUnlessOtherwiseSpecified = L);
var a = Array.isArray(t),
r = Array.isArray(e),
c = a === r;
return c ? (a ? o.arrayMerge(e, t, o) : Ve(e, t, o)) : L(t, o);
}
B.all = function (t, o) {
if (!Array.isArray(t)) throw new Error('first argument should be an array');
return t.reduce(function (a, r) {
return B(a, r, o);
}, {});
};

View File

@ -1,15 +0,0 @@
import { ae, ce, Ke, L, qe } from './tools';
export function Ve(e, t, o) {
var a = {};
return (
o.isMergeableObject(e) &&
ae(e).forEach(function (r) {
a[r] = L(e[r], o);
}),
ae(t).forEach(function (r) {
qe(e, r) || (ce(e, r) && o.isMergeableObject(t[r]) ? (a[r] = Ke(r, o)(e[r], t[r], o)) : (a[r] = L(t[r], o)));
}),
a
);
}

Some files were not shown because too many files have changed in this diff Show More