Skip to content

Commit 5b70936

Browse files
feat: show board participants frontend (#1007)
1 parent 2cac538 commit 5b70936

File tree

26 files changed

+670
-223
lines changed

26 files changed

+670
-223
lines changed
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React from 'react';
2+
3+
import Flex from '@/components/Primitives/Flex';
4+
import Icon from '@/components/icons/Icon';
5+
import {
6+
IconButton,
7+
InnerContainer,
8+
StyledMemberTitle,
9+
} from '@/components/Teams/CreateTeam/CardMember/styles';
10+
import { BoardUser } from '@/types/board/board.user';
11+
import Tooltip from '@/components/Primitives/Tooltip';
12+
import { boardParticipantsState } from '@/store/board/atoms/board.atom';
13+
import { useRecoilState } from 'recoil';
14+
import { usersListState } from '@/store/team/atom/team.atom';
15+
16+
type CardBodyProps = {
17+
member: BoardUser;
18+
isBoardCreator?: boolean;
19+
isOpen?: boolean;
20+
};
21+
22+
const ParticipantCard = React.memo<CardBodyProps>(({ member, isBoardCreator, isOpen }) => {
23+
const [boardParticipants, setBoardParticipants] = useRecoilState(boardParticipantsState);
24+
const [usersList, setUsersListState] = useRecoilState(usersListState);
25+
const handleRemove = () => {
26+
const updateParticipantsList = [...boardParticipants];
27+
const updateUsersList = [...usersList];
28+
29+
const userIdx = updateUsersList.findIndex((user) => user._id === member.user._id);
30+
const participantIdx = updateParticipantsList.findIndex(
31+
(boardUser) => boardUser.user._id === member.user._id,
32+
);
33+
34+
updateParticipantsList.splice(participantIdx, 1);
35+
updateUsersList.splice(userIdx, 1);
36+
37+
setUsersListState(usersList);
38+
setBoardParticipants(updateParticipantsList);
39+
};
40+
return (
41+
<Flex css={{ flex: '1 1 1' }} direction="column">
42+
<Flex>
43+
<InnerContainer
44+
align="center"
45+
elevation="1"
46+
justify="between"
47+
css={{
48+
position: 'relative',
49+
flex: '1 1 0',
50+
py: '$22',
51+
maxHeight: '$76',
52+
ml: 0,
53+
}}
54+
>
55+
<Flex align="center" css={{ width: '100%' }} gap="8">
56+
<Icon
57+
name="blob-personal"
58+
css={{
59+
width: '32px',
60+
height: '$32',
61+
zIndex: 1,
62+
opacity: isOpen ? 0.2 : 1,
63+
}}
64+
/>
65+
<Flex align="center" gap="8" justify="start" css={{ width: '50%' }}>
66+
<StyledMemberTitle>
67+
{`${member.user.firstName} ${member.user.lastName}`}
68+
</StyledMemberTitle>
69+
</Flex>
70+
{!isBoardCreator && (
71+
<Flex align="center" css={{ width: '50%' }} justify="end">
72+
<Tooltip content="Remove participant">
73+
<Flex justify="end">
74+
<IconButton
75+
onClick={handleRemove}
76+
css={{
77+
'&:hover': {
78+
cursor: 'pointer',
79+
},
80+
}}
81+
>
82+
<Icon
83+
name="trash-alt"
84+
css={{
85+
color: '$primary400',
86+
size: '$20',
87+
}}
88+
/>
89+
</IconButton>
90+
</Flex>
91+
</Tooltip>
92+
</Flex>
93+
)}
94+
</Flex>
95+
</InnerContainer>
96+
</Flex>
97+
</Flex>
98+
);
99+
});
100+
101+
export default ParticipantCard;
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import Icon from '@/components/icons/Icon';
2+
import { ContentSection } from '@/components/layouts/DashboardLayout/styles';
3+
import Button from '@/components/Primitives/Button';
4+
import Flex from '@/components/Primitives/Flex';
5+
import Text from '@/components/Primitives/Text';
6+
import ListMembersDialog from '@/components/Teams/CreateTeam/ListMembersDialog';
7+
import { boardParticipantsState } from '@/store/board/atoms/board.atom';
8+
import { usersListState } from '@/store/team/atom/team.atom';
9+
import { toastState } from '@/store/toast/atom/toast.atom';
10+
import { UserList } from '@/types/team/userList';
11+
import { BoardUserRoles } from '@/utils/enums/board.user.roles';
12+
import { ToastStateEnum } from '@/utils/enums/toast-types';
13+
import { useSession } from 'next-auth/react';
14+
import { useState } from 'react';
15+
import { useRecoilState, useSetRecoilState } from 'recoil';
16+
17+
const ParticipantsLayout: React.FC = ({ children }) => {
18+
const [isOpen, setIsOpen] = useState(false);
19+
const handleOpen = () => {
20+
setIsOpen(true);
21+
};
22+
const setToastState = useSetRecoilState(toastState);
23+
24+
const [usersList, setUsersList] = useRecoilState(usersListState);
25+
const [boardParticipants, setBoardParticipants] = useRecoilState(boardParticipantsState);
26+
const { data: session } = useSession({ required: true });
27+
28+
const saveParticipants = (checkedUserList: UserList[]) => {
29+
const listOfUsers = [...boardParticipants];
30+
const selectedUsers = checkedUserList.filter((user) => user.isChecked);
31+
const updatedListWithAdded = selectedUsers.map(
32+
(user) =>
33+
listOfUsers.find((member) => member.user._id === user._id) || {
34+
user,
35+
role: BoardUserRoles.MEMBER,
36+
votesCount: 0,
37+
},
38+
);
39+
40+
// Sort by Name
41+
updatedListWithAdded.sort((a, b) => {
42+
const aFullName = `${a.user.firstName.toLowerCase()} ${a.user.lastName.toLowerCase()}`;
43+
const bFullName = `${b.user.firstName.toLowerCase()} ${b.user.lastName.toLowerCase()}`;
44+
45+
return aFullName < bFullName ? -1 : 1;
46+
});
47+
48+
// this insures that the team creator stays always in first
49+
const userAdminIndex = updatedListWithAdded.findIndex(
50+
(member) => member.user._id === session?.user.id,
51+
);
52+
53+
updatedListWithAdded.unshift(updatedListWithAdded.splice(userAdminIndex, 1)[0]);
54+
55+
setBoardParticipants(updatedListWithAdded);
56+
setUsersList(checkedUserList);
57+
58+
setToastState({
59+
open: true,
60+
content: 'Team member/s successfully updated',
61+
type: ToastStateEnum.SUCCESS,
62+
});
63+
64+
setIsOpen(false);
65+
};
66+
67+
return (
68+
<ContentSection gap="36" justify="between">
69+
<Flex
70+
css={{ width: '100%', marginLeft: '152px', marginRight: '152px', mt: '50px' }}
71+
direction="column"
72+
gap="20"
73+
>
74+
<Flex justify="between">
75+
<Text heading="1">Participants</Text>
76+
<Button size="md" onClick={handleOpen}>
77+
<Icon css={{ color: 'white' }} name="plus" />
78+
Add/remove participants
79+
</Button>
80+
</Flex>
81+
{children}
82+
</Flex>
83+
<ListMembersDialog
84+
usersList={usersList}
85+
isOpen={isOpen}
86+
setIsOpen={setIsOpen}
87+
saveUsers={saveParticipants}
88+
title="Board Participants"
89+
btnTitle="Add/remove participants"
90+
/>
91+
</ContentSection>
92+
);
93+
};
94+
95+
export default ParticipantsLayout;
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import React from 'react';
2+
import { useSession } from 'next-auth/react';
3+
import { useRecoilValue } from 'recoil';
4+
import Flex from '@/components/Primitives/Flex';
5+
import { ScrollableContent } from '@/components/Boards/MyBoards/styles';
6+
import { boardParticipantsState } from '@/store/board/atoms/board.atom';
7+
import ParticipantCard from './ParticipantCard.tsx';
8+
import ParticipantsLayout from './ParticipantsLayout';
9+
10+
const ParticipantsList = () => {
11+
const { data: session } = useSession({ required: true });
12+
13+
const boardParticipants = useRecoilValue(boardParticipantsState);
14+
15+
return (
16+
<ParticipantsLayout>
17+
<Flex direction="column">
18+
<ScrollableContent
19+
direction="column"
20+
gap="8"
21+
justify="start"
22+
css={{ height: 'calc(100vh - 35vh)', paddingBottom: '$8' }}
23+
>
24+
{boardParticipants?.map((member) => (
25+
<ParticipantCard
26+
key={member.user._id}
27+
isBoardCreator={member.user._id === session?.user.id}
28+
member={member}
29+
/>
30+
))}
31+
</ScrollableContent>
32+
</Flex>
33+
</ParticipantsLayout>
34+
);
35+
};
36+
37+
export default ParticipantsList;

0 commit comments

Comments
 (0)