diff --git a/apps/app/components/workspace/activity-graph.tsx b/apps/app/components/workspace/activity-graph.tsx index 39c96890d..f3144f7a1 100644 --- a/apps/app/components/workspace/activity-graph.tsx +++ b/apps/app/components/workspace/activity-graph.tsx @@ -1,33 +1,23 @@ import { useEffect, useRef, useState } from "react"; -import { useRouter } from "next/router"; - -import useSWR from "swr"; - -// services -import userService from "services/user.service"; // ui import { Tooltip } from "components/ui"; // helpers import { renderDateFormat, renderShortNumericDateFormat } from "helpers/date-time.helper"; -// fetch-keys -import { USER_ACTIVITY } from "constants/fetch-keys"; +// types +import { IUserActivity } from "types"; // constants import { DAYS, MONTHS } from "constants/project"; -export const ActivityGraph = () => { +type Props = { + activities: IUserActivity[] | undefined; +}; + +export const ActivityGraph: React.FC = ({ activities }) => { const ref = useRef(null); const [width, setWidth] = useState(0); - const router = useRouter(); - const { workspaceSlug } = router.query; - - const { data: userActivity } = useSWR( - workspaceSlug ? USER_ACTIVITY(workspaceSlug as string) : null, - workspaceSlug ? () => userService.userActivity(workspaceSlug as string) : null - ); - const today = new Date(); const lastMonth = new Date(today.getFullYear(), today.getMonth() - 1, 1); const twoMonthsAgo = new Date(today.getFullYear(), today.getMonth() - 2, 1); @@ -112,7 +102,7 @@ export const ActivityGraph = () => { ref={ref} > {recentDates.map((date) => { - const isActive = userActivity?.find((a) => a.created_date === date); + const isActive = activities?.find((a) => a.created_date === date); return ( = ({ issues, type }) => { return ( diff --git a/apps/app/components/workspace/issues-pie-chart.tsx b/apps/app/components/workspace/issues-pie-chart.tsx index ef0eef461..fa2935990 100644 --- a/apps/app/components/workspace/issues-pie-chart.tsx +++ b/apps/app/components/workspace/issues-pie-chart.tsx @@ -2,52 +2,18 @@ import { useCallback, useState } from "react"; // recharts import { Cell, Legend, Pie, PieChart, ResponsiveContainer, Sector } from "recharts"; -// helpers -import { groupBy } from "helpers/array.helper"; // types -import { IIssue } from "types"; +import { IUserStateDistribution } from "types"; // constants import { STATE_GROUP_COLORS } from "constants/state"; type Props = { - issues: IIssue[] | undefined; + groupedIssues: IUserStateDistribution[] | undefined; }; -export const IssuesPieChart: React.FC = ({ issues }) => { +export const IssuesPieChart: React.FC = ({ groupedIssues }) => { const [activeIndex, setActiveIndex] = useState(0); - const groupedIssues = { - backlog: [], - unstarted: [], - started: [], - cancelled: [], - completed: [], - ...groupBy(issues ?? [], "state_detail.group"), - }; - - const data = [ - { - name: "Backlog", - value: groupedIssues.backlog.length, - }, - { - name: "Unstarted", - value: groupedIssues.unstarted.length, - }, - { - name: "Started", - value: groupedIssues.started.length, - }, - { - name: "Cancelled", - value: groupedIssues.cancelled.length, - }, - { - name: "Completed", - value: groupedIssues.completed.length, - }, - ]; - const onPieEnter = useCallback( (_: any, index: number) => { setActiveIndex(index); @@ -80,8 +46,8 @@ export const IssuesPieChart: React.FC = ({ issues }) => { return ( - - {payload.name} + + {payload.state_group} = ({ issues }) => { = ({ issues }) => { activeShape={renderActiveShape} onMouseEnter={onPieEnter} > - {data.map((cell: any) => ( - + {groupedIssues?.map((cell) => ( + ))} diff --git a/apps/app/components/workspace/issues-stats.tsx b/apps/app/components/workspace/issues-stats.tsx index 773bc0b5c..759f4c550 100644 --- a/apps/app/components/workspace/issues-stats.tsx +++ b/apps/app/components/workspace/issues-stats.tsx @@ -1,54 +1,70 @@ // components +import { Loader } from "components/ui"; import { ActivityGraph } from "components/workspace"; // helpers import { groupBy } from "helpers/array.helper"; // types -import { IIssue } from "types"; +import { IUserWorkspaceDashboard } from "types"; type Props = { - issues: IIssue[] | undefined; + data: IUserWorkspaceDashboard | undefined; }; -export const IssuesStats: React.FC = ({ issues }) => { - const groupedIssues = { - backlog: [], - unstarted: [], - started: [], - cancelled: [], - completed: [], - ...groupBy(issues ?? [], "state_detail.group"), - }; - - return ( -
- {issues ? ( - <> -
-
-

Issues assigned to you

-
{issues.length}
-
-
-

Pending issues

-
- {issues.length - groupedIssues.completed.length} -
-
-
-

Completed issues

-
{groupedIssues.completed.length}
-
-
-

Issues due by this week

-
24
-
-
-
-

Activity Graph

- -
- - ) : null} +export const IssuesStats: React.FC = ({ data }) => ( +
+
+
+

Issues assigned to you

+
+ {data ? ( + data.assigned_issues_count + ) : ( + + + + )} +
+
+
+

Pending issues

+
+ {data ? ( + data.pending_issues_count + ) : ( + + + + )} +
+
+
+

Completed issues

+
+ {data ? ( + data.completed_issues_count + ) : ( + + + + )} +
+
+
+

Issues due by this week

+
+ {data ? ( + data.issues_due_week_count + ) : ( + + + + )} +
+
- ); -}; +
+

