From 91ea30f71577fa58b1c40047b8868600651e35c3 Mon Sep 17 00:00:00 2001
From: Roland Bernard <rolbernard@unibz.it>
Date: Sun, 20 Jun 2021 20:35:33 +0200
Subject: [PATCH] Added time selection to user and project stats

---
 .../src/components/navigation/Tabs/index.tsx  |  4 +-
 .../ProjectDetail/ProjectDetails/index.tsx    | 47 +++++++++++++--
 .../pages/Projects/ProjectDetail/index.tsx    |  2 +-
 client/src/pages/Stats/index.tsx              | 58 +++++++++----------
 client/src/pages/Teams/TeamsStats/index.tsx   | 16 ++---
 5 files changed, 80 insertions(+), 47 deletions(-)

diff --git a/client/src/components/navigation/Tabs/index.tsx b/client/src/components/navigation/Tabs/index.tsx
index 5f053b8..9239426 100644
--- a/client/src/components/navigation/Tabs/index.tsx
+++ b/client/src/components/navigation/Tabs/index.tsx
@@ -6,7 +6,7 @@ import './tabs.scss';
 
 export interface Tab {
     label: string;
-    route: string;
+    route: string | string[];
     link?: string;
     component: ReactNode
 }
@@ -24,7 +24,7 @@ export default function Tabs({ tabs }: Props) {
                         key={tab.label}
                         className="tab"
                         activeClassName="active"
-                        to={tab.link ?? tab.route}
+                        to={tab.link ?? (typeof tab.route === 'string' ? tab.route : tab.route[0])}
                         isActive={(_, location) =>
                             matchPath(location.pathname, { path: tab.route, exact: true })
                                 ? true
diff --git a/client/src/pages/Projects/ProjectDetail/ProjectDetails/index.tsx b/client/src/pages/Projects/ProjectDetail/ProjectDetails/index.tsx
index cf083b7..d1f64bf 100644
--- a/client/src/pages/Projects/ProjectDetail/ProjectDetails/index.tsx
+++ b/client/src/pages/Projects/ProjectDetail/ProjectDetails/index.tsx
@@ -1,9 +1,11 @@
 
 import { useEffect, useState } from 'react';
+import { useParams } from 'react-router';
 
 import { getTeam } from 'adapters/team';
 import { formatDate, subtractTime } from 'timely';
 
+import Dropdown from 'components/navigation/Dropdown';
 import DetailGrid from 'components/layout/DetailGrid';
 import LoadingScreen from 'components/ui/LoadingScreen';
 import { getProjectActivity, Project } from 'adapters/project';
@@ -11,6 +13,16 @@ import BarChart, { ChartItem, parseActivity } from 'components/graphs/BarChart';
 
 import './project-details.scss';
 
+enum Timespan {
+    WEEK = 'week',
+    MONTH = 'month',
+    YEAR = 'year',
+}
+
+interface Params {
+    time?: Timespan;
+}
+
 interface Props {
     project: Project
 }
@@ -19,13 +31,34 @@ export default function ProjectDetails({ project }: Props) {
     const [teams, setTeams] = useState<string[]>([]);
     const [activity, setActivity] = useState<ChartItem[]>([]);
 
+    const time = useParams<Params>().time ?? Timespan.WEEK;
+    const dropdowns = [
+        {
+            time: 'week',
+            label: 'Last week',
+            route: '/projects/' + project.id + '/stats/week'
+        },
+        {
+            time: 'month',
+            label: 'Last month',
+            route: '/projects/' + project.id + '/stats/month'
+        },
+        {
+            time: 'year',
+            label: 'Last year',
+            route: '/projects/' + project.id + '/stats/year'
+        }
+    ];
+
     useEffect(() => {
-        project.teams.forEach(teamId => {
-            getTeam(teamId).then((team) => setTeams(prev => [...prev, team.name]));
-        });
-        getProjectActivity(project.id, subtractTime(new Date(), 1, 'week'), new Date()).then((a) => setActivity(parseActivity(a)))
+        Promise.all(project.teams.map(getTeam))
+            .then(teams => setTeams(teams.map(team => team.name)));
     }, [project]);
 
+    useEffect(() => {
+        getProjectActivity(project.id, subtractTime(new Date(), 1, time), new Date()).then((a) => setActivity(parseActivity(a)))
+    }, [project, time]);
+
     let details = [{
         icon: 'group',
         title: 'Teams',
@@ -43,6 +76,12 @@ export default function ProjectDetails({ project }: Props) {
     return (
         <section className="project-details">
             <DetailGrid details={details} />
+            <Dropdown items={dropdowns.filter(d => d.time !== time)}>
+                <span className="material-icons icon">
+                    expand_more
+                </span>
+                {dropdowns.find(d => d.time === time)?.label}
+            </Dropdown>
             {
                 activity
                     ? <BarChart unit="h" multiplier={1 / 60 / 60 / 1000} data={activity} />
diff --git a/client/src/pages/Projects/ProjectDetail/index.tsx b/client/src/pages/Projects/ProjectDetail/index.tsx
index d345422..0060bf5 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() {
             setTabs([
                 {
                     label: 'Details',
-                    route: '/projects/' + projectId,
+                    route: ['/projects/' + projectId, '/projects/' + projectId + '/stats/:time'],
                     component: <ProjectDetails project={project} />
                 },
                 {
diff --git a/client/src/pages/Stats/index.tsx b/client/src/pages/Stats/index.tsx
index d5a3121..630a4fe 100644
--- a/client/src/pages/Stats/index.tsx
+++ b/client/src/pages/Stats/index.tsx
@@ -7,7 +7,7 @@ import { getUserActivity, getUserCompletion } from 'adapters/user';
 
 import LoadingScreen from 'components/ui/LoadingScreen';
 import CompletionGrid from 'components/layout/CompletionGrid';
-import Dropdown, { DropDownItem } from 'components/navigation/Dropdown';
+import Dropdown from 'components/navigation/Dropdown';
 import { CompletionProps, parseCompletion } from 'components/ui/Completion';
 import BarChart, { ChartItem, parseActivity } from 'components/graphs/BarChart';
 
@@ -23,14 +23,12 @@ interface Params {
     time?: Timespan;
 }
 
-interface FilterDropdownItem extends DropDownItem {
-    time: string
-}
-
 export default function Tasks() {
     const [completions, setCompletions] = useState<CompletionProps[]>();
     const [activity, setActivity] = useState<ChartItem[]>();
-    const [dropdowns] = useState<FilterDropdownItem[]>([
+
+    const time = useParams<Params>().time ?? Timespan.WEEK;
+    const dropdowns = [
         {
             time: 'week',
             label: 'Last week',
@@ -46,9 +44,7 @@ export default function Tasks() {
             label: 'Last year',
             route: '/stats/year'
         }
-    ]);
-
-    const time = useParams<Params>().time ?? Timespan.WEEK;
+    ];
 
     useEffect(() => {
         getUserCompletion().then((completion) => setCompletions(parseCompletion(completion)));
@@ -56,28 +52,30 @@ export default function Tasks() {
     }, [time]);
 
     return (
-        (completions && activity)
-            ? (
-                <div className="stats-page">
-                    <div className="content-container">
-                        <h1 className="underlined">Stats</h1>
-                        <div className="description-container">
-                            Here are some of your recent statistics.
-                        </div>
-                        <Dropdown items={dropdowns.filter(d => d.time !== time)}>
-                            <span className="material-icons icon">
-                                expand_more
-                            </span>
-                            {dropdowns.find(d => d.time === time)?.label}
-                        </Dropdown>
-                        <h2>Activity</h2>
-                        <BarChart unit="h" multiplier={1 / 60 / 60 / 1000} data={activity} />
-                        <h2>Completion</h2>
-                        <CompletionGrid items={completions} />
-                    </div>
+        <div className="stats-page">
+            <div className="content-container">
+                <h1 className="underlined">Stats</h1>
+                <div className="description-container">
+                    Here are some of your recent statistics.
                 </div>
-            )
-            : <LoadingScreen />
+                <Dropdown items={dropdowns.filter(d => d.time !== time)}>
+                    <span className="material-icons icon">
+                        expand_more
+                    </span>
+                    {dropdowns.find(d => d.time === time)?.label}
+                </Dropdown>
+                <h2>Activity</h2>
+                { activity
+                    ? <BarChart unit="h" multiplier={1 / 60 / 60 / 1000} data={activity} />
+                    : <LoadingScreen />
+                }
+                <h2>Completion</h2>
+                { completions
+                    ? <CompletionGrid items={completions} />
+                    : <LoadingScreen />
+                }
+            </div>
+        </div>
     );
 }
 
diff --git a/client/src/pages/Teams/TeamsStats/index.tsx b/client/src/pages/Teams/TeamsStats/index.tsx
index 255ccf6..b56fc4e 100644
--- a/client/src/pages/Teams/TeamsStats/index.tsx
+++ b/client/src/pages/Teams/TeamsStats/index.tsx
@@ -7,7 +7,7 @@ import { getTeamActivity, getTeamCompletion } from 'adapters/team';
 
 import LoadingScreen from 'components/ui/LoadingScreen';
 import CompletionGrid from 'components/layout/CompletionGrid';
-import Dropdown, { DropDownItem } from 'components/navigation/Dropdown';
+import Dropdown from 'components/navigation/Dropdown';
 import { CompletionProps, parseCompletion } from 'components/ui/Completion';
 import BarChart, { ChartItem, parseActivity } from 'components/graphs/BarChart';
 
@@ -27,14 +27,13 @@ interface Params {
     time: Timespan;
 }
 
-interface FilterDropdownItem extends DropDownItem {
-    time: string
-}
-
 export default function TeamsStats({ teamId }: Props) {
     const [activity, setActivity] = useState<ChartItem[]>([]);
     const [completions, setCompletions] = useState<CompletionProps[]>([]);
-    const [dropdowns] = useState<FilterDropdownItem[]>([
+    const history = useHistory();
+
+    const { time } = useParams<Params>();
+    const dropdowns = [
         {
             time: 'week',
             label: 'Last week',
@@ -50,10 +49,7 @@ export default function TeamsStats({ teamId }: Props) {
             label: 'Last year',
             route: '/teams/' + teamId + '/stats/year'
         }
-    ]);
-    const history = useHistory();
-
-    const { time } = useParams<Params>();
+    ];
 
     useEffect(() => {
         getTeamActivity(teamId, subtractTime(new Date(), 1, time), new Date())
-- 
GitLab