diff --git a/apps/app/components/core/sidebar/sidebar-progress-stats.tsx b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx new file mode 100644 index 000000000..63b4a1d02 --- /dev/null +++ b/apps/app/components/core/sidebar/sidebar-progress-stats.tsx @@ -0,0 +1,179 @@ +import React from "react"; +import { Tab } from "@headlessui/react"; +import { useRouter } from "next/router"; +import useSWR from "swr"; +import Image from "next/image"; +// ui +import SingleProgressStats from "./single-progress-stats"; +import { Avatar } from "components/ui"; +// icons +import User from "public/user.png"; +// types +import { IIssue, IIssueLabels } from "types"; +// constants +import { PROJECT_ISSUE_LABELS, PROJECT_MEMBERS } from "constants/fetch-keys"; +// services +import issuesServices from "services/issues.service"; +import projectService from "services/project.service"; + +type Props = { + groupedIssues: any; + issues: IIssue[]; +}; + +const stateGroupColours: { + [key: string]: string; +} = { + backlog: "#3f76ff", + unstarted: "#ff9e9e", + started: "#d687ff", + cancelled: "#ff5353", + completed: "#096e8d", +}; + +const SidebarProgressStats: React.FC = ({ groupedIssues, issues }) => { + const router = useRouter(); + const { workspaceSlug, projectId } = router.query; + const { data: issueLabels } = useSWR( + workspaceSlug && projectId ? PROJECT_ISSUE_LABELS(projectId as string) : null, + workspaceSlug && projectId + ? () => issuesServices.getIssueLabels(workspaceSlug as string, projectId as string) + : null + ); + + const { data: members } = useSWR( + workspaceSlug && projectId ? PROJECT_MEMBERS(workspaceSlug as string) : null, + workspaceSlug && projectId + ? () => projectService.projectMembers(workspaceSlug as string, projectId as string) + : null + ); + return ( +
+ + + + `w-1/2 rounded py-1 ${selected ? "bg-gray-300" : "hover:bg-gray-200"}` + } + > + Assignees + + + `w-1/2 rounded py-1 ${selected ? "bg-gray-300 font-semibold" : "hover:bg-gray-200 "}` + } + > + Labels + + + `w-1/2 rounded py-1 ${selected ? "bg-gray-300 font-semibold" : "hover:bg-gray-200 "}` + } + > + States + + + + + {members?.map((member, index) => { + const totalArray = issues?.filter((i) => i.assignees?.includes(member.member.id)); + const completeArray = totalArray?.filter((i) => i.state_detail.group === "completed"); + if (totalArray.length > 0) { + return ( + + + {member.member.first_name} + + } + completed={completeArray.length} + total={totalArray.length} + /> + ); + } + })} + {issues?.filter((i) => i.assignees?.length === 0).length > 0 ? ( + +
+ User +
+ No assignee + + } + completed={ + issues?.filter( + (i) => i.state_detail.group === "completed" && i.assignees?.length === 0 + ).length + } + total={issues?.filter((i) => i.assignees?.length === 0).length} + /> + ) : ( + "" + )} +
+ + {issueLabels?.map((issue, index) => { + const totalArray = issues?.filter((i) => i.labels?.includes(issue.id)); + const completeArray = totalArray?.filter((i) => i.state_detail.group === "completed"); + if (totalArray.length > 0) { + return ( + + + {issue.name} + + } + completed={completeArray.length} + total={totalArray.length} + /> + ); + } + })} + + + {Object.keys(groupedIssues).map((group, index) => ( + + + {group} + + } + completed={groupedIssues[group].length} + total={issues.length} + /> + ))} + +
+
+
+ ); +}; + +export default SidebarProgressStats; diff --git a/apps/app/components/core/sidebar/single-progress-stats.tsx b/apps/app/components/core/sidebar/single-progress-stats.tsx new file mode 100644 index 000000000..885aed23e --- /dev/null +++ b/apps/app/components/core/sidebar/single-progress-stats.tsx @@ -0,0 +1,29 @@ +import React from "react"; + +import { CircularProgressbar } from "react-circular-progressbar"; + +type TSingleProgressStatsProps = { + title: any; + completed: number; + total: number; +}; + +const SingleProgressStats: React.FC = ({ title, completed, total }) => ( + <> +
+
{title}
+
+
+ + + + {Math.floor((completed / total) * 100)}% +
+ of + {total} +
+
+ +); + +export default SingleProgressStats; diff --git a/apps/app/components/modules/sidebar.tsx b/apps/app/components/modules/sidebar.tsx index 57c9e765b..562593384 100644 --- a/apps/app/components/modules/sidebar.tsx +++ b/apps/app/components/modules/sidebar.tsx @@ -7,6 +7,16 @@ import { mutate } from "swr"; // react-hook-form import { Controller, useForm } from "react-hook-form"; +// icons +import { + CalendarDaysIcon, + ChartPieIcon, + LinkIcon, + PlusIcon, + TrashIcon, +} from "@heroicons/react/24/outline"; +// progress-bar +import { CircularProgressbar } from "react-circular-progressbar"; // services import modulesService from "services/modules.service"; // hooks @@ -18,27 +28,19 @@ import { SidebarMembersSelect, SidebarStatusSelect, } from "components/modules"; -// progress-bar -import { CircularProgressbar } from "react-circular-progressbar"; + import "react-circular-progressbar/dist/styles.css"; // ui import { CustomDatePicker, Loader } from "components/ui"; -// icons -import { - CalendarDaysIcon, - ChartPieIcon, - LinkIcon, - PlusIcon, - TrashIcon, -} from "@heroicons/react/24/outline"; // helpers import { timeAgo } from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; import { groupBy } from "helpers/array.helper"; // types -import { IModule, ModuleIssueResponse } from "types"; +import { IIssue, IModule, ModuleIssueResponse } from "types"; // fetch-keys import { MODULE_DETAILS } from "constants/fetch-keys"; +import SidebarProgressStats from "components/core/sidebar/sidebar-progress-stats"; const defaultValues: Partial = { lead: "", @@ -49,6 +51,7 @@ const defaultValues: Partial = { }; type Props = { + issues: IIssue[]; module?: IModule; isOpen: boolean; moduleIssues: ModuleIssueResponse[] | undefined; @@ -56,6 +59,7 @@ type Props = { }; export const ModuleDetailsSidebar: React.FC = ({ + issues, module, isOpen, moduleIssues, @@ -290,6 +294,9 @@ export const ModuleDetailsSidebar: React.FC = ({ +
+ +
) : ( diff --git a/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx b/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx index a98a2b433..436345714 100644 --- a/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx +++ b/apps/app/components/project/cycles/cycle-detail-sidebar/index.tsx @@ -9,24 +9,27 @@ import { mutate } from "swr"; import { Controller, useForm } from "react-hook-form"; // icons import { CalendarDaysIcon, ChartPieIcon, LinkIcon, UserIcon } from "@heroicons/react/24/outline"; -// services -import cyclesService from "services/cycles.service"; -// hooks -import useToast from "hooks/use-toast"; -// ui -import { Loader, CustomDatePicker } from "components/ui"; + // progress-bar import { CircularProgressbar } from "react-circular-progressbar"; +// ui +import { Loader, CustomDatePicker } from "components/ui"; import "react-circular-progressbar/dist/styles.css"; +// hooks +import useToast from "hooks/use-toast"; +// services +import cyclesService from "services/cycles.service"; // helpers import { copyTextToClipboard } from "helpers/string.helper"; import { groupBy } from "helpers/array.helper"; // types -import { CycleIssueResponse, ICycle } from "types"; +import { CycleIssueResponse, ICycle, IIssue } from "types"; // fetch-keys import { CYCLE_DETAILS } from "constants/fetch-keys"; +import SidebarProgressStats from "components/core/sidebar/sidebar-progress-stats"; type Props = { + issues: IIssue[]; cycle: ICycle | undefined; isOpen: boolean; cycleIssues: CycleIssueResponse[]; @@ -37,7 +40,7 @@ const defaultValues: Partial = { end_date: new Date().toString(), }; -const CycleDetailSidebar: React.FC = ({ cycle, isOpen, cycleIssues }) => { +const CycleDetailSidebar: React.FC = ({ issues, cycle, isOpen, cycleIssues }) => { const router = useRouter(); const { workspaceSlug, projectId, cycleId } = router.query; @@ -219,6 +222,9 @@ const CycleDetailSidebar: React.FC = ({ cycle, isOpen, cycleIssues }) =>
+
+ +
) : ( diff --git a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index a3f8acb8f..8a37d840e 100644 --- a/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/apps/app/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -3,7 +3,10 @@ import React, { useState } from "react"; import { useRouter } from "next/router"; import useSWR, { mutate } from "swr"; - +import { NextPageContext } from "next"; +// icons +import { ArrowLeftIcon, ListBulletIcon, PlusIcon } from "@heroicons/react/24/outline"; +import { CyclesIcon } from "components/icons"; // lib import { requiredAdmin, requiredAuth } from "lib/auth"; // layouts @@ -21,19 +24,17 @@ import projectService from "services/project.service"; // ui import { CustomMenu, EmptySpace, EmptySpaceItem, Spinner } from "components/ui"; import { BreadcrumbItem, Breadcrumbs } from "components/breadcrumbs"; -// icons -import { ArrowLeftIcon, ListBulletIcon, PlusIcon } from "@heroicons/react/24/outline"; -import { CyclesIcon } from "components/icons"; // types -import { CycleIssueResponse, IIssue, SelectIssue, UserAuth } from "types"; -import { NextPageContext } from "next"; +import { CycleIssueResponse, IIssue, IIssueLabels, SelectIssue, UserAuth } from "types"; // fetch-keys import { CYCLE_ISSUES, CYCLE_LIST, PROJECT_ISSUES_LIST, PROJECT_DETAILS, + PROJECT_ISSUE_LABELS, CYCLE_DETAILS, + PROJECT_MEMBERS, } from "constants/fetch-keys"; const SingleCycle: React.FC = (props) => { @@ -95,6 +96,7 @@ const SingleCycle: React.FC = (props) => { ) : null ); + const cycleIssuesArray = cycleIssues?.map((issue) => ({ ...issue.issue_detail, sub_issues_count: issue.sub_issues_count, @@ -241,6 +243,7 @@ const SingleCycle: React.FC = (props) => { )} = (props) => { )}