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