From 83d10e4f2de986febe26aa86bfb7f261e0705cbd Mon Sep 17 00:00:00 2001 From: sriram veeraghanta Date: Sun, 14 Jan 2024 16:16:06 +0530 Subject: [PATCH] fix: active cycles tab implementation --- web/components/cycles/active-cycle-info.tsx | 304 ++++++++++++++++++++ web/components/cycles/index.ts | 1 + web/components/workspace/sidebar-menu.tsx | 5 + web/pages/[workspaceSlug]/active-cycles.tsx | 41 +++ web/services/cycle.service.ts | 8 + 5 files changed, 359 insertions(+) create mode 100644 web/components/cycles/active-cycle-info.tsx create mode 100644 web/pages/[workspaceSlug]/active-cycles.tsx diff --git a/web/components/cycles/active-cycle-info.tsx b/web/components/cycles/active-cycle-info.tsx new file mode 100644 index 000000000..0142add5a --- /dev/null +++ b/web/components/cycles/active-cycle-info.tsx @@ -0,0 +1,304 @@ +import { FC } from "react"; +import Link from "next/link"; +// ui +import { + AvatarGroup, + Loader, + Tooltip, + LinearProgressIndicator, + ContrastIcon, + RunningIcon, + LayersIcon, + StateGroupIcon, + PriorityIcon, + Avatar, +} from "@plane/ui"; +// icons +import { AlarmClock, AlertTriangle, ArrowRight, CalendarDays, Star, Target } from "lucide-react"; +// types +import { ICycle } from "@plane/types"; +// helpers +import { renderFormattedDate, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { truncateText } from "helpers/string.helper"; +// components +import { SingleProgressStats } from "components/core"; +import ProgressChart from "components/core/sidebar/progress-chart"; + +export type ActiveCycleInfoProps = { + cycle: ICycle; + workspaceSlug: string; + projectId: string; +}; + +export const ActiveCycleInfo: FC = (props) => { + const { cycle, workspaceSlug, projectId } = props; + + return ( +
+
+
+
+
+
+ + + + + +

{truncateText(cycle.name, 70)}

