From 0f00191c11bf3d7da92527ae9150e72d6ab5a7ea Mon Sep 17 00:00:00 2001 From: "Planoetscher Daniel (Student Com20)" <daniel.planoetscher@stud-inf.unibz.it> Date: Mon, 17 May 2021 13:47:42 +0200 Subject: [PATCH] connection of teams page to the backend --- .../components/layout/MemberList/index.tsx | 11 +- .../navigation/Dropdown/dropdown.scss | 7 ++ .../components/navigation/Dropdown/index.tsx | 24 +++-- .../src/components/navigation/Tabs/index.tsx | 12 ++- client/src/components/ui/Avatar/index.tsx | 2 +- client/src/components/ui/TeamMember/index.tsx | 21 ++-- .../components/ui/TeamMember/team-member.scss | 3 +- client/src/pages/AppWrapper.tsx | 2 +- .../pages/Projects/ProjectDetail/index.tsx | 2 +- .../Tasks/TaskDetail/TaskAssignees/index.tsx | 14 +-- client/src/pages/Tasks/TaskDetail/index.tsx | 2 +- client/src/pages/Teams/TeamsMembers/index.tsx | 14 +-- client/src/pages/Teams/index.tsx | 101 ++++++++++-------- server/src/v1/team.ts | 2 +- 14 files changed, 117 insertions(+), 100 deletions(-) diff --git a/client/src/components/layout/MemberList/index.tsx b/client/src/components/layout/MemberList/index.tsx index b2bca57..0a91de0 100644 --- a/client/src/components/layout/MemberList/index.tsx +++ b/client/src/components/layout/MemberList/index.tsx @@ -1,14 +1,9 @@ import './member-list.scss'; import TeamMember from 'components/ui/TeamMember'; - -interface TeamMemberInterface { - uuid: string; - name: string; - role: string; -} +import { TeamMember as ITeamMember } from 'adapters/team'; interface Props { - members: TeamMemberInterface[] + members: ITeamMember[] } export default function MemberList({ members }: Props) { @@ -18,7 +13,7 @@ export default function MemberList({ members }: Props) { + </div> {members.map((member) => ( - <TeamMember key={member.uuid} member={member} /> + <TeamMember key={member.id} member={member} /> ))} </div> ); diff --git a/client/src/components/navigation/Dropdown/dropdown.scss b/client/src/components/navigation/Dropdown/dropdown.scss index 3c04a83..ec63bac 100644 --- a/client/src/components/navigation/Dropdown/dropdown.scss +++ b/client/src/components/navigation/Dropdown/dropdown.scss @@ -15,6 +15,13 @@ margin-right: 5px; } } + + &.inactive { + cursor: text; + .icon { + display: none; + } + } &.open { .dropdown { diff --git a/client/src/components/navigation/Dropdown/index.tsx b/client/src/components/navigation/Dropdown/index.tsx index 13bc318..4643f30 100644 --- a/client/src/components/navigation/Dropdown/index.tsx +++ b/client/src/components/navigation/Dropdown/index.tsx @@ -2,7 +2,7 @@ import { ReactNode, useState } from 'react'; import { Link } from 'react-router-dom'; import './dropdown.scss'; -interface DropDownItem { +export interface DropDownItem { label: string; route: string; } @@ -15,19 +15,21 @@ interface Props { export default function Dropdown({ children, items }: Props) { const [isOpen, setOpen] = useState(false); return ( - <div className={'dropdown-container' + (isOpen ? ' open' : '')} onClick={() => setOpen(state => !state)}> + <div className={'dropdown-container' + (isOpen ? ' open' : '') + (items.length === 0 ? ' inactive' : '')} onClick={() => setOpen(state => !state)}> <div className="current-item"> {children} </div> - <div className="dropdown"> - { - items.map((item) => ( - <Link className="dropdown-item" key={item.label} to={item.route}> - {item.label} - </Link> - )) - } - </div> + {items.length > 0 && ( + <div className="dropdown"> + { + items.map((item) => ( + <Link className="dropdown-item" key={item.label} to={item.route}> + {item.label} + </Link> + )) + } + </div> + )} </div> ); } \ No newline at end of file diff --git a/client/src/components/navigation/Tabs/index.tsx b/client/src/components/navigation/Tabs/index.tsx index 897cf81..60e497d 100644 --- a/client/src/components/navigation/Tabs/index.tsx +++ b/client/src/components/navigation/Tabs/index.tsx @@ -1,9 +1,11 @@ -import { NavLink, RouteProps, Route } from 'react-router-dom'; +import { ReactNode } from 'react'; +import { NavLink, Route } from 'react-router-dom'; import './tabs.scss'; -interface Tab extends RouteProps { +export interface Tab { label: string; - routePath?: string; + route: string; + component: ReactNode } interface Props { @@ -16,7 +18,7 @@ export default function Tabs({ tabs }: Props) { <> <nav className="tabs-container"> {tabs.map((tab) => ( - <NavLink key={tab.label} className="tab" exact activeClassName="active" to={{ pathname: tab.path?.toString() }}> + <NavLink key={tab.label} className="tab" exact activeClassName="active" to={tab.route}> {tab.label} </NavLink> ))} @@ -24,7 +26,7 @@ export default function Tabs({ tabs }: Props) { </nav> { tabs.map((tab) => ( - <Route exact key={tab.label} {...tab} path={tab.routePath ?? tab.path} /> + <Route exact key={tab.label} path={tab.route} render={() => (tab.component)} /> )) } </> diff --git a/client/src/components/ui/Avatar/index.tsx b/client/src/components/ui/Avatar/index.tsx index ea6dba5..fdbbf7e 100644 --- a/client/src/components/ui/Avatar/index.tsx +++ b/client/src/components/ui/Avatar/index.tsx @@ -25,7 +25,7 @@ export default function Avatar({ user }: Props) { { error && ( <div className="standard-image"> - {user?.username.charAt(0)} + {user?.username && user.username.charAt(0)} </div> ) } diff --git a/client/src/components/ui/TeamMember/index.tsx b/client/src/components/ui/TeamMember/index.tsx index 5b5d4ec..6060a84 100644 --- a/client/src/components/ui/TeamMember/index.tsx +++ b/client/src/components/ui/TeamMember/index.tsx @@ -1,28 +1,21 @@ import './team-member.scss'; -import avatar from 'images/roland-bernard.jpg'; - -interface TeamMemberInterface { - name: string; - role: string; -} +import { TeamMember as ITeamMember } from 'adapters/team'; +import Avatar from 'components/ui/Avatar'; interface Props { - member: TeamMemberInterface + member: ITeamMember } export default function TeamMember({ member }: Props) { return ( - <div className="team-member-item"> - <div className="avatar-container"> - <img src={avatar} alt={member.name} /> - </div> + <Avatar user={member} /> <div className="details"> - <div className="name">{member.name}</div> - <div className="role">{member.role}</div> + <div className="name">{member.username}</div> + <div className="role">{member.role.name}</div> </div> <div className="settings"> - + </div> </div> ); diff --git a/client/src/components/ui/TeamMember/team-member.scss b/client/src/components/ui/TeamMember/team-member.scss index 6a4db6a..612a305 100644 --- a/client/src/components/ui/TeamMember/team-member.scss +++ b/client/src/components/ui/TeamMember/team-member.scss @@ -8,10 +8,11 @@ background: s.$white; border-radius: 15px; box-shadow: 0 0 10px rgba(s.$black, 0.1); - .avatar-container { + .avatar { width: 60px; height: 60px; transform: translateX(-50%); + font-size: 36px; img { width: 100%; border-radius: 50%; diff --git a/client/src/pages/AppWrapper.tsx b/client/src/pages/AppWrapper.tsx index 564827c..d2dbc2d 100644 --- a/client/src/pages/AppWrapper.tsx +++ b/client/src/pages/AppWrapper.tsx @@ -29,7 +29,7 @@ export default function AppWrapper() { <ProtectedRoute path="/stats" component={Stats} /> <ProtectedRoute path="/settings" component={Settings} /> <ProtectedRoute path="/teams/:uuid/edit" exact component={TeamsEdit} /> - <ProtectedRoute path="/teams" component={Teams} /> + <ProtectedRoute path={['/teams/:teamId', '/teams']} component={Teams} /> </Switch> </Suspense> </Header> diff --git a/client/src/pages/Projects/ProjectDetail/index.tsx b/client/src/pages/Projects/ProjectDetail/index.tsx index cb69c97..3885bd1 100644 --- a/client/src/pages/Projects/ProjectDetail/index.tsx +++ b/client/src/pages/Projects/ProjectDetail/index.tsx @@ -35,7 +35,7 @@ export default function ProjectDetail() { A basic shopping list app, much like Bring! </p> </div> - <Tabs tabs={tabs} /> + {/*<Tabs tabs={tabs} />*/} </div> </div> diff --git a/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx b/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx index 2d2f749..58116b7 100644 --- a/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx +++ b/client/src/pages/Tasks/TaskDetail/TaskAssignees/index.tsx @@ -1,14 +1,14 @@ +import { TeamMember } from 'adapters/team'; import MemberList from 'components/layout/MemberList'; -export default function TaskAssignees() { - const member = { - uuid: 'asdf', - name: 'Roland Bernard', - role: 'Backend' - } +interface Props { + assignees: TeamMember[] +} + +export default function TaskAssignees({assignees}: Props) { return ( <section className="teams-assignees-section"> - <MemberList members={[member]} /> + <MemberList members={assignees} /> </section> ); } \ No newline at end of file diff --git a/client/src/pages/Tasks/TaskDetail/index.tsx b/client/src/pages/Tasks/TaskDetail/index.tsx index 96cb73f..e41ffe1 100644 --- a/client/src/pages/Tasks/TaskDetail/index.tsx +++ b/client/src/pages/Tasks/TaskDetail/index.tsx @@ -52,7 +52,7 @@ export default function TaskDetail() { <ButtonLink href={'/tasks/' + uuid + '/edit'} className="dark expanded"> Edit </ButtonLink> - <Tabs tabs={tabs} /> + {/*<Tabs tabs={tabs} /> */} </div> </div> ); diff --git a/client/src/pages/Teams/TeamsMembers/index.tsx b/client/src/pages/Teams/TeamsMembers/index.tsx index fdb4fe3..4f18e46 100644 --- a/client/src/pages/Teams/TeamsMembers/index.tsx +++ b/client/src/pages/Teams/TeamsMembers/index.tsx @@ -1,15 +1,15 @@ import './teams-members.scss'; import MemberList from 'components/layout/MemberList'; +import { TeamMember } from 'adapters/team'; -export default function TeamsMembers() { - const member = { - uuid: 'asdf', - name: 'Roland Bernard', - role: 'Backend' - } +interface Props { + members: TeamMember[]; +} + +export default function TeamsMembers({ members }: Props) { return ( <section className="teams-members-section"> - <MemberList members={[member]} /> + <MemberList members={members} /> </section> ) } \ No newline at end of file diff --git a/client/src/pages/Teams/index.tsx b/client/src/pages/Teams/index.tsx index f146bc2..4956749 100644 --- a/client/src/pages/Teams/index.tsx +++ b/client/src/pages/Teams/index.tsx @@ -1,66 +1,88 @@ import './teams.scss'; import DetailGrid from 'components/layout/DetailGrid'; import ButtonLink from 'components/navigation/ButtonLink'; -import Tabs from 'components/navigation/Tabs'; -import Dropdown from 'components/navigation/Dropdown'; +import Tabs, { Tab } from 'components/navigation/Tabs'; +import Dropdown, { DropDownItem } from 'components/navigation/Dropdown'; import TeamsMembers from './TeamsMembers'; import TeamsStats from './TeamsStats'; import { useEffect, useState } from 'react'; import { getTeamMembers, getTeamProjects, getTeams, Team } from 'adapters/team'; import { DetailProps } from 'components/ui/DetailBox'; +import { useHistory, useParams } from 'react-router'; + +export interface Params { + teamId: string; +} export default function Teams() { + const { teamId } = useParams<Params>(); + const history = useHistory(); const [allTeams, setTeams] = useState<Team[]>(); const [currentTeam, setCurrentTeam] = useState<Team>(); const [details, setDetails] = useState<DetailProps[]>([]); + const [tabs, setTabs] = useState<Tab[]>([]); + const [pageLinks, setPageLinks] = useState<DropDownItem[]>([]); + useEffect(() => { getTeams().then((teams) => { - setTeams(teams); - setCurrentTeam(teams[0]); - if (teams[0]) { - getTeamProjects(teams[0].id).then((projects) => { - console.log(projects); - setDetails((state) => [...state, { - icon: 'folder', - title: 'Projects', - number: projects.length - }]); - }); - getTeamMembers(teams[0].id).then((members) => { - setDetails((state) => [...state, { - icon: 'group', - title: 'Members', - number: members.length - }]); - }) + //if no team is defined, take the first one + if (!teamId && teams[0]) { + history.push('/teams/' + teams[0].id); } + setTeams(teams); + setCurrentTeam(teams.find(team => team.id === teamId)); }).catch(() => { }); - }, []); + }, [teamId, history]); + + useEffect(() => { + if (currentTeam && allTeams) { + setDetails([]); + getTeamProjects(currentTeam.id).then((projects) => { + setDetails((state) => [...state, { + icon: 'folder', + title: 'Projects', + number: projects.length + }]); + }); + getTeamMembers(currentTeam.id).then((members) => { - const tabs = [{ - path: '/teams', - label: 'Members', - component: TeamsMembers - }, { - path: '/teams/stats', - label: 'Stats', - component: TeamsStats - }]; + setDetails((state) => [...state, { + icon: 'group', + title: 'Members', + number: members.length + }]); + + setTabs([{ + route: '/teams/' + currentTeam.id, + label: 'Members', + component: (<TeamsMembers members={members}/>) + }, { + route: '/teams/' + currentTeam.id + '/stats', + label: 'Stats', + component: <TeamsStats /> + }]); + + }); + + //update Tabs link + setPageLinks(allTeams.filter(team => currentTeam.id !== team.id).map(team => { + return { + route: '/teams' + team.id, + label: team.name, + } + })); + } + }, [currentTeam, allTeams]) - //TODO dynamic - const teams = [{ - route: '/teams/members?team=someOther', - label: 'Some other Team', - }]; return ( <div className="teams-page"> <div className="content-container"> <h1 className="underlined">Teams</h1> { - allTeams && allTeams?.length > 1 && ( - <Dropdown items={teams}> + allTeams && ( + <Dropdown items={pageLinks}> <h2>{currentTeam?.name}</h2> <span className="material-icons icon"> expand_more @@ -68,11 +90,6 @@ export default function Teams() { </Dropdown> ) } - { - allTeams && allTeams?.length === 1 && ( - <h2>{currentTeam?.name}</h2> - ) - } <DetailGrid details={details} /> <ButtonLink href={'/teams/' + currentTeam?.id + '/edit'} className="expanded"> Edit diff --git a/server/src/v1/team.ts b/server/src/v1/team.ts index a12609e..a006f6e 100644 --- a/server/src/v1/team.ts +++ b/server/src/v1/team.ts @@ -203,7 +203,7 @@ team.get('/:uuid/members', async (req, res) => { status: 'success', members: members.map(member => ({ id: member.id, - name: member.name, + username: member.name, email: member.email, realname: member.realname, role: { -- GitLab