189 lines
5.1 KiB
TypeScript
189 lines
5.1 KiB
TypeScript
import {
|
|
ActionContextProvider,
|
|
RecordProvider,
|
|
SchemaComponent,
|
|
useAPIClient,
|
|
useResourceActionContext,
|
|
} from '@nocobase/client';
|
|
import { Menu, Empty, Dropdown, App, Tag, Row, Col, Spin } from 'antd';
|
|
import { TagOutlined, MoreOutlined } from '@ant-design/icons';
|
|
import React, { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
|
|
import { useACLTranslation } from './locale';
|
|
import { Schema } from '@formily/react';
|
|
import { RolesManagerContext } from './RolesManagerProvider';
|
|
import { roleEditSchema } from './schemas/roles';
|
|
import { useLoadMoreObserver } from './hooks/load-more-observer';
|
|
|
|
export const RolesMenu: React.FC & {
|
|
Item: React.FC<{ item: any; onEdit: () => void }>;
|
|
} = () => {
|
|
const { t } = useACLTranslation();
|
|
const { data, mutate } = useResourceActionContext();
|
|
const [visible, setVisible] = useState(false);
|
|
const [record, setRecord] = useState(null);
|
|
const { role, setRole } = useContext(RolesManagerContext);
|
|
const [roles, setRoles] = useState([]);
|
|
const [loading, setLoading] = useState(false);
|
|
const api = useAPIClient();
|
|
|
|
const loadMore = useCallback(async () => {
|
|
const meta = data?.meta;
|
|
if (!meta || meta.page >= meta.totalPage) {
|
|
return;
|
|
}
|
|
setLoading(true);
|
|
const res = await api.resource('roles').list({
|
|
page: meta.page + 1,
|
|
pageSize: meta.pageSize,
|
|
filter: {
|
|
'name.$ne': 'root',
|
|
},
|
|
showAnonymous: true,
|
|
sort: ['createdAt'],
|
|
appends: [],
|
|
});
|
|
mutate(res?.data || {});
|
|
setLoading(false);
|
|
}, [data]);
|
|
const { lastItem, setLastItem } = useLoadMoreObserver({ loadMore });
|
|
|
|
const handleSelect = ({ key }) => {
|
|
setRole(roles.find((item: any) => item.name === key));
|
|
};
|
|
|
|
useEffect(() => {
|
|
if (!roles[0]) {
|
|
return;
|
|
}
|
|
setRole(roles[0]);
|
|
}, [roles, setRole]);
|
|
|
|
useEffect(() => {
|
|
if (!data?.data?.length) {
|
|
return;
|
|
}
|
|
|
|
const ref = React.createRef<any>();
|
|
setLastItem(ref);
|
|
|
|
if (data.meta?.page > 1) {
|
|
setRoles((prev) => prev.concat(data.data));
|
|
} else {
|
|
setRoles(data.data);
|
|
}
|
|
}, [data, setLastItem]);
|
|
|
|
const items = useMemo(
|
|
() =>
|
|
roles.map((item: any, index: number) => ({
|
|
key: item.name,
|
|
label:
|
|
index === roles.length - 1 ? (
|
|
<div ref={lastItem}>
|
|
<RolesMenu.Item
|
|
item={item}
|
|
onEdit={() => {
|
|
setVisible(true);
|
|
setRecord(item);
|
|
}}
|
|
/>
|
|
</div>
|
|
) : (
|
|
<RolesMenu.Item
|
|
item={item}
|
|
onEdit={() => {
|
|
setVisible(true);
|
|
setRecord(item);
|
|
}}
|
|
/>
|
|
),
|
|
})),
|
|
[roles, lastItem],
|
|
);
|
|
|
|
return (
|
|
<>
|
|
{roles.length ? (
|
|
<Menu
|
|
style={{ border: 'none', maxHeight: '65vh', overflowY: 'auto' }}
|
|
items={[...items, ...(loading ? [{ key: 'loading', label: <Spin /> }] : [])]}
|
|
selectedKeys={[role?.name]}
|
|
onSelect={handleSelect}
|
|
/>
|
|
) : (
|
|
<Empty image={Empty.PRESENTED_IMAGE_SIMPLE} />
|
|
)}
|
|
<ActionContextProvider value={{ visible, setVisible }}>
|
|
<RecordProvider record={record} collectionName="departments">
|
|
<SchemaComponent scope={{ t }} schema={roleEditSchema} />
|
|
</RecordProvider>
|
|
</ActionContextProvider>
|
|
</>
|
|
);
|
|
};
|
|
|
|
RolesMenu.Item = function DepartmentTreeItem({ item, onEdit }) {
|
|
const { t } = useACLTranslation();
|
|
const { refreshAsync } = useResourceActionContext();
|
|
const { modal, message } = App.useApp();
|
|
const api = useAPIClient();
|
|
const deleteDepartment = () => {
|
|
modal.confirm({
|
|
title: t('Delete'),
|
|
content: t('Are you sure you want to delete it?'),
|
|
onOk: async () => {
|
|
await api.resource('roles').destroy({ filterByTk: item.name });
|
|
message.success(t('Deleted successfully'));
|
|
await refreshAsync();
|
|
},
|
|
});
|
|
};
|
|
const handleClick = ({ key, domEvent }) => {
|
|
domEvent.stopPropagation();
|
|
switch (key) {
|
|
case 'edit':
|
|
onEdit();
|
|
break;
|
|
case 'delete':
|
|
deleteDepartment();
|
|
}
|
|
};
|
|
const title = Schema.compile(item.title, { t });
|
|
return (
|
|
<Row>
|
|
<Col flex={3} style={{ display: 'inline-flex', alignItems: 'center' }}>
|
|
<span style={{ whiteSpace: 'nowrap', width: '120px', overflow: 'hidden', textOverflow: 'ellipsis' }}>
|
|
<TagOutlined />
|
|
<span style={{ marginLeft: '10px' }} title={title}>
|
|
{title}
|
|
</span>
|
|
</span>
|
|
</Col>
|
|
<Col>
|
|
{item.default ? (
|
|
<Tag color="success" bordered={false}>
|
|
{t('Default')}
|
|
</Tag>
|
|
) : null}
|
|
<Dropdown
|
|
menu={{
|
|
items: [
|
|
{
|
|
label: t('Edit'),
|
|
key: 'edit',
|
|
},
|
|
{
|
|
label: t('Delete'),
|
|
key: 'delete',
|
|
},
|
|
],
|
|
onClick: handleClick,
|
|
}}
|
|
>
|
|
<MoreOutlined />
|
|
</Dropdown>
|
|
</Col>
|
|
</Row>
|
|
);
|
|
};
|