+
+
+ + + + + {findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left + + + {cycle.is_favorite ? ( + + ) : ( + + )} + +
+ +
+
+ + {cycle?.start_date && {renderFormattedDate(cycle?.start_date)}} +
+ +
+ + {cycle?.end_date && {renderFormattedDate(cycle?.end_date)}} +
+
+ +
+
+ {cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( + {cycle.owned_by.display_name} + ) : ( + + {cycle.owned_by.display_name.charAt(0)} + + )} + {cycle.owned_by.display_name} +
+ + {cycle.assignees.length > 0 && ( +
+ + {cycle.assignees.map((assignee: any) => ( + + ))} + +
+ )} +
+ +
+
+ + {cycle.total_issues} issues +
+
+ + {cycle.completed_issues} issues +
+
+ + + + View Cycle + + +
+
+
+
+
+
+
+ Progress + {/* */} +
+
+ {/* {Object.keys(groupedIssues).map((group, index) => ( + + + {group} +
+ } + completed={groupedIssues[group]} + total={activeCycle.total_issues} + /> + ))} */} +
+
+
+
+ {/* */} +
+
+
+
+
+
+
High Priority Issues
+
+ {/* {issueIds ? ( + issueIds.length > 0 ? ( + issueIds.map((issue: any) => ( + +
+
+ + + {issue.project_detail?.identifier}-{issue.sequence_id} + + +
+ + {truncateText(issue.name, 30)} + +
+
+
+ +
+ +
+ {issue.assignees && issue.assignees.length > 0 && Array.isArray(issue.assignees) ? ( +
+ + {issue.assignee_details.map((assignee: any) => ( + + ))} + +
+ ) : ( + "" + )} +
+
+ + )) + ) : ( +
+ No issues present in the cycle. +
+ ) + ) : ( + + + + + + )} */} +
+
+ + {/* {issueIds && issueIds.length > 0 && ( +
+
+
issue?.state_detail?.group === "completed")?.length / + issueIds.length) * + 100 ?? 0 + }%`, + }} + /> +
+
+ of{" "} + { + issueIds?.filter( + (issueId) => + getProjectStates(issueMap[issueId]?.project_id)?.find( + (issue) => issue.id === issueMap[issueId]?.state_id + )?.group === "completed" + )?.length + }{" "} + of {issueIds?.length} +
+
+ )} */} +
+
+
+
+
+ + Ideal +
+
+ + Current +
+
+
+ + + + Pending Issues - {cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)} +
+
+
+ +
+
+
+
+ ); +}; diff --git a/web/components/cycles/index.ts b/web/components/cycles/index.ts index db5e9de9e..975a03188 100644 --- a/web/components/cycles/index.ts +++ b/web/components/cycles/index.ts @@ -15,3 +15,4 @@ export * from "./cycles-board-card"; export * from "./delete-modal"; export * from "./cycle-peek-overview"; export * from "./cycles-list-item"; +export * from "./active-cycle-info"; diff --git a/web/components/workspace/sidebar-menu.tsx b/web/components/workspace/sidebar-menu.tsx index 98f1f880d..1a71b6616 100644 --- a/web/components/workspace/sidebar-menu.tsx +++ b/web/components/workspace/sidebar-menu.tsx @@ -33,6 +33,11 @@ const workspaceLinks = (workspaceSlug: string) => [ name: "All Issues", href: `/${workspaceSlug}/workspace-views/all-issues`, }, + { + Icon: CheckCircle, + name: "Active Cycles", + href: `/${workspaceSlug}/active-cycles`, + }, ]; export const WorkspaceSidebarMenu = observer(() => { diff --git a/web/pages/[workspaceSlug]/active-cycles.tsx b/web/pages/[workspaceSlug]/active-cycles.tsx new file mode 100644 index 000000000..5880ff57b --- /dev/null +++ b/web/pages/[workspaceSlug]/active-cycles.tsx @@ -0,0 +1,41 @@ +import { ReactElement } from "react"; +import { observer } from "mobx-react-lite"; +import { useRouter } from "next/router"; +import useSWR from "swr"; +// components +import { ActiveCycleDetails, ActiveCycleInfo } from "components/cycles"; +// services +import { CycleService } from "services/cycle.service"; +const cycleService = new CycleService(); +// layouts +import { AppLayout } from "layouts/app-layout"; +// types +import { NextPageWithLayout } from "lib/types"; + +const WorkspaceActiveCyclesPage: NextPageWithLayout = observer(() => { + const router = useRouter(); + const { workspaceSlug } = router.query; + // fetching active cycles in workspace + const { data } = useSWR("WORKSPACE_ACTIVE_CYCLES", () => cycleService.workspaceActiveCycles(workspaceSlug as string)); + + console.log(data); + + return ( +
+ {data && + workspaceSlug && + data.map((cycle) => ( +
+ {cycle.name} + +
+ ))} +
+ ); +}); + +WorkspaceActiveCyclesPage.getLayout = function getLayout(page: ReactElement) { + return }>{page}; +}; + +export default WorkspaceActiveCyclesPage; diff --git a/web/services/cycle.service.ts b/web/services/cycle.service.ts index 6b6d17231..7c22f34a6 100644 --- a/web/services/cycle.service.ts +++ b/web/services/cycle.service.ts @@ -10,6 +10,14 @@ export class CycleService extends APIService { super(API_BASE_URL); } + async workspaceActiveCycles(workspaceSlug: string): Promise { + return this.get(`/api/workspaces/${workspaceSlug}/active-cycles/`) + .then((res) => res?.data) + .catch((err) => { + throw err?.response?.data; + }); + } + async createCycle(workspaceSlug: string, projectId: string, data: any): Promise { return this.post(`/api/workspaces/${workspaceSlug}/projects/${projectId}/cycles/`, data) .then((response) => response?.data)