From 6691122c8f9152ae7424e10eee04fd0d1567cb6b Mon Sep 17 00:00:00 2001 From: Tran Anh Tuan Date: Fri, 23 Jan 2026 11:29:35 +0700 Subject: [PATCH] feat(manager/users/share): Enhance device sharing functionality and update related APIs and UI components --- src/components/shared/ThingFilterModal.tsx | 21 +- src/constants/api.ts | 3 +- src/locales/en-US/master/master-user-en.ts | 17 +- src/locales/vi-VN/master/master-group-vi.ts | 2 +- src/locales/vi-VN/master/master-user-vi.ts | 17 +- .../Manager/Log/components/LogActions.tsx | 303 +++++++++++++ src/pages/Manager/Log/index.tsx | 310 +------------ .../User/Permission/components/ShareThing.tsx | 414 +++++++++++++++--- src/pages/Manager/User/index.tsx | 2 +- src/services/master/ThingController.ts | 46 +- src/services/master/typings.d.ts | 27 +- 11 files changed, 782 insertions(+), 380 deletions(-) create mode 100644 src/pages/Manager/Log/components/LogActions.tsx diff --git a/src/components/shared/ThingFilterModal.tsx b/src/components/shared/ThingFilterModal.tsx index b81a631..daee89b 100644 --- a/src/components/shared/ThingFilterModal.tsx +++ b/src/components/shared/ThingFilterModal.tsx @@ -13,17 +13,23 @@ import TreeGroup from './TreeGroup'; const { Paragraph } = Typography; type ThingsFilterProps = { + title?: string; isOpen?: boolean; setIsOpen: React.Dispatch>; thingIds?: string | string[] | null; + extra?: React.ReactNode; onSubmit?: (thingIds: string[]) => void; + disabled?: boolean; }; const ThingsFilter = ({ + title, isOpen, setIsOpen, thingIds, + extra, onSubmit, + disabled = false, }: ThingsFilterProps) => { const intl = useIntl(); const { useBreakpoint } = Grid; @@ -93,6 +99,7 @@ const ThingsFilter = ({ ]; return ( } > - + setSelectedRowKeys(keys), + getCheckboxProps: (record) => ({ + disabled: + disabled && thingIds && record.id + ? (Array.isArray(thingIds) + ? thingIds + : [thingIds] + ).includes(record.id) + : false, + }), }} pagination={{ size: 'small', diff --git a/src/constants/api.ts b/src/constants/api.ts index 33ffafa..6d573a6 100644 --- a/src/constants/api.ts +++ b/src/constants/api.ts @@ -8,7 +8,8 @@ export const API_ALARMS_CONFIRM = '/api/alarms/confirm'; // Thing API Constants export const API_THINGS_SEARCH = '/api/things/search'; -export const API_THINGS_POLICY = '/api/things/policy'; +export const API_THING_POLICY = '/api/things/policy2'; +export const API_SHARE_THING = '/api/things'; // Group API Constants export const API_GROUPS = '/api/groups'; diff --git a/src/locales/en-US/master/master-user-en.ts b/src/locales/en-US/master/master-user-en.ts index ea81138..a767047 100644 --- a/src/locales/en-US/master/master-user-en.ts +++ b/src/locales/en-US/master/master-user-en.ts @@ -48,7 +48,22 @@ export default { 'master.users.unassign.fail': 'Unassign failed', 'master.users.assign.success': 'Assign group successful', 'master.users.assign.fail': 'Assign group failed', - 'master.users.deletion.title': 'Are you sure to delete this selected items?', + 'master.users.delete.title': 'Are you sure to delete this selected items?', 'master.users.delete.success': 'User deleted successfully', 'master.users.delete.fail': 'User deletion failed', + 'master.users.things.list': 'List of devices', + 'master.users.things.relations.text': 'Relations', + 'master.users.things.relation.write': 'Control', + 'master.users.things.relation.read': 'View', + 'master.users.things.relation.delete': 'Config', + 'master.users.thing.unshare.confirm': + 'Are you sure you want to stop sharing these devices?', + 'master.users.thing.unshare.title': 'Stop Sharing', + 'master.users.things.unsharing': 'Unsharing devices...', + 'master.users.thing.unshare.success': 'Stop sharing successful', + 'master.users.thing.unshare.fail': 'Stop sharing failed', + 'master.users.thing.share.title': 'Share things', + 'master.users.things.sharing': 'Sharing devices...', + 'master.users.thing.share.success': 'Device sharing successful', + 'master.users.thing.share.fail': 'Device sharing failed', }; diff --git a/src/locales/vi-VN/master/master-group-vi.ts b/src/locales/vi-VN/master/master-group-vi.ts index ebf0453..29a5869 100644 --- a/src/locales/vi-VN/master/master-group-vi.ts +++ b/src/locales/vi-VN/master/master-group-vi.ts @@ -4,7 +4,7 @@ export default { 'Không thể tạo đơn vị con khi gốc đã có thiết bị', 'master.groups.add': 'Tạo đơn vị cấp dưới', 'master.groups.delete.confirm': 'Bạn có chắc muốn xóa nhóm này không?', - 'master.groups.code': 'Mã đỡ vị', + 'master.groups.code': 'Mã đơn vị', 'master.groups.code.exists': 'Mã đã tồn tại', 'master.groups.short_name': 'Tên viết tắt', 'master.groups.short_name.exists': 'Tên viết tắt đã tồn tại', diff --git a/src/locales/vi-VN/master/master-user-vi.ts b/src/locales/vi-VN/master/master-user-vi.ts index 0ed2ae4..868a78e 100644 --- a/src/locales/vi-VN/master/master-user-vi.ts +++ b/src/locales/vi-VN/master/master-user-vi.ts @@ -47,7 +47,22 @@ export default { 'master.users.unassign.fail': 'Ngừng phân quyền thất bại', 'master.users.assign.success': 'Phân quyền đơn vị thành công', 'master.users.assign.fail': 'Phân quyền đơn vị thất bại', - 'master.users.deletion.title': 'Chắc chắn xoá các tài khoản đã chọn?', + 'master.users.delete.title': 'Chắc chắn xoá các tài khoản đã chọn?', 'master.users.delete.success': 'Xoá người dùng thành công', 'master.users.delete.fail': 'Xoá người dùng thất bại', + 'master.users.things.list': 'Danh sách thiết bị', + 'master.users.things.relations.text': 'Hành động', + 'master.users.things.relation.write': 'Điều khiển', + 'master.users.things.relation.read': 'Giám sát', + 'master.users.things.relation.delete': 'Quản lí', + 'master.users.thing.unshare.confirm': + 'Chắc chắn muốn ngừng chia sẻ các thiết bị này?', + 'master.users.thing.unshare.title': 'Ngừng chia sẻ', + 'master.users.thing.unshare.success': 'Ngừng chia sẻ thành công', + 'master.users.thing.unshare.fail': 'Ngừng chia sẻ thất bại', + 'master.users.things.unsharing': 'Đang ngừng chia sẻ thiết bị...', + 'master.users.thing.share.title': 'Chia sẻ thiết bị', + 'master.users.things.sharing': 'Đang chia sẻ thiết bị...', + 'master.users.thing.share.success': 'Chia sẻ thiết bị thành công', + 'master.users.thing.share.fail': 'Chia sẻ thiết bị thất bại', }; diff --git a/src/pages/Manager/Log/components/LogActions.tsx b/src/pages/Manager/Log/components/LogActions.tsx new file mode 100644 index 0000000..aad4a4c --- /dev/null +++ b/src/pages/Manager/Log/components/LogActions.tsx @@ -0,0 +1,303 @@ +const LogActions = (intl: any) => { + return [ + //Alarm + { + title: intl.formatMessage({ + id: 'master.logs.things.alarm.confirm', + defaultMessage: 'Alarm confirm', + }), + value: '0-0', + selectable: false, + children: [ + { + value: 'things.alarm_confirm', + title: intl.formatMessage({ + id: 'master.logs.things.confirm', + defaultMessage: 'Confirm', + }), + }, + { + value: 'things.alarm_unconfirm', + title: intl.formatMessage({ + id: 'master.logs.things.unconfirm', + defaultMessage: 'Unconfirm', + }), + }, + ], + }, + //Things + { + title: intl.formatMessage({ + id: 'master.logs.things', + defaultMessage: 'Things', + }), + value: '0-1', + selectable: false, + children: [ + { + value: 'things.create', + title: intl.formatMessage({ + id: 'master.logs.things.create', + defaultMessage: 'Create new thing', + }), + }, + { + value: 'things.update', + title: intl.formatMessage({ + id: 'master.logs.things.update', + defaultMessage: 'Update thing', + }), + }, + { + value: 'things.remove', + title: intl.formatMessage({ + id: 'master.logs.things.remove', + defaultMessage: 'Remove thing', + }), + }, + { + value: 'things.share', + title: intl.formatMessage({ + id: 'master.logs.things.share', + defaultMessage: 'Share thing', + }), + }, + { + value: 'things.unshare', + title: intl.formatMessage({ + id: 'master.logs.things.unshare', + defaultMessage: 'Unshare thing', + }), + }, + { + value: 'things.update_key', + title: intl.formatMessage({ + id: 'master.logs.things.update_key', + defaultMessage: 'Update key thing', + }), + }, + ], + }, + // Users + { + title: intl.formatMessage({ + id: 'master.logs.users', + defaultMessage: 'Users', + }), + value: '0-2', + selectable: false, + children: [ + { + value: 'users.create', + title: intl.formatMessage({ + id: 'master.logs.users.create', + defaultMessage: 'Register user', + }), + }, + { + value: 'users.update', + title: intl.formatMessage({ + id: 'master.logs.users.update', + defaultMessage: 'Update user', + }), + }, + { + value: 'users.remove', + title: intl.formatMessage({ + id: 'master.logs.users.remove', + defaultMessage: 'Remove user', + }), + }, + { + value: 'users.login', + title: intl.formatMessage({ + id: 'master.logs.users.login', + defaultMessage: 'User login', + }), + }, + ], + }, + // Groups + { + title: intl.formatMessage({ + id: 'master.logs.groups', + defaultMessage: 'Groups', + }), + value: '0-3', + selectable: false, + children: [ + { + value: 'group.create', + title: intl.formatMessage({ + id: 'master.logs.groups.create', + defaultMessage: 'Create new group', + }), + }, + { + value: 'group.update', + title: intl.formatMessage({ + id: 'master.logs.groups.update', + defaultMessage: 'Update group', + }), + }, + { + value: 'group.remove', + title: intl.formatMessage({ + id: 'master.logs.groups.remove', + defaultMessage: 'Remove group', + }), + }, + { + value: 'group.assign_thing', + title: intl.formatMessage({ + id: 'master.logs.groups.assign_thing', + defaultMessage: 'Assign thing to group', + }), + }, + { + value: 'group.assign_user', + title: intl.formatMessage({ + id: 'master.logs.groups.assign_user', + defaultMessage: 'Assign user to group', + }), + }, + { + value: 'group.unassign_thing', + title: intl.formatMessage({ + id: 'master.logs.groups.unassign_thing', + defaultMessage: 'Remove thing from group', + }), + }, + { + value: 'group.unassign_user', + title: intl.formatMessage({ + id: 'master.logs.groups.unassign_user', + defaultMessage: 'Remove user from group', + }), + }, + ], + }, + // Ships + { + title: intl.formatMessage({ + id: 'master.logs.ships', + defaultMessage: 'Ships', + }), + value: '0-4', + selectable: false, + children: [ + { + value: 'ships.create', + title: intl.formatMessage({ + id: 'master.logs.ships.create', + defaultMessage: 'Create new ship', + }), + }, + { + value: 'ships.update', + title: intl.formatMessage({ + id: 'master.logs.ships.update', + defaultMessage: 'Update ship', + }), + }, + { + value: 'ships.remove', + title: intl.formatMessage({ + id: 'master.logs.ships.remove', + defaultMessage: 'Remove ship', + }), + }, + { + value: 'ships.assign_thing', + title: intl.formatMessage({ + id: 'master.logs.ships.assign_thing', + defaultMessage: 'Assign thing to ship', + }), + }, + { + value: 'ships.assign_user', + title: intl.formatMessage({ + id: 'master.logs.ships.assign_user', + defaultMessage: 'Assign user to ship', + }), + }, + { + value: 'ships.unassign_thing', + title: intl.formatMessage({ + id: 'master.logs.ships.unassign_thing', + defaultMessage: 'Remove thing from ship', + }), + }, + { + value: 'ships.unassign_user', + title: intl.formatMessage({ + id: 'master.logs.ships.unassign_user', + defaultMessage: 'Remove user from ship', + }), + }, + ], + }, + // Trips + { + title: intl.formatMessage({ + id: 'master.logs.trips', + defaultMessage: 'Trips', + }), + value: '0-5', + selectable: false, + children: [ + { + value: 'trips.create', + title: intl.formatMessage({ + id: 'master.logs.trips.create', + defaultMessage: 'Create new ship', + }), + }, + { + value: 'trips.update', + title: intl.formatMessage({ + id: 'master.logs.trips.update', + defaultMessage: 'Update ship', + }), + }, + { + value: 'trips.remove', + title: intl.formatMessage({ + id: 'master.logs.trips.remove', + defaultMessage: 'Remove ship', + }), + }, + { + value: 'trips.approve', + title: intl.formatMessage({ + id: 'master.logs.trips.approve', + defaultMessage: 'Approve trip', + }), + }, + { + value: 'trips.request_approve', + title: intl.formatMessage({ + id: 'master.logs.trips.request_approve', + defaultMessage: 'Request approval for trip', + }), + }, + { + value: 'trips.unassign_thing', + title: intl.formatMessage({ + id: 'master.logs.trips.unassign_thing', + defaultMessage: 'Remove thing from ship', + }), + }, + { + value: 'trips.unassign_user', + title: intl.formatMessage({ + id: 'master.logs.trips.unassign_user', + defaultMessage: 'Remove user from ship', + }), + }, + ], + }, + ]; +}; + +export default LogActions; diff --git a/src/pages/Manager/Log/index.tsx b/src/pages/Manager/Log/index.tsx index 4873a49..2f6495c 100644 --- a/src/pages/Manager/Log/index.tsx +++ b/src/pages/Manager/Log/index.tsx @@ -3,312 +3,16 @@ import { apiQueryLogs } from '@/services/master/LogController'; import { apiQueryUsers } from '@/services/master/UserController'; import { ActionType, ProColumns, ProTable } from '@ant-design/pro-components'; import { useIntl } from '@umijs/max'; +import { theme } from 'antd'; import { DatePicker } from 'antd/lib'; import dayjs from 'dayjs'; import { useRef } from 'react'; +import LogActions from './components/LogActions'; const SystemLogs = () => { const intl = useIntl(); const tableRef = useRef(); - const actions = [ - //Alarm - { - title: intl.formatMessage({ - id: 'master.logs.things.alarm.confirm', - defaultMessage: 'Alarm confirm', - }), - value: '0-0', - selectable: false, - children: [ - { - value: 'things.alarm_confirm', - title: intl.formatMessage({ - id: 'master.logs.things.confirm', - defaultMessage: 'Confirm', - }), - }, - { - value: 'things.alarm_unconfirm', - title: intl.formatMessage({ - id: 'master.logs.things.unconfirm', - defaultMessage: 'Unconfirm', - }), - }, - ], - }, - //Things - { - title: intl.formatMessage({ - id: 'master.logs.things', - defaultMessage: 'Things', - }), - value: '0-1', - selectable: false, - children: [ - { - value: 'things.create', - title: intl.formatMessage({ - id: 'master.logs.things.create', - defaultMessage: 'Create new thing', - }), - }, - { - value: 'things.update', - title: intl.formatMessage({ - id: 'master.logs.things.update', - defaultMessage: 'Update thing', - }), - }, - { - value: 'things.remove', - title: intl.formatMessage({ - id: 'master.logs.things.remove', - defaultMessage: 'Remove thing', - }), - }, - { - value: 'things.share', - title: intl.formatMessage({ - id: 'master.logs.things.share', - defaultMessage: 'Share thing', - }), - }, - { - value: 'things.unshare', - title: intl.formatMessage({ - id: 'master.logs.things.unshare', - defaultMessage: 'Unshare thing', - }), - }, - { - value: 'things.update_key', - title: intl.formatMessage({ - id: 'master.logs.things.update_key', - defaultMessage: 'Update key thing', - }), - }, - ], - }, - // Users - { - title: intl.formatMessage({ - id: 'master.logs.users', - defaultMessage: 'Users', - }), - value: '0-2', - selectable: false, - children: [ - { - value: 'users.create', - title: intl.formatMessage({ - id: 'master.logs.users.create', - defaultMessage: 'Register user', - }), - }, - { - value: 'users.update', - title: intl.formatMessage({ - id: 'master.logs.users.update', - defaultMessage: 'Update user', - }), - }, - { - value: 'users.remove', - title: intl.formatMessage({ - id: 'master.logs.users.remove', - defaultMessage: 'Remove user', - }), - }, - { - value: 'users.login', - title: intl.formatMessage({ - id: 'master.logs.users.login', - defaultMessage: 'User login', - }), - }, - ], - }, - // Groups - { - title: intl.formatMessage({ - id: 'master.logs.groups', - defaultMessage: 'Groups', - }), - value: '0-3', - selectable: false, - children: [ - { - value: 'group.create', - title: intl.formatMessage({ - id: 'master.logs.groups.create', - defaultMessage: 'Create new group', - }), - }, - { - value: 'group.update', - title: intl.formatMessage({ - id: 'master.logs.groups.update', - defaultMessage: 'Update group', - }), - }, - { - value: 'group.remove', - title: intl.formatMessage({ - id: 'master.logs.groups.remove', - defaultMessage: 'Remove group', - }), - }, - { - value: 'group.assign_thing', - title: intl.formatMessage({ - id: 'master.logs.groups.assign_thing', - defaultMessage: 'Assign thing to group', - }), - }, - { - value: 'group.assign_user', - title: intl.formatMessage({ - id: 'master.logs.groups.assign_user', - defaultMessage: 'Assign user to group', - }), - }, - { - value: 'group.unassign_thing', - title: intl.formatMessage({ - id: 'master.logs.groups.unassign_thing', - defaultMessage: 'Remove thing from group', - }), - }, - { - value: 'group.unassign_user', - title: intl.formatMessage({ - id: 'master.logs.groups.unassign_user', - defaultMessage: 'Remove user from group', - }), - }, - ], - }, - // Ships - { - title: intl.formatMessage({ - id: 'master.logs.ships', - defaultMessage: 'Ships', - }), - value: '0-4', - selectable: false, - children: [ - { - value: 'ships.create', - title: intl.formatMessage({ - id: 'master.logs.ships.create', - defaultMessage: 'Create new ship', - }), - }, - { - value: 'ships.update', - title: intl.formatMessage({ - id: 'master.logs.ships.update', - defaultMessage: 'Update ship', - }), - }, - { - value: 'ships.remove', - title: intl.formatMessage({ - id: 'master.logs.ships.remove', - defaultMessage: 'Remove ship', - }), - }, - { - value: 'ships.assign_thing', - title: intl.formatMessage({ - id: 'master.logs.ships.assign_thing', - defaultMessage: 'Assign thing to ship', - }), - }, - { - value: 'ships.assign_user', - title: intl.formatMessage({ - id: 'master.logs.ships.assign_user', - defaultMessage: 'Assign user to ship', - }), - }, - { - value: 'ships.unassign_thing', - title: intl.formatMessage({ - id: 'master.logs.ships.unassign_thing', - defaultMessage: 'Remove thing from ship', - }), - }, - { - value: 'ships.unassign_user', - title: intl.formatMessage({ - id: 'master.logs.ships.unassign_user', - defaultMessage: 'Remove user from ship', - }), - }, - ], - }, - // Trips - { - title: intl.formatMessage({ - id: 'master.logs.trips', - defaultMessage: 'Trips', - }), - value: '0-5', - selectable: false, - children: [ - { - value: 'trips.create', - title: intl.formatMessage({ - id: 'master.logs.trips.create', - defaultMessage: 'Create new ship', - }), - }, - { - value: 'trips.update', - title: intl.formatMessage({ - id: 'master.logs.trips.update', - defaultMessage: 'Update ship', - }), - }, - { - value: 'trips.remove', - title: intl.formatMessage({ - id: 'master.logs.trips.remove', - defaultMessage: 'Remove ship', - }), - }, - { - value: 'trips.approve', - title: intl.formatMessage({ - id: 'master.logs.trips.approve', - defaultMessage: 'Approve trip', - }), - }, - { - value: 'trips.request_approve', - title: intl.formatMessage({ - id: 'master.logs.trips.request_approve', - defaultMessage: 'Request approval for trip', - }), - }, - { - value: 'trips.unassign_thing', - title: intl.formatMessage({ - id: 'master.logs.trips.unassign_thing', - defaultMessage: 'Remove thing from ship', - }), - }, - { - value: 'trips.unassign_user', - title: intl.formatMessage({ - id: 'master.logs.trips.unassign_user', - defaultMessage: 'Remove user from ship', - }), - }, - ], - }, - ]; + const { token } = theme.useToken(); const queryUserSource = async (): Promise => { try { @@ -345,6 +49,10 @@ const SystemLogs = () => { return ( { treeCheckable: true, multiple: true, }, - request: async () => actions, + request: async () => LogActions(intl), render: (_, item) => { - const childs = actions.flatMap((a) => a.children || []); + const childs = LogActions(intl).flatMap((a) => a.children || []); const action = childs.find((a) => a?.value === item.subtopic); return action?.title ?? '...'; }, diff --git a/src/pages/Manager/User/Permission/components/ShareThing.tsx b/src/pages/Manager/User/Permission/components/ShareThing.tsx index f6ced78..ee23534 100644 --- a/src/pages/Manager/User/Permission/components/ShareThing.tsx +++ b/src/pages/Manager/User/Permission/components/ShareThing.tsx @@ -1,7 +1,35 @@ -import { ActionType, ProList } from '@ant-design/pro-components'; -import { useIntl } from '@umijs/max'; -import { message } from 'antd'; -import { useRef } from 'react'; +import ThingsFilter from '@/components/shared/ThingFilterModal'; +import { DEFAULT_PAGE_SIZE } from '@/constants'; +import { + apiDeleteUserThingPolicy, + apiGetThingPolicyByUser, + apiShareThingToUser, +} from '@/services/master/ThingController'; +import { DeleteOutlined, ShareAltOutlined } from '@ant-design/icons'; +import { + ActionType, + FooterToolbar, + ProList, + ProListMetas, +} from '@ant-design/pro-components'; +import { FormattedMessage, useIntl } from '@umijs/max'; +import { + Button, + Checkbox, + GetProp, + message, + Popconfirm, + Tag, + Typography, +} from 'antd'; +import { useRef, useState } from 'react'; +const { Paragraph } = Typography; + +type PolicyShareDefault = { + read: boolean; + write: boolean; + delete: boolean; +}; type ShareThingProps = { user: MasterModel.ProfileResponse | null; @@ -9,56 +37,287 @@ type ShareThingProps = { const ShareThing = ({ user }: ShareThingProps) => { const listActionRef = useRef(); const intl = useIntl(); - const [messageAPI, contextHolder] = message.useMessage(); + const [messageApi, contextHolder] = message.useMessage(); + const [selectedRowsState, setSelectedRows] = useState< + MasterModel.ThingPolicy[] + >([]); + const [thingPolicy, setThingPolicy] = useState([]); + const [shareThingModalVisible, setShareThingModalVisible] = + useState(false); + const [policyShare, setPolicyShare] = useState({ + read: true, + write: true, + delete: true, + }); + const getPolicyInfo = ( + policy: MasterModel.Policy, + ): { color: string; text: string } => { + switch (policy) { + case 'read': + return { + color: 'blue', + text: intl.formatMessage({ + id: 'master.users.things.relation.read', + defaultMessage: 'Read', + }), + }; + case 'write': + return { + color: 'gold', + text: intl.formatMessage({ + id: 'master.users.things.relation.write', + defaultMessage: 'Write', + }), + }; + case 'delete': + return { + color: 'red', + text: intl.formatMessage({ + id: 'master.users.things.relation.delete', + defaultMessage: 'Delete', + }), + }; + default: + return { color: 'default', text: policy }; + } + }; + + const columns: ProListMetas = { + title: { + dataIndex: 'name', + render: (_, record: MasterModel.ThingPolicy) => ( + {record?.thing_name} + ), + }, + subTitle: { + dataIndex: 'metadata.external_id', + render: (_, record: MasterModel.ThingPolicy) => ( + {record?.external_id} + ), + }, + description: { + dataIndex: 'policies', + render: (_, record: MasterModel.ThingPolicy) => { + return record?.policies?.map((policy) => { + const info = getPolicyInfo(policy); + return ( + + {info.text} + + ); + }); + }, + }, + }; + + const handleShareThings = async (thingIds: string[]) => { + const thingsFiltered = thingIds.filter((thingId) => { + return !thingPolicy.find((thing) => thing.thing_id === thingId); + }); + if (thingsFiltered.length === 0) return; + const key = 'share'; + try { + messageApi.open({ + type: 'loading', + content: intl.formatMessage({ + id: 'master.users.things.sharing', + defaultMessage: 'Sharing devices...', + }), + key, + }); + const allShare = thingsFiltered.map(async (thingId) => { + const resp = await apiShareThingToUser( + thingId, + user?.id || '', + Object.keys(policyShare).filter( + (key) => policyShare[key as keyof PolicyShareDefault], + ), + ); + }); + + await Promise.all(allShare); + + messageApi.open({ + type: 'success', + content: intl.formatMessage({ + id: 'master.users.thing.share.success', + defaultMessage: 'Share successfully and will refresh soon', + }), + key, + }); + + await new Promise((resolve) => { + setTimeout(resolve, 500); + }); + + if (listActionRef.current) { + listActionRef.current.reload(); + } + } catch (error) { + console.error('Error when share thing: ', error); + messageApi.open({ + type: 'error', + content: intl.formatMessage({ + id: 'master.users.thing.share.fail', + defaultMessage: 'Share failed, please try again!', + }), + key, + }); + } + }; + + const onChange: GetProp = ( + checkedValues, + ) => { + setPolicyShare({ + read: checkedValues.includes('read'), + write: checkedValues.includes('write'), + delete: checkedValues.includes('delete'), + }); + }; + const handleUnshare = async (selectedRows: MasterModel.ThingPolicy[]) => { + if (!selectedRows) return true; + const key = 'unshare'; + try { + messageApi.open({ + type: 'loading', + content: intl.formatMessage({ + id: 'master.users.things.unsharing', + defaultMessage: 'Unsharing devices...', + }), + key, + }); + + const allUnshare = selectedRows.map(async (row) => { + const resp = await apiDeleteUserThingPolicy( + row?.thing_id || '', + user?.id || '', + ); + }); + + await Promise.all(allUnshare); + + messageApi.open({ + type: 'success', + content: intl.formatMessage({ + id: 'master.users.thing.unshare.success', + defaultMessage: 'Unshare successfully and will refresh soon', + }), + key, + }); + + await new Promise((resolve) => { + setTimeout(resolve, 500); + }); + + return true; + } catch (error) { + console.error('Error when unshare thing: ', error); + messageApi.open({ + type: 'error', + content: intl.formatMessage({ + id: 'master.users.thing.unshare.fail', + defaultMessage: 'Unshare failed, please try again!', + }), + key, + }); + return false; + } + }; return ( <> {contextHolder} - thing.thing_id!)} + disabled + extra={ + policyShare[key as keyof PolicyShareDefault], + )} + onChange={onChange} + /> + } + onSubmit={handleShareThings} + /> + headerTitle={intl.formatMessage({ - id: 'pages.users.things.list', + id: 'master.users.things.list', defaultMessage: 'List things', })} actionRef={listActionRef} toolBarRender={() => [ - // , + , ]} + pagination={{ + pageSize: DEFAULT_PAGE_SIZE, + }} metas={columns} - request={async () => { + request={async (params) => { + const { current, pageSize } = params; const query = { type: 'sub', id: user?.id || '', }; if (user?.id) { - const resp = (await apiQueryThingsByPolicy( - query, - )) as PolicyResponse; - const { relations } = resp; - if (relations) { - const queries = relations.map(async (rel: PolicyRelation) => { - const thg = await apiQueryThing(rel.id); - return { - ...thg, - relations: rel?.actions, - }; - }); - const policies = await Promise.all(queries); + const offset = current === 1 ? 0 : (current! - 1) * pageSize!; + const policyBody: Partial = { + offset: offset, + limit: pageSize, + }; + const resp = await apiGetThingPolicyByUser(policyBody, user.id); + + if (resp.things) { + setThingPolicy(resp.things); return Promise.resolve({ success: true, - data: policies, - total: policies.length, + data: resp.things, + total: resp.total, }); + } else { + return { + success: false, + data: [], + total: 0, + }; } } return Promise.resolve({ @@ -67,32 +326,67 @@ const ShareThing = ({ user }: ShareThingProps) => { total: 0, }); }} - rowKey="id" + rowKey="external_id" search={false} - // rowSelection={{ - // selectedRowKeys: selectedRowsState.map((row) => row.id).filter((id): id is string => id !== undefined), - // onChange: (_: React.Key[], selectedRows: API.Thing[]) => { - // setSelectedRows(selectedRows); - // }, - // }} - /> - {/* { - console.log(values); - const success = await handleShare(values); - if (success) { - handleShareModalVisible(false); - onReload(); - if (actionRef.current) { - //await delay(1000); - actionRef.current.reload(); - } - } + rowSelection={{ + selectedRowKeys: selectedRowsState + .map((row) => row.external_id) + .filter((id): id is string => id !== undefined), + onChange: (_, selectedRows: MasterModel.ThingPolicy[]) => { + setSelectedRows(selectedRows); + }, }} - /> */} + /> + {selectedRowsState?.length > 0 && ( + + {' '} + + {selectedRowsState.length} + {' '} + + + } + > + { + const success = await handleUnshare(selectedRowsState); + if (success) { + setSelectedRows([]); + if (listActionRef.current) { + listActionRef.current.reload(); + } + } + }} + > + + + + )} ); }; diff --git a/src/pages/Manager/User/index.tsx b/src/pages/Manager/User/index.tsx index 31e7031..e69c6c6 100644 --- a/src/pages/Manager/User/index.tsx +++ b/src/pages/Manager/User/index.tsx @@ -343,7 +343,7 @@ const ManagerUserPage = () => { > , + userId: string, +) { + return request( + `${API_THING_POLICY}/${userId}`, + { + params: params, + }, + ); +} + +export async function apiDeleteUserThingPolicy( + thing_id: string, + user_id: string, +) { + return request(`${API_SHARE_THING}/${thing_id}/share`, { + method: 'DELETE', + data: { + policies: ['read', 'write', 'delete'], + user_ids: [user_id], + }, + getResponse: true, + }); +} +export async function apiShareThingToUser( + thing_id: string, + user_id: string, + policies: string[], +) { + return request(`${API_SHARE_THING}/${thing_id}/share`, { + method: 'POST', + data: { + policies: policies, + user_ids: [user_id], + }, + getResponse: true, + }); +} diff --git a/src/services/master/typings.d.ts b/src/services/master/typings.d.ts index dc9fdbf..f55cd7e 100644 --- a/src/services/master/typings.d.ts +++ b/src/services/master/typings.d.ts @@ -160,21 +160,24 @@ declare namespace MasterModel { } // Thing Policy + interface ThingPolicyResponse { + total?: number; + offset?: number; + limit?: number; + order?: string; + direction?: string; + metadata?: null; + things?: ThingPolicy[]; + } + interface ThingPolicy { - next_page_token?: string; - relations?: Relation[]; + policies?: Policy[]; + thing_id?: string; + thing_name?: string; + external_id?: string; } - interface Relation { - id?: string; - actions?: Action[]; - } - - enum Action { - Delete = 'delete', - Read = 'read', - Write = 'write', - } + type Policy = 'read' | 'delete' | 'write'; // Group