Activity Graph

+ +
+
+); diff --git a/apps/app/constants/fetch-keys.ts b/apps/app/constants/fetch-keys.ts index cfdcaccf5..ce1241a33 100644 --- a/apps/app/constants/fetch-keys.ts +++ b/apps/app/constants/fetch-keys.ts @@ -79,6 +79,8 @@ export const STATE_DETAIL = "STATE_DETAILS"; export const USER_ISSUE = (workspaceSlug: string) => `USER_ISSUE_${workspaceSlug}`; export const USER_ACTIVITY = (workspaceSlug: string) => `USER_ACTIVITY_${workspaceSlug}`; +export const USER_WORKSPACE_DASHBOARD = (workspaceSlug: string) => + `USER_WORKSPACE_DASHBOARD_${workspaceSlug}`; export const USER_PROJECT_VIEW = (projectId: string) => `USER_PROJECT_VIEW_${projectId}`; export const MODULE_LIST = (projectId: string) => `MODULE_LIST_${projectId}`; diff --git a/apps/app/pages/[workspaceSlug]/index.tsx b/apps/app/pages/[workspaceSlug]/index.tsx index 5990e2292..6d600ef8f 100644 --- a/apps/app/pages/[workspaceSlug]/index.tsx +++ b/apps/app/pages/[workspaceSlug]/index.tsx @@ -2,10 +2,14 @@ import React from "react"; import { useRouter } from "next/router"; +import useSWR from "swr"; + // lib import { requiredAuth } from "lib/auth"; // layouts import AppLayout from "layouts/app-layout"; +// services +import userService from "services/user.service"; // hooks import useIssues from "hooks/use-issues"; // components @@ -15,10 +19,10 @@ import { IssuesPieChart, IssuesStats, } from "components/workspace"; -// helpers -import { orderArrayBy } from "helpers/array.helper"; // types import type { NextPage, GetServerSidePropsContext } from "next"; +// fetch-keys +import { USER_WORKSPACE_DASHBOARD } from "constants/fetch-keys"; const WorkspacePage: NextPage = () => { const router = useRouter(); @@ -26,23 +30,9 @@ const WorkspacePage: NextPage = () => { const { myIssues } = useIssues(workspaceSlug as string); - const overdueIssues = orderArrayBy( - myIssues?.filter( - (i) => - i.target_date && - i.target_date !== "" && - !i.completed_at && - new Date(i.target_date) < new Date() - ) ?? [], - "target_date", - "descending" - ); - - const incomingIssues = orderArrayBy( - myIssues?.filter( - (i) => i.target_date && i.target_date !== "" && new Date(i.target_date) > new Date() - ) ?? [], - "target_date" + const { data: workspaceDashboardData } = useSWR( + workspaceSlug ? USER_WORKSPACE_DASHBOARD(workspaceSlug as string) : null, + workspaceSlug ? () => userService.userWorkspaceDashboard(workspaceSlug as string) : null ); return ( @@ -68,11 +58,11 @@ const WorkspacePage: NextPage = () => {
- +
- - - + + +
diff --git a/apps/app/services/user.service.ts b/apps/app/services/user.service.ts index 0f0f85e9a..4e92e42e7 100644 --- a/apps/app/services/user.service.ts +++ b/apps/app/services/user.service.ts @@ -1,6 +1,6 @@ // services import APIService from "services/api.service"; -import type { IUser, IUserActivity } from "types"; +import type { IUser, IUserActivity, IUserWorkspaceDashboard } from "types"; const { NEXT_PUBLIC_API_BASE_URL } = process.env; @@ -57,6 +57,14 @@ class UserService extends APIService { throw error?.response?.data; }); } + + async userWorkspaceDashboard(workspaceSlug: string): Promise { + return this.get(`/api/users/me/workspaces/${workspaceSlug}/dashboard/`) + .then((response) => response?.data) + .catch((error) => { + throw error?.response?.data; + }); + } } export default new UserService(); diff --git a/apps/app/types/issues.d.ts b/apps/app/types/issues.d.ts index ab2f65052..ab2be7146 100644 --- a/apps/app/types/issues.d.ts +++ b/apps/app/types/issues.d.ts @@ -150,6 +150,7 @@ export interface IIssueComment { workspace: string; issue: string; } + export type IssuePriorities = { id: string; created_at: Date; @@ -207,6 +208,14 @@ export interface IIssueActivity { actor: string; } +export interface IIssueLite { + id: string; + name: string; + project_id: string; + target_date: string; + workspace__slug: string; +} + export interface IIssueFilterOptions { type: "active" | "backlog" | null; assignees: string[] | null; diff --git a/apps/app/types/users.d.ts b/apps/app/types/users.d.ts index 4bf6f7f64..61859c369 100644 --- a/apps/app/types/users.d.ts +++ b/apps/app/types/users.d.ts @@ -1,4 +1,4 @@ -import { IIssue, IWorkspace, NestedKeyOf, Properties } from "./"; +import { IIssue, IIssueLite, IWorkspace, NestedKeyOf, Properties } from "./"; export interface IUser { id: readonly string; @@ -41,6 +41,22 @@ export interface IUserActivity { activity_count: number; } +export interface IUserStateDistribution { + state_group: string; + state_count: number; +} + +export interface IUserWorkspaceDashboard { + assigned_issues_count: number; + completed_issues_count: number; + issue_activities: IUserActivity[]; + issues_due_week_count: number; + overdue_issues: IIssueLite[]; + pending_issues_count: number; + state_distribution: IUserStateDistribution[]; + upcoming_issues: IIssueLite[]; +} + export type UserAuth = { isMember: boolean; isOwner: boolean;