refactor(department): dev (#1492)
Reviewed-on: daoyoucloud/tachybase#1492
This commit is contained in:
parent
a654933ab2
commit
bf1f6349f1
@ -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 }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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);
|
||||
}
|
@ -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),
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
@ -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;
|
||||
};
|
@ -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);
|
@ -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,
|
||||
};
|
||||
}
|
@ -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>
|
||||
);
|
||||
};
|
@ -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, {}),
|
||||
}),
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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',
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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 },
|
||||
}),
|
||||
],
|
||||
});
|
||||
};
|
@ -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 }} />;
|
||||
};
|
@ -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 }}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}}
|
||||
/>
|
||||
);
|
@ -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)}</>;
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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),
|
||||
},
|
||||
});
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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,
|
||||
}}
|
||||
/>,
|
||||
],
|
||||
});
|
||||
};
|
@ -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>>({});
|
@ -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();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -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();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -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));
|
||||
},
|
||||
};
|
||||
};
|
@ -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
|
||||
);
|
||||
};
|
@ -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();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -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();
|
||||
});
|
||||
},
|
||||
};
|
||||
};
|
@ -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 });
|
||||
};
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -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>;
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
@ -0,0 +1,12 @@
|
||||
import { uid } from '@tachybase/schema';
|
||||
|
||||
export const schemaDepartmentManagement = {
|
||||
type: 'void',
|
||||
properties: {
|
||||
[uid()]: {
|
||||
type: 'void',
|
||||
'x-decorator': 'CardItem',
|
||||
'x-component': 'DepartmentManagement',
|
||||
},
|
||||
},
|
||||
};
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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} />;
|
||||
};
|
@ -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}
|
@ -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 }} />;
|
||||
};
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
@ -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 }}' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -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>;
|
||||
};
|
@ -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 }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
@ -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 }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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 }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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 {
|
@ -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();
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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>
|
@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
};
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
@ -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} />;
|
||||
};
|
@ -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',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
@ -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;
|
||||
};
|
@ -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 }}',
|
||||
},
|
||||
},
|
@ -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;
|
||||
};
|
@ -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 }}',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
@ -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,
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
@ -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>
|
||||
);
|
||||
};
|
@ -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();
|
||||
},
|
||||
};
|
||||
};
|
@ -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 };
|
@ -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;
|
||||
};
|
@ -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,
|
||||
};
|
||||
}
|
@ -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();
|
||||
},
|
||||
};
|
||||
};
|
@ -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 });
|
||||
};
|
@ -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,
|
||||
});
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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 || {};
|
||||
|
@ -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 (
|
@ -1,4 +0,0 @@
|
||||
// NOTE: ohters 文件里都是工具函数, 替换后可删除
|
||||
import { Ft, Mt } from './wt';
|
||||
|
||||
export var T = (w, s) => Ft(w, Mt(s));
|
@ -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);
|
@ -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());
|
||||
});
|
@ -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;
|
@ -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;
|
||||
};
|
@ -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;
|
||||
}
|
||||
}
|
@ -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',
|
@ -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>
|
||||
);
|
||||
};
|
@ -0,0 +1,2 @@
|
||||
## 简介
|
||||
1. 用户和权限/角色和权限下的, 部门部分
|
@ -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,
|
||||
});
|
||||
}
|
||||
}
|
@ -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();
|
||||
},
|
||||
};
|
@ -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();
|
||||
},
|
||||
};
|
||||
};
|
@ -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 };
|
@ -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);
|
||||
},
|
||||
};
|
||||
};
|
@ -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();
|
||||
},
|
||||
};
|
||||
};
|
@ -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: {
|
||||
|
||||
// // }
|
||||
// };
|
||||
};
|
@ -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);
|
||||
}, {});
|
||||
};
|
@ -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
Loading…
Reference in New Issue
Block a user