Groups Members update

This commit is contained in:
MyNameIsBatman 2021-09-04 04:05:50 -03:00
parent 447fc2afff
commit ce38e9045c
12 changed files with 234 additions and 2 deletions

View File

@ -0,0 +1,6 @@
import { CreateLinkEvent } from '..';
export function GetGroupMembers(groupId: number): void
{
CreateLinkEvent(`groups/members/${groupId}`);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 203 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 198 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 B

View File

@ -603,6 +603,30 @@
height: 14px; height: 14px;
} }
&.icon-group-small-admin {
background: url('../images/groups/icons/group_icon_admin.png');
width: 11px;
height: 13px;
}
&.icon-group-small-not-admin {
background: url('../images/groups/icons/group_icon_not_admin.png');
width: 11px;
height: 13px;
}
&.icon-group-remove-member {
background: url('../images/groups/icons/group_icon_remove_member.png');
width: 13px;
height: 14px;
}
&.icon-group-small-owner {
background: url('../images/groups/icons/group_icon_small_owner.png');
width: 13px;
height: 13px;
}
&.icon-navigator-info { &.icon-navigator-info {
background: url('../images/navigator/icons/info.png'); background: url('../images/navigator/icons/info.png');
width: 18px; width: 18px;

View File

@ -49,5 +49,6 @@
@import './views/information/GroupInformationView'; @import './views/information/GroupInformationView';
@import './views/information-standalone/GroupInformationStandaloneView'; @import './views/information-standalone/GroupInformationStandaloneView';
@import './views/manager/GroupManagerView'; @import './views/manager/GroupManagerView';
@import './views/members/GroupMembersView';
@import './views/room-information/GroupRoomInformationView'; @import './views/room-information/GroupRoomInformationView';
@import './views/shared-tabs/GroupSharedTabs'; @import './views/shared-tabs/GroupSharedTabs';

View File

@ -8,12 +8,15 @@ import { GroupsMessageHandler } from './GroupsMessageHandler';
import { GroupCreatorView } from './views/creator/GroupCreatorView'; import { GroupCreatorView } from './views/creator/GroupCreatorView';
import { GroupInformationStandaloneView } from './views/information-standalone/GroupInformationStandaloneView'; import { GroupInformationStandaloneView } from './views/information-standalone/GroupInformationStandaloneView';
import { GroupManagerView } from './views/manager/GroupManagerView'; import { GroupManagerView } from './views/manager/GroupManagerView';
import { GroupMembersView } from './views/members/GroupMembersView';
export const GroupsView: FC<{}> = props => export const GroupsView: FC<{}> = props =>
{ {
const [ groupsState, dispatchGroupsState ] = useReducer(GroupsReducer, initialGroups); const [ groupsState, dispatchGroupsState ] = useReducer(GroupsReducer, initialGroups);
const [ isCreatorVisible, setIsCreatorVisible ] = useState<boolean>(false); const [ isCreatorVisible, setIsCreatorVisible ] = useState<boolean>(false);
const [ groupMembersId, setGroupMembersId ] = useState<number>(null);
const [ groupMembersLevel, setGroupMembersLevel ] = useState<number>(null);
useEffect(() => useEffect(() =>
{ {
@ -36,6 +39,13 @@ export const GroupsView: FC<{}> = props =>
SendMessageHook(new GroupSettingsComposer(Number(parts[2]))); SendMessageHook(new GroupSettingsComposer(Number(parts[2])));
return; return;
case 'members':
if(!parts[2]) return;
setGroupMembersId(Number(parts[2]));
if(parts[3]) setGroupMembersLevel(Number(parts[3]));
return;
} }
}, []); }, []);
@ -60,6 +70,12 @@ export const GroupsView: FC<{}> = props =>
}, []); }, []);
CreateMessageHook(GroupPurchasedEvent, onGroupPurchasedEvent); CreateMessageHook(GroupPurchasedEvent, onGroupPurchasedEvent);
const closeMembers = useCallback(() =>
{
setGroupMembersId(null);
setGroupMembersLevel(null);
}, []);
return ( return (
<GroupsContextProvider value={ { groupsState, dispatchGroupsState } }> <GroupsContextProvider value={ { groupsState, dispatchGroupsState } }>
@ -67,6 +83,7 @@ export const GroupsView: FC<{}> = props =>
<div className="nitro-groups"> <div className="nitro-groups">
<GroupCreatorView isVisible={ isCreatorVisible } onClose={ () => setIsCreatorVisible(false) } /> <GroupCreatorView isVisible={ isCreatorVisible } onClose={ () => setIsCreatorVisible(false) } />
<GroupManagerView /> <GroupManagerView />
<GroupMembersView groupId={ groupMembersId } levelId={ groupMembersLevel } onClose={ closeMembers } />
<GroupInformationStandaloneView /> <GroupInformationStandaloneView />
</div> </div>
</GroupsContextProvider> </GroupsContextProvider>

View File

@ -2,6 +2,7 @@ import { GroupRemoveMemberComposer } from '@nitrots/nitro-renderer';
import { FC, useCallback } from 'react'; import { FC, useCallback } from 'react';
import { CreateLinkEvent, GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api'; import { CreateLinkEvent, GetSessionDataManager, LocalizeText, TryVisitRoom } from '../../../../api';
import { GetGroupManager } from '../../../../api/groups/GetGroupManager'; import { GetGroupManager } from '../../../../api/groups/GetGroupManager';
import { GetGroupMembers } from '../../../../api/groups/GetGroupMembers';
import { TryJoinGroup } from '../../../../api/groups/TryJoinGroup'; import { TryJoinGroup } from '../../../../api/groups/TryJoinGroup';
import { SendMessageHook } from '../../../../hooks'; import { SendMessageHook } from '../../../../hooks';
import { CatalogPageName } from '../../../catalog/common/CatalogPageName'; import { CatalogPageName } from '../../../catalog/common/CatalogPageName';
@ -76,6 +77,12 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
{ {
switch(action) switch(action)
{ {
case 'members':
GetGroupMembers(groupInformation.id);
break;
case 'manage':
GetGroupManager(groupInformation.id);
break;
case 'homeroom': case 'homeroom':
TryVisitRoom(groupInformation.roomId); TryVisitRoom(groupInformation.roomId);
break; break;
@ -94,13 +101,13 @@ export const GroupInformationView: FC<GroupInformationViewProps> = props =>
<div className="group-badge"> <div className="group-badge">
<BadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } /> <BadgeImageView badgeCode={ groupInformation.badge } isGroup={ true } />
</div> </div>
<div className="mt-3 cursor-pointer"> <div className="mt-3 cursor-pointer" onClick={ () => handleAction('members') }>
{ LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) } { LocalizeText('group.membercount', ['totalMembers'], [groupInformation.membersCount.toString()]) }
</div> </div>
{ groupInformation.pendingRequestsCount > 0 && <div className="cursor-pointer"> { groupInformation.pendingRequestsCount > 0 && <div className="cursor-pointer">
{ LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) } { LocalizeText('group.pendingmembercount', ['totalMembers'], [groupInformation.pendingRequestsCount.toString()]) }
</div> } </div> }
{ groupInformation.isOwner && <div className="cursor-pointer" onClick={ () => GetGroupManager(groupInformation.id) }> { groupInformation.isOwner && <div className="cursor-pointer" onClick={ () => handleAction('manage') }>
{ LocalizeText('group.manage') } { LocalizeText('group.manage') }
</div> } </div> }
<div className="mt-auto mb-1"> <div className="mt-auto mb-1">

View File

@ -0,0 +1,29 @@
.nitro-group-members {
width: 400px;
.group-badge {
width: 50px;
height: 50px;
}
.members-list {
height: 300px;
.list-member {
border: 1px solid #ced4da;
.avatar-head {
position: relative;
overflow: hidden;
width: 40px;
height: 40px;
.avatar-image {
position: absolute;
left: -25px;
top: -25px;
}
}
}
}
}

View File

@ -0,0 +1,142 @@
import { GroupMemberParser, GroupMembersComposer, GroupMembersEvent, GroupMembersParser, GroupRank } from '@nitrots/nitro-renderer';
import classNames from 'classnames';
import { FC, KeyboardEvent, useCallback, useEffect, useState } from 'react';
import { GetSessionDataManager, LocalizeText } from '../../../../api';
import { CreateMessageHook, SendMessageHook } from '../../../../hooks';
import { NitroCardContentView, NitroCardHeaderView, NitroCardView } from '../../../../layout';
import { AvatarImageView } from '../../../shared/avatar-image/AvatarImageView';
import { BadgeImageView } from '../../../shared/badge-image/BadgeImageView';
import { GroupMembersViewProps } from './GroupMembersView.types';
export const GroupMembersView: FC<GroupMembersViewProps> = props =>
{
const { groupId = null, levelId = null, onClose = null } = props;
const [ pageData, setPageData ] = useState<GroupMembersParser>(null);
const [ searchQuery, setSearchQuery ] = useState<string>('');
const [ searchLevelId, setSearchLevelId ] = useState<number>(3);
const [ totalPages, setTotalPages ] = useState<number>(0);
const searchMembers = useCallback((pageId: number) =>
{
if(!groupId) return;
SendMessageHook(new GroupMembersComposer(groupId, pageId, searchQuery, searchLevelId));
}, [ groupId, searchQuery, searchLevelId ]);
const onGroupMembersEvent = useCallback((event: GroupMembersEvent) =>
{
const parser = event.getParser();
setPageData(parser);
setTotalPages(Math.ceil(parser.totalMembersCount / parser.pageSize));
}, []);
CreateMessageHook(GroupMembersEvent, onGroupMembersEvent);
useEffect(() =>
{
setPageData(null);
setSearchQuery('');
setSearchLevelId(0);
if(!groupId) return;
if(levelId !== null) setSearchLevelId(levelId);
searchMembers(0);
}, [ groupId, levelId ]);
useEffect(() =>
{
searchMembers(0);
}, [ searchLevelId ]);
const previousPage = useCallback(() =>
{
searchMembers(pageData.pageIndex - 1);
}, [ searchMembers, pageData ]);
const nextPage = useCallback(() =>
{
searchMembers(pageData.pageIndex + 1);
}, [ searchMembers, pageData ]);
const onKeyDown = useCallback((event: KeyboardEvent<HTMLInputElement>) =>
{
if(event.key !== 'Enter') return;
searchMembers(pageData.pageIndex);
}, [ searchMembers, pageData ]);
const getRankDescription = useCallback((member: GroupMemberParser) =>
{
if(member.rank === GroupRank.OWNER) return 'group.members.owner';
if(pageData.admin)
{
if(member.rank === GroupRank.ADMIN) return 'group.members.removerights';
if(member.rank === GroupRank.MEMBER) return 'group.members.giverights';
}
return '';
}, [ pageData ]);
if(!pageData) return null;
return (
<NitroCardView className="nitro-group-members" simple={ true }>
<NitroCardHeaderView headerText={ LocalizeText('group.members.title', ['groupName'], [ pageData.groupTitle ]) } onCloseClick={ onClose } />
<NitroCardContentView className="pb-2">
<div className="d-flex gap-2 align-items-center mb-2">
<div className="group-badge">
<BadgeImageView badgeCode={ pageData.badge } isGroup={ true } />
</div>
<div className="w-100">
<input type="text" className="form-control form-control-sm w-100 mb-1" placeholder={ LocalizeText('group.members.searchinfo') } value={ searchQuery } onChange={ (e) => setSearchQuery(e.target.value) } onBlur={ () => searchMembers(pageData.pageIndex) } onKeyDown={ onKeyDown } />
<select className="form-select form-select-sm w-100" value={ searchLevelId } onChange={ (e) => setSearchLevelId(Number(e.target.value)) }>
<option value="0">{ LocalizeText('group.members.search.all') }</option>
<option value="1">{ LocalizeText('group.members.search.admins') }</option>
<option value="2">{ LocalizeText('group.members.search.pending') }</option>
</select>
</div>
</div>
<div className="row row-cols-2 align-content-start g-0 w-100 members-list overflow-auto">
{ pageData.result.map((member, index) =>
{
return (
<div key={ index } className={ 'col pb-1' + classNames({ ' pe-1': index % 2 === 0 }) }>
<div className="list-member bg-white rounded d-flex text-black">
<div className="avatar-head flex-shrink-0">
<AvatarImageView figure={ member.figure } headOnly={ true } direction={ 2 } />
</div>
<div className="p-1 w-100">
<div className="fw-bold small">{ member.name }</div>
<div className="text-muted fst-italic small">{ LocalizeText('group.members.since', ['date'], [member.joinedAt]) }</div>
</div>
<div className="d-flex flex-column pe-2 align-items-center justify-content-center">
<div className="d-flex align-items-center">
<i className={ 'icon icon-group-small-' + classNames({ 'owner': member.rank === GroupRank.OWNER, 'admin': member.rank === GroupRank.ADMIN, 'not-admin': member.rank === GroupRank.MEMBER, 'cursor-pointer': pageData.admin }) } title={ LocalizeText(getRankDescription(member)) } />
</div>
{ member.rank !== GroupRank.OWNER && pageData.admin && member.id !== GetSessionDataManager().userId &&<div className="d-flex align-items-center mt-1">
<i className="icon cursor-pointer icon-group-remove-member" title={ LocalizeText(member.rank === GroupRank.REQUESTED ? 'group.members.reject' : 'group.members.kick') } />
</div> }
</div>
</div>
</div>
);
}) }
</div>
<div className="d-flex w-100 align-items-center">
<div>
<button disabled={ pageData.pageIndex === 0 } className="btn btn-primary" onClick={ previousPage }><i className="fas fa-chevron-left" /></button>
</div>
<div className="text-center text-black w-100">{ LocalizeText('group.members.pageinfo', ['amount', 'page', 'totalPages'], [pageData.totalMembersCount.toString(), (pageData.pageIndex + 1).toString(), totalPages.toString()]) }</div>
<div>
<button disabled={ pageData.pageIndex === totalPages - 1 } className="btn btn-primary" onClick={ nextPage }><i className="fas fa-chevron-right" /></button>
</div>
</div>
</NitroCardContentView>
</NitroCardView>
);
};

View File

@ -0,0 +1,6 @@
export interface GroupMembersViewProps
{
groupId: number;
levelId: number;
onClose: () => void;
}