Client: EditGroupDialog: Rewrite dialog to work with infinite loader
This commit is contained in:
parent
fdc06cf61c
commit
a8e5a244f7
@ -34,3 +34,35 @@ export const StyledModal = styled(ModalDialog)`
|
||||
padding-top: 20px;
|
||||
}
|
||||
`;
|
||||
|
||||
export const StyledBodyLoader = styled.div`
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title-section {
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.group-title {
|
||||
margin-bottom: 4px;
|
||||
height: 16px;
|
||||
padding-top: 4px;
|
||||
}
|
||||
|
||||
.manager-title,
|
||||
.members-title {
|
||||
padding-block: 8px;
|
||||
}
|
||||
|
||||
.add-member-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-block: 8px;
|
||||
}
|
||||
|
||||
.member-row {
|
||||
padding-inline: 0;
|
||||
}
|
||||
`;
|
||||
|
@ -42,7 +42,7 @@ import GroupNameParam from "./sub-components/GroupNameParam";
|
||||
import HeadOfGroup from "./sub-components/HeadOfGroupParam";
|
||||
import MembersParam from "./sub-components/MembersParam";
|
||||
import SelectGroupManagerPanel from "./sub-components/HeadOfGroupParam/SelectGroupManagerPanel";
|
||||
import SelectGroupMembersPanel from "./sub-components/MembersParam/SelectGroupMembersPanel";
|
||||
import { SelectMembersPanel } from "./sub-components/create-components/SelectMembersPanel";
|
||||
|
||||
interface CreateGroupDialogProps {
|
||||
visible: boolean;
|
||||
@ -177,7 +177,7 @@ const CreateGroupDialog = ({
|
||||
)}
|
||||
|
||||
{selectMembersPanelIsVisible && (
|
||||
<SelectGroupMembersPanel
|
||||
<SelectMembersPanel
|
||||
isVisible={selectMembersPanelIsVisible}
|
||||
onClose={onHideSelectMembersPanel}
|
||||
onParentPanelClose={onClose}
|
||||
|
@ -24,172 +24,160 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { ChangeEvent, useEffect, useRef, useState } from "react";
|
||||
import { ModalDialog } from "@docspace/shared/components/modal-dialog";
|
||||
import { Button } from "@docspace/shared/components/button";
|
||||
import { ChangeEvent, useEffect, useState } from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { getGroupById } from "@docspace/shared/api/groups";
|
||||
import { compareGroupParams } from "./utils";
|
||||
import { EditGroupParams } from "./types";
|
||||
|
||||
import { Button, ButtonSize } from "@docspace/shared/components/button";
|
||||
import {
|
||||
ModalDialog,
|
||||
ModalDialogType,
|
||||
} from "@docspace/shared/components/modal-dialog";
|
||||
import { TGroup } from "@docspace/shared/api/groups/types";
|
||||
import EditGroupStore from "SRC_DIR/store/EditGroupStore";
|
||||
|
||||
import { StyledModal } from "./CreateEditGroupDialog.styled";
|
||||
import GroupNameParam from "./sub-components/GroupNameParam";
|
||||
import HeadOfGroup from "./sub-components/HeadOfGroupParam";
|
||||
import MembersParam from "./sub-components/MembersParam";
|
||||
import SelectGroupManagerPanel from "./sub-components/HeadOfGroupParam/SelectGroupManagerPanel";
|
||||
import SelectGroupMembersPanel from "./sub-components/MembersParam/SelectGroupMembersPanel";
|
||||
import { SelectMembersPanel } from "./sub-components/edit-components/SelectMembersPanel";
|
||||
import { BodyLoader } from "./sub-components/BodyLoader/BodyLoader";
|
||||
|
||||
interface EditGroupDialogProps {
|
||||
group: {
|
||||
members: object[];
|
||||
[key: string]: any;
|
||||
};
|
||||
type InjectedProps = Pick<
|
||||
EditGroupStore,
|
||||
| "initGroupData"
|
||||
| "resetGroupData"
|
||||
| "isInit"
|
||||
| "loadMembers"
|
||||
| "manager"
|
||||
| "addManager"
|
||||
| "removeManager"
|
||||
| "members"
|
||||
| "addMembers"
|
||||
| "removeMember"
|
||||
| "currentTotal"
|
||||
| "submitChanges"
|
||||
| "title"
|
||||
| "setTitle"
|
||||
| "hasChanges"
|
||||
>;
|
||||
|
||||
type EditGroupDialogProps = {
|
||||
group: TGroup;
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
updateGroup: (
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
groupManager: string,
|
||||
membersToAdd: string[],
|
||||
membersToRemove: string[],
|
||||
) => Promise<void>;
|
||||
}
|
||||
injectedProps?: InjectedProps;
|
||||
};
|
||||
|
||||
const EditGroupDialog = ({
|
||||
group,
|
||||
visible,
|
||||
onClose,
|
||||
updateGroup,
|
||||
setInfoPanelSelectedGroup,
|
||||
|
||||
injectedProps,
|
||||
}: EditGroupDialogProps) => {
|
||||
const {
|
||||
initGroupData,
|
||||
resetGroupData,
|
||||
isInit,
|
||||
loadMembers,
|
||||
manager,
|
||||
addManager,
|
||||
removeManager,
|
||||
members,
|
||||
addMembers,
|
||||
removeMember,
|
||||
currentTotal,
|
||||
submitChanges,
|
||||
title,
|
||||
setTitle,
|
||||
hasChanges,
|
||||
} = injectedProps!;
|
||||
|
||||
const { t } = useTranslation(["PeopleTranslations", "Common"]);
|
||||
|
||||
const [initialMembersIds, setInitialMembersIds] = useState<string[]>([]);
|
||||
|
||||
const [isCreateGroupLoading, setCreateGroupIsLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [isFetchMembersLoading, setFetchMembersIsLoading] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const [groupParams, setGroupParams] = useState<EditGroupParams>({
|
||||
groupName: group.name,
|
||||
groupManager: group.manager,
|
||||
groupMembers: null,
|
||||
});
|
||||
|
||||
const prevGroupParams = useRef({ ...groupParams });
|
||||
|
||||
const onChangeGroupName = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
setGroupParams((prev) => ({ ...prev, groupName: e.target.value }));
|
||||
|
||||
const setGroupManager = (groupManager: object | null) =>
|
||||
setGroupParams((prev) => ({ ...prev, groupManager }));
|
||||
|
||||
const setGroupMembers = (groupMembers: object[]) =>
|
||||
setGroupParams((prev) => ({ ...prev, groupMembers }));
|
||||
|
||||
const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
|
||||
const [selectGroupMangerPanelIsVisible, setSelectGroupMangerPanelIsVisible] =
|
||||
useState<boolean>(false);
|
||||
const [selectMembersPanelIsVisible, setSelectMembersPanelIsVisible] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const onChangeGroupName = (e: ChangeEvent<HTMLInputElement>) => {
|
||||
setTitle(e.target.value);
|
||||
};
|
||||
|
||||
const closeModal = () => {
|
||||
resetGroupData();
|
||||
onClose();
|
||||
};
|
||||
|
||||
const onShowSelectGroupManagerPanel = () =>
|
||||
setSelectGroupMangerPanelIsVisible(true);
|
||||
const onHideSelectGroupManagerPanel = () =>
|
||||
setSelectGroupMangerPanelIsVisible(false);
|
||||
|
||||
const [selectMembersPanelIsVisible, setSelectMembersPanelIsVisible] =
|
||||
useState<boolean>(false);
|
||||
|
||||
const onShowSelectMembersPanel = () => setSelectMembersPanelIsVisible(true);
|
||||
const onHideSelectMembersPanel = () => setSelectMembersPanelIsVisible(false);
|
||||
|
||||
const onEditGroup = async () => {
|
||||
setCreateGroupIsLoading(true);
|
||||
setIsSubmitting(true);
|
||||
|
||||
const groupManagerId = groupParams.groupManager?.id || undefined;
|
||||
await submitChanges();
|
||||
|
||||
const newMembersIds =
|
||||
groupParams.groupMembers?.map((gm: any) => gm.id) || [];
|
||||
const membersToAdd = newMembersIds.filter(
|
||||
(gm) => !initialMembersIds.includes(gm),
|
||||
);
|
||||
const membersToDelete = initialMembersIds.filter(
|
||||
(gm) => !newMembersIds.includes(gm),
|
||||
);
|
||||
|
||||
await updateGroup(
|
||||
group.id,
|
||||
groupParams.groupName,
|
||||
groupManagerId,
|
||||
membersToAdd,
|
||||
membersToDelete,
|
||||
);
|
||||
|
||||
setCreateGroupIsLoading(false);
|
||||
onClose();
|
||||
setIsSubmitting(false);
|
||||
closeModal();
|
||||
};
|
||||
|
||||
const notEnoughGroupParamsToEdit =
|
||||
!groupParams.groupName ||
|
||||
(!groupParams.groupManager && !groupParams.groupMembers?.length);
|
||||
|
||||
const groupParamsNotChanged = compareGroupParams(
|
||||
groupParams,
|
||||
prevGroupParams.current,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (groupParams.groupMembers) return;
|
||||
setFetchMembersIsLoading(true);
|
||||
initGroupData(group);
|
||||
|
||||
getGroupById(group.id, true)!
|
||||
.then((data: any) => {
|
||||
prevGroupParams.current.groupMembers = data.members;
|
||||
setInitialMembersIds(data.members.map((gm) => gm.id));
|
||||
setGroupMembers(data.members);
|
||||
})
|
||||
.then((data) => {
|
||||
setInfoPanelSelectedGroup(data);
|
||||
})
|
||||
.catch((err) => console.error(err))
|
||||
.finally(() => setFetchMembersIsLoading(false));
|
||||
}, [group.id]);
|
||||
return () => {
|
||||
resetGroupData();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const notEnoughParamsToEdit = !title || (!manager && !members?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<StyledModal
|
||||
displayType="aside"
|
||||
displayType={ModalDialogType.aside}
|
||||
withBodyScroll
|
||||
visible={visible}
|
||||
onClose={onClose}
|
||||
onClose={closeModal}
|
||||
withFooterBorder
|
||||
// isScrollLocked={isScrollLocked}
|
||||
// isOauthWindowOpen={isOauthWindowOpen}
|
||||
>
|
||||
<ModalDialog.Header>
|
||||
{t("PeopleTranslations:EditGroup")}
|
||||
</ModalDialog.Header>
|
||||
|
||||
<ModalDialog.Body>
|
||||
<GroupNameParam
|
||||
groupName={groupParams.groupName}
|
||||
onChangeGroupName={onChangeGroupName}
|
||||
/>
|
||||
<HeadOfGroup
|
||||
groupManager={groupParams.groupManager}
|
||||
setGroupManager={setGroupManager}
|
||||
groupMembers={groupParams.groupMembers}
|
||||
setGroupMembers={setGroupMembers}
|
||||
onShowSelectGroupManagerPanel={onShowSelectGroupManagerPanel}
|
||||
/>
|
||||
{!isFetchMembersLoading && (
|
||||
<MembersParam
|
||||
groupManager={groupParams.groupManager}
|
||||
groupMembers={groupParams.groupMembers}
|
||||
setGroupMembers={setGroupMembers}
|
||||
onShowSelectMembersPanel={onShowSelectMembersPanel}
|
||||
/>
|
||||
{isInit ? (
|
||||
<>
|
||||
<GroupNameParam
|
||||
groupName={title}
|
||||
onChangeGroupName={onChangeGroupName}
|
||||
/>
|
||||
<HeadOfGroup
|
||||
groupManager={manager}
|
||||
onShowSelectGroupManagerPanel={onShowSelectGroupManagerPanel}
|
||||
removeManager={removeManager}
|
||||
/>
|
||||
|
||||
<MembersParam
|
||||
groupManager={manager}
|
||||
groupMembers={members}
|
||||
setGroupMembers={addMembers}
|
||||
removeMember={removeMember}
|
||||
onShowSelectMembersPanel={onShowSelectMembersPanel}
|
||||
withInfiniteLoader
|
||||
total={currentTotal}
|
||||
loadNextPage={loadMembers}
|
||||
hasNextPage={!!members && members.length < currentTotal}
|
||||
/>
|
||||
</>
|
||||
) : (
|
||||
<BodyLoader />
|
||||
)}
|
||||
</ModalDialog.Body>
|
||||
|
||||
@ -198,21 +186,21 @@ const EditGroupDialog = ({
|
||||
id="edit-group-modal_submit"
|
||||
tabIndex={5}
|
||||
label={t("Common:SaveButton")}
|
||||
size="normal"
|
||||
size={ButtonSize.normal}
|
||||
primary
|
||||
scale
|
||||
onClick={onEditGroup}
|
||||
isDisabled={notEnoughGroupParamsToEdit || groupParamsNotChanged}
|
||||
isLoading={isCreateGroupLoading}
|
||||
isDisabled={!hasChanges || notEnoughParamsToEdit}
|
||||
isLoading={isSubmitting}
|
||||
/>
|
||||
<Button
|
||||
id="edit-group-modal_cancel"
|
||||
tabIndex={5}
|
||||
label={t("Common:CancelButton")}
|
||||
size="normal"
|
||||
size={ButtonSize.normal}
|
||||
scale
|
||||
isDisabled={isCreateGroupLoading}
|
||||
onClick={onClose}
|
||||
isDisabled={isSubmitting}
|
||||
onClick={closeModal}
|
||||
/>
|
||||
</ModalDialog.Footer>
|
||||
</StyledModal>
|
||||
@ -222,25 +210,60 @@ const EditGroupDialog = ({
|
||||
isVisible={selectGroupMangerPanelIsVisible}
|
||||
onClose={onHideSelectGroupManagerPanel}
|
||||
onParentPanelClose={onClose}
|
||||
setGroupManager={setGroupManager}
|
||||
setGroupManager={addManager}
|
||||
/>
|
||||
)}
|
||||
|
||||
{selectMembersPanelIsVisible && (
|
||||
<SelectGroupMembersPanel
|
||||
<SelectMembersPanel
|
||||
isVisible={selectMembersPanelIsVisible}
|
||||
onClose={onHideSelectMembersPanel}
|
||||
onParentPanelClose={onClose}
|
||||
groupManager={groupParams.groupManager}
|
||||
groupMembers={groupParams.groupMembers}
|
||||
setGroupMembers={setGroupMembers}
|
||||
addMembers={addMembers}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default inject(({ peopleStore, infoPanelStore }) => ({
|
||||
updateGroup: peopleStore.groupsStore.updateGroup,
|
||||
setInfoPanelSelectedGroup: infoPanelStore.setInfoPanelSelectedGroup,
|
||||
}))(observer(EditGroupDialog));
|
||||
export default inject<{ editGroupStore: EditGroupStore }>(
|
||||
({ editGroupStore }) => {
|
||||
const {
|
||||
initGroupData,
|
||||
resetGroupData,
|
||||
isInit,
|
||||
loadMembers,
|
||||
manager,
|
||||
addManager,
|
||||
removeManager,
|
||||
members,
|
||||
addMembers,
|
||||
removeMember,
|
||||
currentTotal,
|
||||
submitChanges,
|
||||
title,
|
||||
setTitle,
|
||||
hasChanges,
|
||||
} = editGroupStore;
|
||||
|
||||
return {
|
||||
injectedProps: {
|
||||
initGroupData,
|
||||
resetGroupData,
|
||||
isInit,
|
||||
loadMembers,
|
||||
manager,
|
||||
addManager,
|
||||
removeManager,
|
||||
members,
|
||||
addMembers,
|
||||
removeMember,
|
||||
currentTotal,
|
||||
submitChanges,
|
||||
title,
|
||||
setTitle,
|
||||
hasChanges,
|
||||
},
|
||||
};
|
||||
},
|
||||
)(observer(EditGroupDialog));
|
||||
|
@ -0,0 +1,43 @@
|
||||
import React from "react";
|
||||
|
||||
import { RectangleSkeleton } from "@docspace/shared/skeletons";
|
||||
import { RowLoader } from "@docspace/shared/skeletons/selector";
|
||||
|
||||
import { StyledBodyLoader } from "../../CreateEditGroupDialog.styled";
|
||||
|
||||
export const BodyLoader = () => {
|
||||
return (
|
||||
<StyledBodyLoader>
|
||||
<div className="title-section">
|
||||
<RectangleSkeleton className="group-title" width="50px" />
|
||||
<RectangleSkeleton height="32px" />
|
||||
</div>
|
||||
<div className="manager-section">
|
||||
<RectangleSkeleton
|
||||
className="manager-title"
|
||||
height="16px"
|
||||
width="100px"
|
||||
/>
|
||||
<RowLoader className="member-row" isMultiSelect isUser count={1} />
|
||||
</div>
|
||||
<div className="members-section">
|
||||
<RectangleSkeleton
|
||||
className="members-title"
|
||||
height="16px"
|
||||
width="100px"
|
||||
/>
|
||||
<div className="add-member-container">
|
||||
<RectangleSkeleton height="32px" width="32px" />
|
||||
<RectangleSkeleton height="14px" width="100px" />
|
||||
</div>
|
||||
<RowLoader
|
||||
className="member-row"
|
||||
isContainer
|
||||
isMultiSelect
|
||||
isUser
|
||||
count={10}
|
||||
/>
|
||||
</div>
|
||||
</StyledBodyLoader>
|
||||
);
|
||||
};
|
@ -24,11 +24,10 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { memo } from "react";
|
||||
import { ReactSVG } from "react-svg";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
|
||||
|
||||
import {
|
||||
Avatar,
|
||||
AvatarRole,
|
||||
@ -36,18 +35,16 @@ import {
|
||||
} from "@docspace/shared/components/avatar";
|
||||
import { getUserRole, getUserTypeLabel } from "@docspace/shared/utils/common";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import RemoveReactSvgUrl from "PUBLIC_DIR/images/remove.react.svg?url";
|
||||
|
||||
import * as Styled from "./index.styled";
|
||||
|
||||
interface GroupMemberRowProps {
|
||||
groupMember: TUser;
|
||||
onClickRemove: () => void;
|
||||
removeMember: (member: TUser) => void;
|
||||
}
|
||||
|
||||
const GroupMemberRow = ({
|
||||
groupMember,
|
||||
onClickRemove,
|
||||
}: GroupMemberRowProps) => {
|
||||
const GroupMemberRow = ({ groupMember, removeMember }: GroupMemberRowProps) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const role = getUserRole(groupMember);
|
||||
@ -68,6 +65,10 @@ const GroupMemberRow = ({
|
||||
default:
|
||||
}
|
||||
|
||||
const onRemove = () => {
|
||||
removeMember(groupMember);
|
||||
};
|
||||
|
||||
return (
|
||||
<Styled.GroupMemberRow>
|
||||
<Avatar
|
||||
@ -83,10 +84,10 @@ const GroupMemberRow = ({
|
||||
<ReactSVG
|
||||
className="remove-icon"
|
||||
src={RemoveReactSvgUrl}
|
||||
onClick={onClickRemove}
|
||||
onClick={onRemove}
|
||||
/>
|
||||
</Styled.GroupMemberRow>
|
||||
);
|
||||
};
|
||||
|
||||
export default GroupMemberRow;
|
||||
export default memo(GroupMemberRow);
|
||||
|
@ -0,0 +1,152 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import {
|
||||
Index,
|
||||
IndexRange,
|
||||
InfiniteLoader,
|
||||
List,
|
||||
ListRowProps,
|
||||
WindowScroller,
|
||||
} from "react-virtualized";
|
||||
import styled from "styled-components";
|
||||
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import { RowLoader } from "@docspace/shared/skeletons/selector";
|
||||
import GroupMemberRow from "SRC_DIR/components/dialogs/CreateEditGroupDialog/sub-components/GroupMemberRow";
|
||||
|
||||
const ROW_HEIGHT = 50;
|
||||
|
||||
export const StyledList = styled(List)`
|
||||
width: ${({ width }) => `${width - 16}px`} !important;
|
||||
|
||||
.group-member-row-loader {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
interface GroupMembersListProps {
|
||||
members: TUser[];
|
||||
removeMember: (member: TUser) => void;
|
||||
loadNextPage: (startIndex: number) => Promise<void>;
|
||||
hasNextPage: boolean;
|
||||
total: number;
|
||||
}
|
||||
|
||||
export const GroupMembersList = (props: GroupMembersListProps) => {
|
||||
const { members, removeMember, loadNextPage, hasNextPage, total } = props;
|
||||
|
||||
const [scrollElement, setScrollElement] = useState<HTMLDivElement | null>(
|
||||
null,
|
||||
);
|
||||
const [isNextPageLoading, setIsNextPageLoading] = useState(false);
|
||||
|
||||
const itemsCount = hasNextPage ? members.length + 1 : members.length;
|
||||
|
||||
const isItemLoaded = useCallback(
|
||||
({ index }: Index) => {
|
||||
return !hasNextPage || index < itemsCount;
|
||||
},
|
||||
[hasNextPage, itemsCount],
|
||||
);
|
||||
|
||||
const loadMoreItems = isNextPageLoading
|
||||
? async () => {}
|
||||
: async ({ startIndex }: IndexRange) => {
|
||||
setIsNextPageLoading(true);
|
||||
await loadNextPage(startIndex - 1);
|
||||
setIsNextPageLoading(false);
|
||||
};
|
||||
|
||||
const renderRow = ({ key, index, style }: ListRowProps) => {
|
||||
const item = members[index];
|
||||
|
||||
return (
|
||||
<div key={key} style={style}>
|
||||
{item ? (
|
||||
<GroupMemberRow groupMember={item} removeMember={removeMember} />
|
||||
) : (
|
||||
<RowLoader
|
||||
className="group-member-row-loader"
|
||||
isMultiSelect={false}
|
||||
isUser
|
||||
count={1}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const scrollEl = document.querySelector(
|
||||
"#modal-scroll > .scroll-wrapper > .scroller",
|
||||
);
|
||||
|
||||
if (scrollEl) {
|
||||
setScrollElement(scrollEl as HTMLDivElement);
|
||||
}
|
||||
}, []);
|
||||
|
||||
if (!scrollElement) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<InfiniteLoader
|
||||
loadMoreRows={loadMoreItems}
|
||||
isRowLoaded={isItemLoaded}
|
||||
rowCount={total}
|
||||
>
|
||||
{({ onRowsRendered, registerChild }) => (
|
||||
<WindowScroller scrollElement={scrollElement}>
|
||||
{({ height, isScrolling, scrollTop }) => {
|
||||
const scrollBodyRect =
|
||||
scrollElement.children[0].getBoundingClientRect();
|
||||
|
||||
return (
|
||||
<StyledList
|
||||
autoHeight
|
||||
height={height || scrollBodyRect.height}
|
||||
onRowsRendered={onRowsRendered}
|
||||
ref={registerChild}
|
||||
rowCount={itemsCount}
|
||||
rowHeight={ROW_HEIGHT}
|
||||
rowRenderer={renderRow}
|
||||
width={scrollBodyRect.width}
|
||||
isScrolling={isScrolling}
|
||||
overscanRowCount={3}
|
||||
scrollTop={scrollTop}
|
||||
// React virtualized sets "LTR" by default.
|
||||
style={{ direction: "inherit" }}
|
||||
/>
|
||||
);
|
||||
}}
|
||||
</WindowScroller>
|
||||
)}
|
||||
</InfiniteLoader>
|
||||
);
|
||||
};
|
@ -25,8 +25,11 @@
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ShareAccessRights } from "@docspace/shared/enums";
|
||||
import { Portal } from "@docspace/shared/components/portal";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
|
||||
import AddUsersPanel from "../../../../panels/AddUsersPanel";
|
||||
import { getAccessOptions } from "../../../../panels/InvitePanel/utils";
|
||||
|
||||
@ -34,7 +37,7 @@ interface SelectGroupManagerPanelProps {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onParentPanelClose: () => void;
|
||||
setGroupManager: (groupManager: object) => void;
|
||||
setGroupManager: (groupManager: TUser) => void;
|
||||
}
|
||||
|
||||
const SelectGroupManagerPanel = ({
|
||||
@ -46,7 +49,7 @@ const SelectGroupManagerPanel = ({
|
||||
const { t } = useTranslation(["InviteDialog"]);
|
||||
const accessOptions = getAccessOptions(t);
|
||||
|
||||
const onSelectGroupManager = (newGroupManager: object[]) => {
|
||||
const onSelectGroupManager = (newGroupManager: TUser[]) => {
|
||||
setGroupManager(newGroupManager[0]);
|
||||
};
|
||||
|
||||
|
@ -24,37 +24,28 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { SelectorAddButton } from "@docspace/shared/components/selector-add-button";
|
||||
import * as Styled from "./index.styled";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import PlusSvgUrl from "PUBLIC_DIR/images/icons/16/button.plus.react.svg?url";
|
||||
import SelectGroupManagerPanel from "./SelectGroupManagerPanel";
|
||||
|
||||
import * as Styled from "./index.styled";
|
||||
import GroupMemberRow from "../GroupMemberRow";
|
||||
|
||||
interface HeadOfGroupProps {
|
||||
groupManager: object | null;
|
||||
setGroupManager: (groupManager: object | null) => void;
|
||||
groupMembers: object[] | null;
|
||||
setGroupMembers: (groupMembers: object[]) => void;
|
||||
groupManager: TUser | null;
|
||||
removeManager: () => void;
|
||||
onShowSelectGroupManagerPanel: () => void;
|
||||
}
|
||||
|
||||
const HeadOfGroup = ({
|
||||
groupManager,
|
||||
setGroupManager,
|
||||
groupMembers,
|
||||
setGroupMembers,
|
||||
removeManager,
|
||||
onShowSelectGroupManagerPanel,
|
||||
}: HeadOfGroupProps) => {
|
||||
const { t } = useTranslation(["Common"]);
|
||||
|
||||
const onRemoveGroupManager = () => {
|
||||
setGroupManager(null);
|
||||
setGroupMembers(
|
||||
groupMembers?.filter((gm) => gm.id !== groupManager!.id) || [],
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Styled.Header>{t("Common:HeadOfGroup")}</Styled.Header>
|
||||
@ -67,7 +58,7 @@ const HeadOfGroup = ({
|
||||
) : (
|
||||
<GroupMemberRow
|
||||
groupMember={groupManager}
|
||||
onClickRemove={onRemoveGroupManager}
|
||||
removeMember={removeManager}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@ -26,38 +26,49 @@
|
||||
|
||||
import { useCallback } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { SelectorAddButton } from "@docspace/shared/components/selector-add-button";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import PlusSvgUrl from "PUBLIC_DIR/images/icons/16/button.plus.react.svg?url";
|
||||
|
||||
import * as Styled from "./index.styled";
|
||||
import SelectGroupMembersPanel from "./SelectGroupMembersPanel";
|
||||
import GroupMemberRow from "../GroupMemberRow";
|
||||
import { GroupMembersList } from "../GroupMembersList/GroupMembersList";
|
||||
|
||||
interface GroupMember {
|
||||
id: string;
|
||||
}
|
||||
type InfiniteLoaderProps =
|
||||
| {
|
||||
withInfiniteLoader: true;
|
||||
loadNextPage: (startIndex: number) => Promise<void>;
|
||||
hasNextPage: boolean;
|
||||
total: number;
|
||||
}
|
||||
| Partial<{
|
||||
withInfiniteLoader: undefined;
|
||||
loadNextPage: undefined;
|
||||
hasNextPage: undefined;
|
||||
total: undefined;
|
||||
}>;
|
||||
|
||||
interface MembersParamProps {
|
||||
groupManager: GroupMember | null;
|
||||
groupMembers: GroupMember[] | null;
|
||||
setGroupMembers: (groupMembers: GroupMember[]) => void;
|
||||
type MembersParamProps = {
|
||||
groupManager: TUser | null;
|
||||
groupMembers: TUser[] | null;
|
||||
setGroupMembers: (groupMembers: TUser[]) => void;
|
||||
onShowSelectMembersPanel: () => void;
|
||||
}
|
||||
removeMember: (member: TUser) => void;
|
||||
} & InfiniteLoaderProps;
|
||||
|
||||
const MembersParam = ({
|
||||
groupManager,
|
||||
groupMembers,
|
||||
setGroupMembers,
|
||||
onShowSelectMembersPanel,
|
||||
withInfiniteLoader,
|
||||
hasNextPage,
|
||||
loadNextPage,
|
||||
total,
|
||||
removeMember,
|
||||
}: MembersParamProps) => {
|
||||
const { t } = useTranslation(["Common", "PeopleTranslation"]);
|
||||
|
||||
const onRemoveUserById = useCallback(
|
||||
(id: string) => {
|
||||
const newGroupMembers = groupMembers?.filter((gm) => gm.id !== id);
|
||||
setGroupMembers(newGroupMembers || []);
|
||||
},
|
||||
[groupMembers, setGroupMembers],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Styled.Header>{t("Common:Members")}</Styled.Header>
|
||||
@ -67,16 +78,27 @@ const MembersParam = ({
|
||||
<div className="label">{t("PeopleTranslations:AddMembers")}</div>
|
||||
</Styled.AddMembersButton>
|
||||
|
||||
{groupMembers &&
|
||||
groupMembers
|
||||
.filter((member) => member.id !== groupManager?.id)
|
||||
.map((member) => (
|
||||
<GroupMemberRow
|
||||
key={member.id}
|
||||
groupMember={member}
|
||||
onClickRemove={() => onRemoveUserById(member.id)}
|
||||
/>
|
||||
))}
|
||||
{groupMembers ? (
|
||||
withInfiniteLoader ? (
|
||||
<GroupMembersList
|
||||
members={groupMembers}
|
||||
removeMember={removeMember}
|
||||
hasNextPage={hasNextPage}
|
||||
loadNextPage={loadNextPage}
|
||||
total={total}
|
||||
/>
|
||||
) : (
|
||||
groupMembers
|
||||
.filter((member) => member.id !== groupManager?.id)
|
||||
.map((member) => (
|
||||
<GroupMemberRow
|
||||
key={member.id}
|
||||
groupMember={member}
|
||||
removeMember={removeMember}
|
||||
/>
|
||||
))
|
||||
)
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -26,59 +26,38 @@
|
||||
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { ShareAccessRights } from "@docspace/shared/enums";
|
||||
import { toastr } from "@docspace/shared/components/toast";
|
||||
import { Portal } from "@docspace/shared/components/portal";
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import { TSelectorItem } from "@docspace/shared/components/selector";
|
||||
|
||||
import AddUsersPanel from "../../../../panels/AddUsersPanel";
|
||||
import { getAccessOptions } from "../../../../panels/InvitePanel/utils";
|
||||
|
||||
interface SelectGroupMembersPanelProps {
|
||||
type MembersSelectorProps = {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onParentPanelClose: () => void;
|
||||
groupManager?: TUser;
|
||||
groupMembers: TUser[];
|
||||
setGroupMembers: (groupMembers: (TUser | TSelectorItem)[]) => void;
|
||||
}
|
||||
|
||||
const SelectGroupMembersPanel = ({
|
||||
addMembers: (members: TUser[]) => void;
|
||||
} & (
|
||||
| { checkIfUserInvited: (user: TUser) => boolean; invitedUsers?: undefined }
|
||||
| { invitedUsers: string[]; checkIfUserInvited?: undefined }
|
||||
);
|
||||
|
||||
export const MembersSelector = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
addMembers,
|
||||
onParentPanelClose,
|
||||
groupManager,
|
||||
groupMembers,
|
||||
setGroupMembers,
|
||||
}: SelectGroupMembersPanelProps) => {
|
||||
onClose,
|
||||
checkIfUserInvited,
|
||||
invitedUsers,
|
||||
}: MembersSelectorProps) => {
|
||||
const { t } = useTranslation(["InviteDialog"]);
|
||||
const accessOptions = getAccessOptions(t, 5, false, true);
|
||||
|
||||
const onAddGroupMembers = (newGroupMembers: TSelectorItem[]) => {
|
||||
const resultGroupMembers: (TUser | TSelectorItem)[] = [...groupMembers];
|
||||
let showErrorWasSelected = false;
|
||||
|
||||
newGroupMembers.forEach((groupMember) => {
|
||||
if (groupMembers.findIndex((gm) => gm.id === groupMember.id) !== -1) {
|
||||
showErrorWasSelected = true;
|
||||
return;
|
||||
}
|
||||
resultGroupMembers.push(groupMember);
|
||||
});
|
||||
|
||||
if (showErrorWasSelected) {
|
||||
toastr.warning("Some users have already been added");
|
||||
}
|
||||
|
||||
setGroupMembers(resultGroupMembers);
|
||||
};
|
||||
|
||||
const invitedUsers = React.useMemo(
|
||||
() => [...groupMembers].map((g) => g?.id),
|
||||
[groupMembers],
|
||||
);
|
||||
|
||||
if (groupManager) invitedUsers.push(groupManager.id);
|
||||
const invitedProps = checkIfUserInvited
|
||||
? { checkIfUserInvited }
|
||||
: { invitedUsers };
|
||||
|
||||
return (
|
||||
<Portal
|
||||
@ -88,19 +67,17 @@ const SelectGroupMembersPanel = ({
|
||||
onClose={onClose}
|
||||
onParentPanelClose={onParentPanelClose}
|
||||
isMultiSelect
|
||||
setDataItems={onAddGroupMembers}
|
||||
setDataItems={addMembers}
|
||||
accessOptions={accessOptions}
|
||||
withAccessRights={false}
|
||||
isEncrypted
|
||||
defaultAccess={ShareAccessRights.FullAccess}
|
||||
withoutBackground
|
||||
withBlur={false}
|
||||
invitedUsers={invitedUsers}
|
||||
disableDisabledUsers
|
||||
{...invitedProps}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default SelectGroupMembersPanel;
|
@ -24,41 +24,43 @@
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import React from "react";
|
||||
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import { EditGroupParams, GroupMembers } from "../types";
|
||||
|
||||
const compareMembers = (a: GroupMembers, b: GroupMembers): boolean => {
|
||||
if (!a && !b) return true;
|
||||
if (!Array.isArray(a) || !Array.isArray(b)) return false;
|
||||
if (a.length !== b.length) return false;
|
||||
import { MembersSelector } from "../MembersSelector/MembersSelector";
|
||||
|
||||
const sortCb = (first: TUser, second: TUser) =>
|
||||
first.id < second.id ? -1 : 1;
|
||||
const sortedA = [...a].sort(sortCb);
|
||||
const sortedB = [...b].sort(sortCb);
|
||||
|
||||
return !sortedA.some((el, i) => el.id !== sortedB[i].id);
|
||||
type SelectMembersPanelProps = {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onParentPanelClose: () => void;
|
||||
groupManager?: TUser;
|
||||
groupMembers: TUser[];
|
||||
addMembers: (members: TUser[]) => void;
|
||||
};
|
||||
|
||||
const removeManagerFromMembers = (members: GroupMembers, managerId: string) => {
|
||||
return members?.filter((g) => g.id !== managerId) || null;
|
||||
};
|
||||
|
||||
export const compareGroupParams = (
|
||||
prev: EditGroupParams,
|
||||
current: EditGroupParams,
|
||||
): boolean => {
|
||||
const equalTitle = prev.groupName === current.groupName;
|
||||
const equalManager = prev.groupManager?.id === current.groupManager?.id;
|
||||
|
||||
const prevGroupMembers = prev.groupManager?.id
|
||||
? removeManagerFromMembers(prev.groupMembers, prev.groupManager.id)
|
||||
: prev.groupMembers;
|
||||
const currentGroupMembers = current.groupManager?.id
|
||||
? removeManagerFromMembers(current.groupMembers, current.groupManager.id)
|
||||
: current.groupMembers;
|
||||
|
||||
const equalMembers = compareMembers(prevGroupMembers, currentGroupMembers);
|
||||
|
||||
return equalTitle && equalManager && equalMembers;
|
||||
export const SelectMembersPanel = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
onParentPanelClose,
|
||||
groupManager,
|
||||
groupMembers,
|
||||
addMembers,
|
||||
}: SelectMembersPanelProps) => {
|
||||
const invitedUsers = React.useMemo(
|
||||
() => [...groupMembers].map((g) => g?.id),
|
||||
[groupMembers],
|
||||
);
|
||||
|
||||
if (groupManager) invitedUsers.push(groupManager.id);
|
||||
|
||||
return (
|
||||
<MembersSelector
|
||||
isVisible={isVisible}
|
||||
onClose={onClose}
|
||||
onParentPanelClose={onParentPanelClose}
|
||||
addMembers={addMembers}
|
||||
invitedUsers={invitedUsers}
|
||||
/>
|
||||
);
|
||||
};
|
@ -0,0 +1,62 @@
|
||||
import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import EditGroupStore from "SRC_DIR/store/EditGroupStore";
|
||||
|
||||
import { MembersSelector } from "../MembersSelector/MembersSelector";
|
||||
|
||||
type InjectedProps = Pick<
|
||||
EditGroupStore,
|
||||
"group" | "removedMembersMap" | "addedMembersMap"
|
||||
>;
|
||||
|
||||
type SelectMembersPanelProps = {
|
||||
isVisible: boolean;
|
||||
onClose: () => void;
|
||||
onParentPanelClose: () => void;
|
||||
addMembers: (members: TUser[]) => void;
|
||||
|
||||
injectedProps?: InjectedProps;
|
||||
};
|
||||
|
||||
const Panel = ({
|
||||
isVisible,
|
||||
onClose,
|
||||
onParentPanelClose,
|
||||
addMembers,
|
||||
|
||||
injectedProps,
|
||||
}: SelectMembersPanelProps) => {
|
||||
const { addedMembersMap, removedMembersMap, group } = injectedProps!;
|
||||
|
||||
const checkIfUserInvited = (user: TUser) => {
|
||||
if (removedMembersMap.has(user.id)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (addedMembersMap.has(user.id)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Boolean(user.groups?.find((g) => g.id === group?.id));
|
||||
};
|
||||
|
||||
return (
|
||||
<MembersSelector
|
||||
isVisible={isVisible}
|
||||
onClose={onClose}
|
||||
onParentPanelClose={onParentPanelClose}
|
||||
addMembers={addMembers}
|
||||
checkIfUserInvited={checkIfUserInvited}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export const SelectMembersPanel = inject<{ editGroupStore: EditGroupStore }>(
|
||||
({ editGroupStore }) => {
|
||||
const { group, removedMembersMap, addedMembersMap } = editGroupStore;
|
||||
|
||||
return { injectedProps: { group, removedMembersMap, addedMembersMap } };
|
||||
},
|
||||
)(observer(Panel));
|
@ -71,6 +71,7 @@ const toListItem = (
|
||||
invitedUsers?: string[],
|
||||
disableDisabledUsers?: boolean,
|
||||
isRoom?: boolean,
|
||||
checkIfUserInvited?: (user: TUser) => void,
|
||||
) => {
|
||||
if ("displayName" in item) {
|
||||
const {
|
||||
@ -87,13 +88,16 @@ const toListItem = (
|
||||
isRoomAdmin,
|
||||
status,
|
||||
shared,
|
||||
groups,
|
||||
} = item;
|
||||
|
||||
const role = getUserRole(item);
|
||||
|
||||
const userAvatar = hasAvatar ? avatar : DefaultUserPhoto;
|
||||
|
||||
const isInvited = invitedUsers?.includes(id) || (isRoom && shared);
|
||||
const isInvited = checkIfUserInvited
|
||||
? checkIfUserInvited(item)
|
||||
: invitedUsers?.includes(id) || (isRoom && shared);
|
||||
|
||||
const isDisabled =
|
||||
disableDisabledUsers && status === EmployeeStatus.Disabled;
|
||||
@ -117,6 +121,7 @@ const toListItem = (
|
||||
isRoomAdmin,
|
||||
isDisabled: isInvited || isDisabled,
|
||||
disabledText,
|
||||
groups,
|
||||
} as TSelectorItem;
|
||||
}
|
||||
|
||||
@ -162,6 +167,7 @@ type AddUsersPanelProps = {
|
||||
|
||||
invitedUsers?: string[];
|
||||
disableDisabledUsers?: boolean;
|
||||
checkIfUserInvited?: (user: TUser) => boolean;
|
||||
|
||||
roomId?: string | number;
|
||||
withGroups?: boolean;
|
||||
@ -189,6 +195,7 @@ const AddUsersPanel = ({
|
||||
|
||||
invitedUsers,
|
||||
disableDisabledUsers,
|
||||
checkIfUserInvited,
|
||||
}: AddUsersPanelProps) => {
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation([
|
||||
@ -285,6 +292,7 @@ const AddUsersPanel = ({
|
||||
newItem.isCollaborator = user.isCollaborator;
|
||||
newItem.isRoomAdmin = user.isRoomAdmin;
|
||||
newItem.email = user.email;
|
||||
newItem.groups = user.groups;
|
||||
}
|
||||
|
||||
items.push(newItem);
|
||||
@ -366,7 +374,14 @@ const AddUsersPanel = ({
|
||||
const totalDifferent = startIndex ? response.total - totalRef.current : 0;
|
||||
|
||||
const items = response.items.map((item) =>
|
||||
toListItem(item, t, invitedUsers, disableDisabledUsers, !!roomId),
|
||||
toListItem(
|
||||
item,
|
||||
t,
|
||||
invitedUsers,
|
||||
disableDisabledUsers,
|
||||
!!roomId,
|
||||
checkIfUserInvited,
|
||||
),
|
||||
);
|
||||
const newTotal = response.total - totalDifferent;
|
||||
|
||||
|
248
packages/client/src/store/EditGroupStore.ts
Normal file
248
packages/client/src/store/EditGroupStore.ts
Normal file
@ -0,0 +1,248 @@
|
||||
// (c) Copyright Ascensio System SIA 2009-2024
|
||||
//
|
||||
// This program is a free software product.
|
||||
// You can redistribute it and/or modify it under the terms
|
||||
// of the GNU Affero General Public License (AGPL) version 3 as published by the Free Software
|
||||
// Foundation. In accordance with Section 7(a) of the GNU AGPL its Section 15 shall be amended
|
||||
// to the effect that Ascensio System SIA expressly excludes the warranty of non-infringement of
|
||||
// any third-party rights.
|
||||
//
|
||||
// This program is distributed WITHOUT ANY WARRANTY, without even the implied warranty
|
||||
// of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. For details, see
|
||||
// the GNU AGPL at: http://www.gnu.org/licenses/agpl-3.0.html
|
||||
//
|
||||
// You can contact Ascensio System SIA at Lubanas st. 125a-25, Riga, Latvia, EU, LV-1021.
|
||||
//
|
||||
// The interactive user interfaces in modified source and object code versions of the Program must
|
||||
// display Appropriate Legal Notices, as required under Section 5 of the GNU AGPL version 3.
|
||||
//
|
||||
// Pursuant to Section 7(b) of the License you must retain the original Product logo when
|
||||
// distributing the program. Pursuant to Section 7(e) we decline to grant you any rights under
|
||||
// trademark law for use of our trademarks.
|
||||
//
|
||||
// All the Product's GUI elements, including illustrations and icon sets, as well as technical writing
|
||||
// content are licensed under the terms of the Creative Commons Attribution-ShareAlike 4.0
|
||||
// International. See the License terms at http://creativecommons.org/licenses/by-sa/4.0/legalcode
|
||||
|
||||
import { makeAutoObservable } from "mobx";
|
||||
|
||||
import { TUser } from "@docspace/shared/api/people/types";
|
||||
import { TGroup } from "@docspace/shared/api/groups/types";
|
||||
import AccountsFilter from "@docspace/shared/api/people/filter";
|
||||
import api from "@docspace/shared/api";
|
||||
import PeopleStore from "SRC_DIR/store/PeopleStore";
|
||||
import GroupsStore from "SRC_DIR/store/GroupsStore";
|
||||
|
||||
class EditGroupStore {
|
||||
isInit = false;
|
||||
|
||||
group: TGroup | null = null;
|
||||
|
||||
title: string = "";
|
||||
|
||||
manager: TUser | null = null;
|
||||
|
||||
members: TUser[] | null = null;
|
||||
|
||||
addedMembersMap: Map<string, TUser> = new Map();
|
||||
|
||||
removedMembersMap: Map<string, TUser> = new Map();
|
||||
|
||||
initialTotal: number = 0;
|
||||
|
||||
filter = AccountsFilter.getDefault();
|
||||
|
||||
peopleStore: PeopleStore;
|
||||
|
||||
constructor(peopleStore: PeopleStore) {
|
||||
this.peopleStore = peopleStore;
|
||||
|
||||
makeAutoObservable(this);
|
||||
}
|
||||
|
||||
initGroupData = async (group: TGroup) => {
|
||||
try {
|
||||
this.setGroup(group);
|
||||
this.setTitle(group.name);
|
||||
|
||||
if (group.manager) {
|
||||
this.setManager(group.manager);
|
||||
}
|
||||
|
||||
this.filter.group = group.id;
|
||||
this.filter.pageCount = 100;
|
||||
|
||||
await this.loadMembers(0);
|
||||
|
||||
this.setIsInit(true);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
resetGroupData = () => {
|
||||
this.isInit = false;
|
||||
this.group = null;
|
||||
this.title = "";
|
||||
this.manager = null;
|
||||
this.members = null;
|
||||
this.addedMembersMap = new Map();
|
||||
this.removedMembersMap = new Map();
|
||||
this.initialTotal = 0;
|
||||
this.filter = AccountsFilter.getDefault();
|
||||
};
|
||||
|
||||
loadMembers = async (startIndex: number) => {
|
||||
try {
|
||||
if (!this.group?.id) return;
|
||||
|
||||
this.filter.page = !startIndex ? 0 : this.filter.page + 1;
|
||||
|
||||
const res = await api.people.getUserList(this.filter);
|
||||
|
||||
const membersWithoutManager = res.items.filter(
|
||||
(item) =>
|
||||
item.id !== this.manager?.id || item.id !== this.group?.manager?.id,
|
||||
);
|
||||
|
||||
this.setInitialTotal(res.total);
|
||||
|
||||
if (startIndex === 0 || !this.members) {
|
||||
this.setMembers(membersWithoutManager);
|
||||
} else {
|
||||
this.setMembers([...this.members, ...membersWithoutManager]);
|
||||
}
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
submitChanges = async () => {
|
||||
try {
|
||||
if (!this.group) return;
|
||||
|
||||
const { updateGroup } = this.peopleStore.groupsStore! as GroupsStore;
|
||||
|
||||
const addedIds = Array.from(this.addedMembersMap.keys());
|
||||
const removedIds = Array.from(this.removedMembersMap.keys());
|
||||
|
||||
await updateGroup(
|
||||
this.group?.id,
|
||||
this.title.trim(),
|
||||
this.manager?.id,
|
||||
addedIds,
|
||||
removedIds,
|
||||
);
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
};
|
||||
|
||||
addManager = (manager: TUser) => {
|
||||
this.removedMembersMap.delete(manager.id);
|
||||
const alreadyMember = manager.groups?.find((g) => g.id === this.group?.id);
|
||||
|
||||
if (!alreadyMember) {
|
||||
this.addedMembersMap.set(manager.id, manager);
|
||||
}
|
||||
|
||||
if (this.members?.length) {
|
||||
this.members = this.members.filter((member) => member.id !== manager.id);
|
||||
}
|
||||
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
removeManager = () => {
|
||||
if (!this.manager) return;
|
||||
|
||||
const wasAdded = this.addedMembersMap.delete(this.manager.id);
|
||||
|
||||
if (!wasAdded) {
|
||||
this.removedMembersMap.set(this.manager.id, this.manager);
|
||||
}
|
||||
|
||||
this.manager = null;
|
||||
};
|
||||
|
||||
setIsInit = (value: boolean) => {
|
||||
this.isInit = value;
|
||||
};
|
||||
|
||||
setGroup = (group: TGroup) => {
|
||||
this.group = group;
|
||||
};
|
||||
|
||||
setTitle = (title: string) => {
|
||||
this.title = title;
|
||||
};
|
||||
|
||||
setManager = (manager: TUser | null) => {
|
||||
this.manager = manager;
|
||||
};
|
||||
|
||||
setMembers = (members: TUser[] | null) => {
|
||||
this.members = members;
|
||||
};
|
||||
|
||||
addMembers = (members: TUser[]) => {
|
||||
members.forEach((member) => {
|
||||
const wasRemoved = this.removedMembersMap.delete(member.id);
|
||||
|
||||
if (!wasRemoved) {
|
||||
this.addedMembersMap.set(member.id, member);
|
||||
}
|
||||
});
|
||||
|
||||
this.members = this.members ? [...this.members, ...members] : members;
|
||||
};
|
||||
|
||||
removeMember = (member: TUser) => {
|
||||
const wasAdded = this.addedMembersMap.delete(member.id);
|
||||
|
||||
if (!wasAdded) {
|
||||
this.removedMembersMap.set(member.id, member);
|
||||
}
|
||||
|
||||
this.members = this.members?.filter((m) => m.id !== member.id) || null;
|
||||
};
|
||||
|
||||
setInitialTotal = (total: number) => {
|
||||
this.initialTotal = total;
|
||||
};
|
||||
|
||||
get currentTotal() {
|
||||
let total =
|
||||
this.initialTotal +
|
||||
this.addedMembersMap.size -
|
||||
this.removedMembersMap.size;
|
||||
|
||||
const prevManager = this.group?.manager;
|
||||
const newManager = this.manager;
|
||||
const managerWasChanged = prevManager?.id !== newManager?.id;
|
||||
|
||||
if (prevManager && !managerWasChanged) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
if (newManager && managerWasChanged) {
|
||||
total -= 1;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
get hasChanges() {
|
||||
const titleWasChanged = this.title.trim() !== this.group?.name;
|
||||
const managerWasChanged = this.group?.manager?.id !== this.manager?.id;
|
||||
|
||||
return (
|
||||
titleWasChanged ||
|
||||
managerWasChanged ||
|
||||
this.addedMembersMap.size ||
|
||||
this.removedMembersMap.size
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default EditGroupStore;
|
@ -637,7 +637,7 @@ class GroupsStore {
|
||||
updateGroup = async (
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
groupManagerId: string,
|
||||
groupManagerId: string | undefined,
|
||||
membersToAdd: string[],
|
||||
membersToRemove: string[],
|
||||
) => {
|
||||
|
@ -80,6 +80,7 @@ import ImportAccountsStore from "./ImportAccountsStore";
|
||||
import PluginStore from "./PluginStore";
|
||||
import InfoPanelStore from "./InfoPanelStore";
|
||||
import CampaignsStore from "./CampaignsStore";
|
||||
import EditGroupStore from "./EditGroupStore";
|
||||
|
||||
const selectedFolderStore = new SelectedFolderStore(settingsStore);
|
||||
|
||||
@ -300,6 +301,8 @@ const storageManagement = new StorageManagement(
|
||||
|
||||
const campaignsStore = new CampaignsStore(settingsStore, userStore);
|
||||
|
||||
const editGroupStore = new EditGroupStore(peopleStore);
|
||||
|
||||
const store = {
|
||||
authStore,
|
||||
userStore,
|
||||
@ -354,6 +357,7 @@ const store = {
|
||||
pluginStore,
|
||||
storageManagement,
|
||||
campaignsStore,
|
||||
editGroupStore,
|
||||
};
|
||||
|
||||
export default store;
|
||||
|
@ -127,7 +127,7 @@ export const getGroupMembersInRoom = (
|
||||
export const updateGroup = (
|
||||
groupId: string,
|
||||
groupName: string,
|
||||
groupManager: string,
|
||||
groupManager: string | undefined,
|
||||
membersToAdd: string[],
|
||||
membersToRemove: string[],
|
||||
) => {
|
||||
|
@ -30,7 +30,7 @@ import { ShareAccessRights } from "../../enums";
|
||||
export type TGroup = {
|
||||
category: string;
|
||||
id: string;
|
||||
manager: TUser;
|
||||
manager?: TUser;
|
||||
name: string;
|
||||
parent: string;
|
||||
isGroup?: boolean;
|
||||
|
@ -30,6 +30,7 @@ import { MergeTypes, Nullable } from "../../types";
|
||||
|
||||
import { TFileSecurity, TFolderSecurity } from "../../api/files/types";
|
||||
import { TRoomSecurity } from "../../api/rooms/types";
|
||||
import { TGroup } from "../../api/groups/types";
|
||||
|
||||
import { AvatarRole } from "../avatar";
|
||||
import { TTabItem } from "../tabs";
|
||||
@ -394,6 +395,7 @@ type TSelectorItemEmpty = {
|
||||
iconOriginal?: undefined;
|
||||
role?: undefined;
|
||||
email?: undefined;
|
||||
groups?: TGroup[];
|
||||
isOwner?: undefined;
|
||||
isAdmin?: undefined;
|
||||
isVisitor?: undefined;
|
||||
@ -438,6 +440,7 @@ export type TSelectorItemUser = MergeTypes<
|
||||
avatar: string;
|
||||
hasAvatar: boolean;
|
||||
role: AvatarRole;
|
||||
groups?: TGroup[];
|
||||
|
||||
access?: ShareAccessRights | string | number;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user