From 8eaac60aa5c2913e0cdddf046cb65cbea762868c Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Mon, 30 Oct 2023 19:22:27 +0530 Subject: [PATCH] style: cycle ui revamp, and chore: code refactor (#2558) * chore: cycle custom svg icon added and code refactor * chore: module code refactor * style: cycle ui revamp and code refactor * chore: cycle card view layout fix * chore: layout fix * style: module and cycle title tooltip position --- .../src/icons/cycle/circle-dot-full-icon.tsx | 20 + .../src/icons/{ => cycle}/contrast-icon.tsx | 2 +- .../ui/src/icons/cycle/cycle-group-icon.tsx | 33 ++ .../icons/{ => cycle}/double-circle-icon.tsx | 2 +- packages/ui/src/icons/cycle/helper.tsx | 18 + packages/ui/src/icons/cycle/index.ts | 5 + packages/ui/src/icons/index.tsx | 3 +- .../core/sidebar/sidebar-progress-stats.tsx | 8 +- web/components/cycles/cycle-peek-overview.tsx | 55 ++ web/components/cycles/cycles-board-card.tsx | 483 ++++++--------- web/components/cycles/cycles-board.tsx | 33 +- web/components/cycles/cycles-list-item.tsx | 431 ++++++-------- web/components/cycles/cycles-list.tsx | 23 +- web/components/cycles/cycles-view.tsx | 11 +- web/components/cycles/index.ts | 2 + web/components/cycles/sidebar.tsx | 556 ++++++++---------- web/components/modules/module-card-item.tsx | 170 +++--- web/components/modules/module-list-item.tsx | 155 +++-- web/components/modules/sidebar.tsx | 2 +- web/constants/cycle.tsx | 37 ++ .../projects/[projectId]/cycles/[cycleId].tsx | 22 +- .../projects/[projectId]/cycles/index.tsx | 18 +- yarn.lock | 4 +- 23 files changed, 986 insertions(+), 1107 deletions(-) create mode 100644 packages/ui/src/icons/cycle/circle-dot-full-icon.tsx rename packages/ui/src/icons/{ => cycle}/contrast-icon.tsx (95%) create mode 100644 packages/ui/src/icons/cycle/cycle-group-icon.tsx rename packages/ui/src/icons/{ => cycle}/double-circle-icon.tsx (91%) create mode 100644 packages/ui/src/icons/cycle/helper.tsx create mode 100644 packages/ui/src/icons/cycle/index.ts create mode 100644 web/components/cycles/cycle-peek-overview.tsx diff --git a/packages/ui/src/icons/cycle/circle-dot-full-icon.tsx b/packages/ui/src/icons/cycle/circle-dot-full-icon.tsx new file mode 100644 index 000000000..0a2c46e99 --- /dev/null +++ b/packages/ui/src/icons/cycle/circle-dot-full-icon.tsx @@ -0,0 +1,20 @@ +import * as React from "react"; + +import { ISvgIcons } from "../type"; + +export const CircleDotFullIcon: React.FC = ({ + className = "text-current", + ...rest +}) => ( + + + + +); diff --git a/packages/ui/src/icons/contrast-icon.tsx b/packages/ui/src/icons/cycle/contrast-icon.tsx similarity index 95% rename from packages/ui/src/icons/contrast-icon.tsx rename to packages/ui/src/icons/cycle/contrast-icon.tsx index 99316dbe0..7b51fd1e7 100644 --- a/packages/ui/src/icons/contrast-icon.tsx +++ b/packages/ui/src/icons/cycle/contrast-icon.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { ISvgIcons } from "./type"; +import { ISvgIcons } from "../type"; export const ContrastIcon: React.FC = ({ className = "text-current", diff --git a/packages/ui/src/icons/cycle/cycle-group-icon.tsx b/packages/ui/src/icons/cycle/cycle-group-icon.tsx new file mode 100644 index 000000000..731d90702 --- /dev/null +++ b/packages/ui/src/icons/cycle/cycle-group-icon.tsx @@ -0,0 +1,33 @@ +import * as React from "react"; + +import { ContrastIcon } from "./contrast-icon"; +import { CircleDotFullIcon } from "./circle-dot-full-icon"; +import { CircleDotDashed, Circle } from "lucide-react"; + +import { CYCLE_GROUP_COLORS, ICycleGroupIcon } from "./helper"; + +const iconComponents = { + current: ContrastIcon, + upcoming: CircleDotDashed, + completed: CircleDotFullIcon, + draft: Circle, +}; + +export const CycleGroupIcon: React.FC = ({ + className = "", + color, + cycleGroup, + height = "12px", + width = "12px", +}) => { + const CycleIconComponent = iconComponents[cycleGroup] || ContrastIcon; + + return ( + + ); +}; diff --git a/packages/ui/src/icons/double-circle-icon.tsx b/packages/ui/src/icons/cycle/double-circle-icon.tsx similarity index 91% rename from packages/ui/src/icons/double-circle-icon.tsx rename to packages/ui/src/icons/cycle/double-circle-icon.tsx index b5ced3f8a..a191b71a6 100644 --- a/packages/ui/src/icons/double-circle-icon.tsx +++ b/packages/ui/src/icons/cycle/double-circle-icon.tsx @@ -1,6 +1,6 @@ import * as React from "react"; -import { ISvgIcons } from "./type"; +import { ISvgIcons } from "../type"; export const DoubleCircleIcon: React.FC = ({ className = "text-current", diff --git a/packages/ui/src/icons/cycle/helper.tsx b/packages/ui/src/icons/cycle/helper.tsx new file mode 100644 index 000000000..ec91cc2c2 --- /dev/null +++ b/packages/ui/src/icons/cycle/helper.tsx @@ -0,0 +1,18 @@ +export interface ICycleGroupIcon { + className?: string; + color?: string; + cycleGroup: TCycleGroups; + height?: string; + width?: string; +} + +export type TCycleGroups = "current" | "upcoming" | "completed" | "draft"; + +export const CYCLE_GROUP_COLORS: { + [key in TCycleGroups]: string; +} = { + current: "#F59E0B", + upcoming: "#3F76FF", + completed: "#16A34A", + draft: "#525252", +}; diff --git a/packages/ui/src/icons/cycle/index.ts b/packages/ui/src/icons/cycle/index.ts new file mode 100644 index 000000000..e74c8ff8c --- /dev/null +++ b/packages/ui/src/icons/cycle/index.ts @@ -0,0 +1,5 @@ +export * from "./double-circle-icon"; +export * from "./circle-dot-full-icon"; +export * from "./contrast-icon"; +export * from "./circle-dot-full-icon"; +export * from "./cycle-group-icon"; diff --git a/packages/ui/src/icons/index.tsx b/packages/ui/src/icons/index.tsx index 2e98e7138..518a4bfad 100644 --- a/packages/ui/src/icons/index.tsx +++ b/packages/ui/src/icons/index.tsx @@ -1,5 +1,4 @@ export * from "./user-group-icon"; -export * from "./contrast-icon"; export * from "./dice-icon"; export * from "./layers-icon"; export * from "./photo-filter-icon"; @@ -7,7 +6,6 @@ export * from "./archive-icon"; export * from "./admin-profile-icon"; export * from "./create-icon"; export * from "./subscribe-icon"; -export * from "./double-circle-icon"; export * from "./external-link-icon"; export * from "./copy-icon"; export * from "./layer-stack"; @@ -20,6 +18,7 @@ export * from "./blocked-icon"; export * from "./blocker-icon"; export * from "./related-icon"; export * from "./module"; +export * from "./cycle"; export * from "./github-icon"; export * from "./discord-icon"; export * from "./transfer-icon"; diff --git a/web/components/core/sidebar/sidebar-progress-stats.tsx b/web/components/core/sidebar/sidebar-progress-stats.tsx index bb6690172..706448492 100644 --- a/web/components/core/sidebar/sidebar-progress-stats.tsx +++ b/web/components/core/sidebar/sidebar-progress-stats.tsx @@ -36,7 +36,7 @@ type Props = { module?: IModule; roundedTab?: boolean; noBackground?: boolean; - isPeekModuleDetails?: boolean; + isPeekView?: boolean; }; export const SidebarProgressStats: React.FC = ({ @@ -46,7 +46,7 @@ export const SidebarProgressStats: React.FC = ({ module, roundedTab, noBackground, - isPeekModuleDetails = false, + isPeekView = false, }) => { const { filters, setFilters } = useIssuesView(); @@ -154,7 +154,7 @@ export const SidebarProgressStats: React.FC = ({ } completed={assignee.completed_issues} total={assignee.total_issues} - {...(!isPeekModuleDetails && { + {...(!isPeekView && { onClick: () => { if (filters?.assignees?.includes(assignee.assignee_id ?? "")) setFilters({ @@ -213,7 +213,7 @@ export const SidebarProgressStats: React.FC = ({ } completed={label.completed_issues} total={label.total_issues} - {...(!isPeekModuleDetails && { + {...(!isPeekView && { onClick: () => { if (filters.labels?.includes(label.label_id ?? "")) setFilters({ diff --git a/web/components/cycles/cycle-peek-overview.tsx b/web/components/cycles/cycle-peek-overview.tsx new file mode 100644 index 000000000..fb30150ca --- /dev/null +++ b/web/components/cycles/cycle-peek-overview.tsx @@ -0,0 +1,55 @@ +import React, { useEffect } from "react"; + +import { useRouter } from "next/router"; + +// mobx +import { observer } from "mobx-react-lite"; +import { useMobxStore } from "lib/mobx/store-provider"; +// components +import { CycleDetailsSidebar } from "./sidebar"; + +type Props = { + projectId: string; + workspaceSlug: string; +}; + +export const CyclePeekOverview: React.FC = observer(({ projectId, workspaceSlug }) => { + const router = useRouter(); + const { peekCycle } = router.query; + + const ref = React.useRef(null); + + const { cycle: cycleStore } = useMobxStore(); + + const { fetchCycleWithId } = cycleStore; + + const handleClose = () => { + delete router.query.peekCycle; + router.push({ + pathname: router.pathname, + query: { ...router.query }, + }); + }; + + useEffect(() => { + if (!peekCycle) return; + fetchCycleWithId(workspaceSlug, projectId, peekCycle.toString()); + }, [fetchCycleWithId, peekCycle, projectId, workspaceSlug]); + + return ( + <> + {peekCycle && ( +
+ +
+ )} + + ); +}); diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index f2f921365..cbcfe2b55 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -1,64 +1,32 @@ import { FC, MouseEvent, useState } from "react"; + +import { useRouter } from "next/router"; + // next imports import Link from "next/link"; -// headless ui -import { Disclosure, Transition } from "@headlessui/react"; // hooks import useToast from "hooks/use-toast"; // components -import { SingleProgressStats } from "components/core"; import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles"; // ui import { AssigneesList } from "components/ui/avatar"; -import { CustomMenu, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui"; +import { CustomMenu, Tooltip, LayersIcon, CycleGroupIcon } from "@plane/ui"; // icons -import { - AlarmClock, - AlertTriangle, - ArrowRight, - CalendarDays, - ChevronDown, - LinkIcon, - Pencil, - Star, - Target, - Trash2, -} from "lucide-react"; +import { Info, LinkIcon, Pencil, Star, Trash2 } from "lucide-react"; // helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; -import { copyTextToClipboard, truncateText } from "helpers/string.helper"; +import { + getDateRangeStatus, + findHowManyDaysLeft, + renderShortDate, + renderShortMonthDate, +} from "helpers/date-time.helper"; +import { copyTextToClipboard } from "helpers/string.helper"; // types import { ICycle } from "types"; // store import { useMobxStore } from "lib/mobx/store-provider"; - -const stateGroups = [ - { - key: "backlog_issues", - title: "Backlog", - color: "#dee2e6", - }, - { - key: "unstarted_issues", - title: "Unstarted", - color: "#26b5ce", - }, - { - key: "started_issues", - title: "Started", - color: "#f7ae59", - }, - { - key: "cancelled_issues", - title: "Cancelled", - color: "#d687ff", - }, - { - key: "completed_issues", - title: "Completed", - color: "#09a953", - }, -]; +// constants +import { CYCLE_STATUS } from "constants/cycle"; export interface ICyclesBoardCard { workspaceSlug: string; @@ -81,7 +49,34 @@ export const CyclesBoardCard: FC = (props) => { const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); - const handleCopyText = () => { + const router = useRouter(); + + const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus); + + const areYearsEqual = startDate.getFullYear() === endDate.getFullYear(); + + const cycleTotalIssues = + cycle.backlog_issues + + cycle.unstarted_issues + + cycle.started_issues + + cycle.completed_issues + + cycle.cancelled_issues; + + const completionPercentage = (cycle.completed_issues / cycleTotalIssues) * 100; + + const issueCount = cycle + ? cycleTotalIssues === 0 + ? "0 Issue" + : cycleTotalIssues === cycle.completed_issues + ? cycleTotalIssues > 1 + ? `${cycleTotalIssues} Issues` + : `${cycleTotalIssues} Issue` + : `${cycle.completed_issues}/${cycleTotalIssues} Issues` + : "0 Issue"; + + const handleCopyText = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`).then(() => { @@ -93,21 +88,6 @@ export const CyclesBoardCard: FC = (props) => { }); }; - const progressIndicatorData = stateGroups.map((group, index) => ({ - id: index, - name: group.title, - value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0, - color: group.color, - })); - - const groupedIssues: any = { - backlog: cycle.backlog_issues, - unstarted: cycle.unstarted_issues, - started: cycle.started_issues, - completed: cycle.completed_issues, - cancelled: cycle.cancelled_issues, - }; - const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; @@ -134,6 +114,29 @@ export const CyclesBoardCard: FC = (props) => { }); }; + const handleEditCycle = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setUpdateModal(true); + }; + + const handleDeleteCycle = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDeleteModal(true); + }; + + const openCycleOverview = (e: MouseEvent) => { + const { query } = router; + e.preventDefault(); + e.stopPropagation(); + + router.push({ + pathname: router.pathname, + query: { ...query, peekCycle: cycle.id }, + }); + }; + return (
= (props) => { projectId={projectId} /> -
- - -
-
- - - - - -

{truncateText(cycle.name, 15)}

-
+ +
+
+
+
+ + - + + {cycle.name} + +
+
+ {currentCycle && ( - {cycleStatus === "current" ? ( - - - {findHowManyDaysLeft(cycle.end_date ?? new Date())} Days Left - - ) : cycleStatus === "upcoming" ? ( - - - {findHowManyDaysLeft(cycle.start_date ?? new Date())} Days Left - - ) : cycleStatus === "completed" ? ( - - {cycle.total_issues - cycle.completed_issues > 0 && ( - - - - - - )}{" "} - Completed - - ) : ( - cycleStatus - )} + {currentCycle.value === "current" + ? `${findHowManyDaysLeft(cycle.end_date ?? new Date())} ${currentCycle.label}` + : `${currentCycle.label}`} - {cycle.is_favorite ? ( - - ) : ( - - )} - -
-
- {cycleStatus !== "draft" && ( - <> -
- - {renderShortDateWithYearFormat(startDate)} -
- -
- - {renderShortDateWithYearFormat(endDate)} -
- )} -
- -
-
-
-
Creator:
-
- {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} -
-
-
-
Members:
- {cycle.assignees.length > 0 ? ( -
- -
- ) : ( - "No members" - )} -
-
- -
- {!isCompleted && ( - - )} - - - {!isCompleted && ( - { - e.preventDefault(); - setDeleteModal(true); - }} - > - - - Delete cycle - - - )} - { - e.preventDefault(); - handleCopyText(); - }} - > - - - Copy cycle link - - - -
+
-
- +
-
- - {({ open }) => ( -
-
- Progress - - {Object.keys(groupedIssues).map((group, index) => ( - - - {group} -
- } - completed={groupedIssues[group]} - total={cycle.total_issues} - /> - ))} -
- } - position="bottom" - > -
- -
- - - - - -
- - -
-
-
- {stateGroups.map((group) => ( -
-
- -
{group.title}
-
-
- - {cycle[group.key as keyof ICycle] as number}{" "} - - -{" "} - {cycle.total_issues > 0 - ? `${Math.round( - ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 - )}%` - : "0%"} - - -
-
- ))} -
-
-
-
-
+
+
+
+ + {issueCount}
- )} - -
-
+ {cycle.assignees.length > 0 && ( + +
+ +
+
+ )} +
+ + +
+
+
+
+
+ + +
+ + {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} -{" "} + {areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")} + +
+ {cycle.is_favorite ? ( + + ) : ( + + )} + + {!isCompleted && ( + <> + + + + Edit cycle + + + + + + Delete module + + + + )} + + + + Copy cycle link + + + +
+
+
+ +
); }; diff --git a/web/components/cycles/cycles-board.tsx b/web/components/cycles/cycles-board.tsx index 3eca2e9b9..105d16128 100644 --- a/web/components/cycles/cycles-board.tsx +++ b/web/components/cycles/cycles-board.tsx @@ -2,26 +2,41 @@ import { FC } from "react"; // types import { ICycle } from "types"; // components -import { CyclesBoardCard } from "components/cycles"; +import { CyclePeekOverview, CyclesBoardCard } from "components/cycles"; export interface ICyclesBoard { cycles: ICycle[]; filter: string; workspaceSlug: string; projectId: string; + peekCycle: string; } export const CyclesBoard: FC = (props) => { - const { cycles, filter, workspaceSlug, projectId } = props; + const { cycles, filter, workspaceSlug, projectId, peekCycle } = props; return ( -
+ <> {cycles.length > 0 ? ( - <> - {cycles.map((cycle) => ( - - ))} - +
+
+
+ {cycles.map((cycle) => ( + + ))} +
+ +
+
) : (
@@ -50,6 +65,6 @@ export const CyclesBoard: FC = (props) => {
)} -
+ ); }; diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index 9b381a03d..96efceddb 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -1,29 +1,30 @@ import { FC, MouseEvent, useState } from "react"; import Link from "next/link"; +import { useRouter } from "next/router"; + +// stores +import { useMobxStore } from "lib/mobx/store-provider"; // hooks import useToast from "hooks/use-toast"; // components import { CycleCreateUpdateModal, CycleDeleteModal } from "components/cycles"; +import { AssigneesList } from "components/ui"; // ui -import { CustomMenu, RadialProgressBar, Tooltip, LinearProgressIndicator, ContrastIcon, RunningIcon } from "@plane/ui"; +import { CustomMenu, Tooltip, CircularProgressIndicator, CycleGroupIcon } from "@plane/ui"; // icons -import { - AlarmClock, - AlertTriangle, - ArrowRight, - CalendarDays, - LinkIcon, - Pencil, - Star, - Target, - Trash2, -} from "lucide-react"; +import { Check, Info, LinkIcon, Pencil, Star, Trash2, User2 } from "lucide-react"; // helpers -import { getDateRangeStatus, renderShortDateWithYearFormat, findHowManyDaysLeft } from "helpers/date-time.helper"; +import { + getDateRangeStatus, + findHowManyDaysLeft, + renderShortDate, + renderShortMonthDate, +} from "helpers/date-time.helper"; import { copyTextToClipboard } from "helpers/string.helper"; // types import { ICycle } from "types"; -import { useMobxStore } from "lib/mobx/store-provider"; +// constants +import { CYCLE_STATUS } from "constants/cycle"; type TCyclesListItem = { cycle: ICycle; @@ -35,34 +36,6 @@ type TCyclesListItem = { projectId: string; }; -const stateGroups = [ - { - key: "backlog_issues", - title: "Backlog", - color: "#dee2e6", - }, - { - key: "unstarted_issues", - title: "Unstarted", - color: "#26b5ce", - }, - { - key: "started_issues", - title: "Started", - color: "#f7ae59", - }, - { - key: "cancelled_issues", - title: "Cancelled", - color: "#d687ff", - }, - { - key: "completed_issues", - title: "Completed", - color: "#09a953", - }, -]; - export const CyclesListItem: FC = (props) => { const { cycle, workspaceSlug, projectId } = props; // store @@ -78,7 +51,28 @@ export const CyclesListItem: FC = (props) => { const endDate = new Date(cycle.end_date ?? ""); const startDate = new Date(cycle.start_date ?? ""); - const handleCopyText = () => { + const router = useRouter(); + + const cycleTotalIssues = + cycle.backlog_issues + + cycle.unstarted_issues + + cycle.started_issues + + cycle.completed_issues + + cycle.cancelled_issues; + + const renderDate = cycle.start_date || cycle.end_date; + + const areYearsEqual = startDate.getFullYear() === endDate.getFullYear(); + + const completionPercentage = (cycle.completed_issues / cycleTotalIssues) * 100; + + const progress = isNaN(completionPercentage) ? 0 : Math.floor(completionPercentage); + + const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus); + + const handleCopyText = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle.id}`).then(() => { @@ -90,13 +84,6 @@ export const CyclesListItem: FC = (props) => { }); }; - const progressIndicatorData = stateGroups.map((group, index) => ({ - id: index, - name: group.title, - value: cycle.total_issues > 0 ? ((cycle[group.key as keyof ICycle] as number) / cycle.total_issues) * 100 : 0, - color: group.color, - })); - const handleAddToFavorites = (e: MouseEvent) => { e.preventDefault(); if (!workspaceSlug || !projectId) return; @@ -123,224 +110,31 @@ export const CyclesListItem: FC = (props) => { }); }; + const handleEditCycle = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setUpdateModal(true); + }; + + const handleDeleteCycle = (e: MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDeleteModal(true); + }; + + const openCycleOverview = (e: MouseEvent) => { + const { query } = router; + e.preventDefault(); + e.stopPropagation(); + + router.push({ + pathname: router.pathname, + query: { ...query, peekCycle: cycle.id }, + }); + }; + return ( <> -
-
- - - {/* left content */} -
- {/* cycle state */} -
- -
- - {/* cycle title and description */} -
- -
- {cycle.name} -
-
- {cycle.description && ( -
{cycle.description}
- )} -
-
- - {/* right content */} -
- {/* cycle status */} -
- {cycleStatus === "current" ? ( - - - {findHowManyDaysLeft(cycle.end_date ?? new Date())} days left - - ) : cycleStatus === "upcoming" ? ( - - - {findHowManyDaysLeft(cycle.start_date ?? new Date())} days left - - ) : cycleStatus === "completed" ? ( - - {cycle.total_issues - cycle.completed_issues > 0 && ( - - - - - - )}{" "} - Completed - - ) : ( - cycleStatus - )} -
- - {/* cycle start_date and target_date */} - {cycleStatus !== "draft" && ( -
-
- - {renderShortDateWithYearFormat(startDate)} -
- - - -
- - {renderShortDateWithYearFormat(endDate)} -
-
- )} - - {/* cycle created by */} -
- {cycle.owned_by.avatar && cycle.owned_by.avatar !== "" ? ( - {cycle.owned_by.display_name} - ) : ( - - {cycle.owned_by.display_name.charAt(0)} - - )} -
- - {/* cycle progress */} - - Progress - -
- } - > - - {cycleStatus === "current" ? ( - - {cycle.total_issues > 0 ? ( - <> - - {Math.floor((cycle.completed_issues / cycle.total_issues) * 100)} % - - ) : ( - No issues present - )} - - ) : cycleStatus === "upcoming" ? ( - - Yet to start - - ) : cycleStatus === "completed" ? ( - - - {100} % - - ) : ( - - - {cycleStatus} - - )} - - - - {/* cycle favorite */} - {cycle.is_favorite ? ( - - ) : ( - - )} -
- - -
- -
- - {!isCompleted && ( - setUpdateModal(true)}> - - - Edit Cycle - - - )} - - {!isCompleted && ( - setDeleteModal(true)}> - - - Delete cycle - - - )} - - - - - Copy cycle link - - - -
-
- = (props) => { workspaceSlug={workspaceSlug} projectId={projectId} /> - = (props) => { workspaceSlug={workspaceSlug} projectId={projectId} /> + + +
+
+ + + {isCompleted ? ( + {`!`} + ) : progress === 100 ? ( + + ) : ( + {`${progress}%`} + )} + + + +
+ + + + + {cycle.name} + +
+
+ +
+ +
+
+ {currentCycle && ( + + {currentCycle.value === "current" + ? `${findHowManyDaysLeft(cycle.end_date ?? new Date())} ${currentCycle.label}` + : `${currentCycle.label}`} + + )} +
+ + {renderDate && ( + + {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} + {" - "} + {areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")} + + )} + + +
+ {cycle.assignees.length > 0 ? ( + + ) : ( + + + + )} +
+
+ + {cycle.is_favorite ? ( + + ) : ( + + )} + + + {!isCompleted && ( + <> + + + + Edit cycle + + + + + + Delete module + + + + )} + + + + Copy cycle link + + + +
+
+ ); }; diff --git a/web/components/cycles/cycles-list.tsx b/web/components/cycles/cycles-list.tsx index 947bd1fea..03698f1d8 100644 --- a/web/components/cycles/cycles-list.tsx +++ b/web/components/cycles/cycles-list.tsx @@ -1,6 +1,7 @@ import { FC } from "react"; // components -import { CyclesListItem } from "./cycles-list-item"; +import { CyclePeekOverview, CyclesListItem } from "components/cycles"; + // ui import { Loader } from "@plane/ui"; // types @@ -17,18 +18,22 @@ export const CyclesList: FC = (props) => { const { cycles, filter, workspaceSlug, projectId } = props; return ( -
+ <> {cycles ? ( <> {cycles.length > 0 ? ( -
- {cycles.map((cycle) => ( -
-
+
+
+
+ {cycles.map((cycle) => ( -
+ ))}
- ))} + +
) : (
@@ -68,6 +73,6 @@ export const CyclesList: FC = (props) => { )} -
+ ); }; diff --git a/web/components/cycles/cycles-view.tsx b/web/components/cycles/cycles-view.tsx index 36955398e..f0640dec6 100644 --- a/web/components/cycles/cycles-view.tsx +++ b/web/components/cycles/cycles-view.tsx @@ -15,10 +15,11 @@ export interface ICyclesView { layout: TCycleLayout; workspaceSlug: string; projectId: string; + peekCycle: string; } export const CyclesView: FC = observer((props) => { - const { filter, layout, workspaceSlug, projectId } = props; + const { filter, layout, workspaceSlug, projectId, peekCycle } = props; // store const { cycle: cycleStore } = useMobxStore(); @@ -50,7 +51,13 @@ export const CyclesView: FC = observer((props) => { {layout === "board" && ( <> {!isLoading ? ( - + ) : ( diff --git a/web/components/cycles/index.ts b/web/components/cycles/index.ts index 20bbfb627..6bf801bbe 100644 --- a/web/components/cycles/index.ts +++ b/web/components/cycles/index.ts @@ -17,3 +17,5 @@ export * from "./cycles-board"; export * from "./cycles-board-card"; export * from "./cycles-gantt"; export * from "./delete-modal"; +export * from "./cycle-peek-overview"; +export * from "./cycles-list-item"; diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index d55a261eb..f56ddcf18 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -15,36 +15,29 @@ import { SidebarProgressStats } from "components/core"; import ProgressChart from "components/core/sidebar/progress-chart"; import { CycleDeleteModal } from "components/cycles/delete-modal"; // ui -import { CustomRangeDatePicker } from "components/ui"; -import { CustomMenu, Loader, ProgressBar } from "@plane/ui"; +import { Avatar, CustomRangeDatePicker } from "components/ui"; +import { CustomMenu, Loader, LayersIcon } from "@plane/ui"; // icons -import { - CalendarDays, - ChevronDown, - File, - MoveRight, - LinkIcon, - PieChart, - Trash2, - UserCircle2, - AlertCircle, -} from "lucide-react"; +import { ChevronDown, LinkIcon, Trash2, UserCircle2, AlertCircle, ChevronRight, MoveRight } from "lucide-react"; // helpers -import { capitalizeFirstLetter, copyUrlToClipboard } from "helpers/string.helper"; +import { copyUrlToClipboard } from "helpers/string.helper"; import { + findHowManyDaysLeft, getDateRangeStatus, isDateGreaterThanToday, renderDateFormat, - renderShortDateWithYearFormat, + renderShortDate, + renderShortMonthDate, } from "helpers/date-time.helper"; // types import { ICycle } from "types"; // fetch-keys import { CYCLE_DETAILS } from "constants/fetch-keys"; +import { CYCLE_STATUS } from "constants/cycle"; type Props = { - isOpen: boolean; cycleId: string; + handleClose: () => void; }; // services @@ -52,12 +45,12 @@ const cycleService = new CycleService(); // TODO: refactor the whole component export const CycleDetailsSidebar: React.FC = observer((props) => { - const { isOpen, cycleId } = props; + const { cycleId, handleClose } = props; const [cycleDeleteModal, setCycleDeleteModal] = useState(false); const router = useRouter(); - const { workspaceSlug, projectId } = router.query; + const { workspaceSlug, projectId, peekCycle } = router.query; const { user: userStore, cycle: cycleDetailsStore } = useMobxStore(); @@ -280,6 +273,22 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { if (!cycleDetails) return null; + const endDate = new Date(cycleDetails.end_date ?? ""); + const startDate = new Date(cycleDetails.start_date ?? ""); + + const areYearsEqual = startDate.getFullYear() === endDate.getFullYear(); + + const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus); + + const issueCount = + cycleDetails.total_issues === 0 + ? "0 Issue" + : cycleDetails.total_issues === cycleDetails.completed_issues + ? cycleDetails.total_issues > 1 + ? `${cycleDetails.total_issues}` + : `${cycleDetails.total_issues}` + : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`; + return ( <> {cycleDetails && workspaceSlug && projectId && ( @@ -291,327 +300,266 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { projectId={projectId.toString()} /> )} -
- {cycleDetails ? ( - <> -
-
-
- - {capitalizeFirstLetter(cycleStatus)} - -
-
- - {({}) => ( - <> - - - - {renderShortDateWithYearFormat( - new Date(`${watch("start_date") ? watch("start_date") : cycleDetails?.start_date}`), - "Start date" - )} - - - - - { - if (val) { - handleStartDateChange(val); - } - }} - startDate={watch("start_date") ? `${watch("start_date")}` : null} - endDate={watch("end_date") ? `${watch("end_date")}` : null} - maxDate={new Date(`${watch("end_date")}`)} - selectsStart - /> - - - - )} - - - - - - {({}) => ( - <> - - + {cycleDetails ? ( + <> +
+
+ {peekCycle && ( + + )} +
+
+ + {!isCompleted && ( + + setCycleDeleteModal(true)}> + + + Delete + + + + )} +
+
- - {renderShortDateWithYearFormat( - new Date(`${watch("end_date") ? watch("end_date") : cycleDetails?.end_date}`), - "End date" - )} - -
+
+

{cycleDetails.name}

+
+ {currentCycle && ( + + {currentCycle.value === "current" + ? `${findHowManyDaysLeft(cycleDetails.end_date ?? new Date())} ${currentCycle.label}` + : `${currentCycle.label}`} + + )} +
+ + {({}) => ( + <> + + {areYearsEqual ? renderShortDate(startDate, "_ _") : renderShortMonthDate(startDate, "_ _")} + - - - { - if (val) { - handleEndDateChange(val); - } - }} - startDate={watch("start_date") ? `${watch("start_date")}` : null} - endDate={watch("end_date") ? `${watch("end_date")}` : null} - minDate={new Date(`${watch("start_date")}`)} - selectsEnd - /> - - - - )} - -
+ + + { + if (val) { + handleStartDateChange(val); + } + }} + startDate={watch("start_date") ? `${watch("start_date")}` : null} + endDate={watch("end_date") ? `${watch("end_date")}` : null} + maxDate={new Date(`${watch("end_date")}`)} + selectsStart + /> + + + + )} + + + + {({}) => ( + <> + + {areYearsEqual ? renderShortDate(endDate, "_ _") : renderShortMonthDate(endDate, "_ _")} + + + + + { + if (val) { + handleEndDateChange(val); + } + }} + startDate={watch("start_date") ? `${watch("start_date")}` : null} + endDate={watch("end_date") ? `${watch("end_date")}` : null} + minDate={new Date(`${watch("start_date")}`)} + selectsEnd + /> + + + + )} +
+
+
-
-
-
-
-

- {cycleDetails.name} -

-
- - {!isCompleted && ( - setCycleDeleteModal(true)}> - - - Delete - - - )} - - - - Copy link - - - -
+ {cycleDetails.description && ( + + {cycleDetails.description} + + )} - - {cycleDetails.description} - -
- -
-
-
- - Lead -
- -
- {cycleDetails.owned_by.avatar && cycleDetails.owned_by.avatar !== "" ? ( - {cycleDetails.owned_by.display_name} - ) : ( - - {cycleDetails.owned_by.display_name.charAt(0)} - - )} - {cycleDetails.owned_by.display_name} -
-
- -
-
- - Progress -
- -
- - - - {cycleDetails.completed_issues}/{cycleDetails.total_issues} -
-
+
+
+
+ + Lead +
+
+
+ + {cycleDetails.owned_by.display_name}
-
- + +
+
+ + Issues +
+
+ {issueCount} +
+
+
+ +
+
+ {({ open }) => (
-
+
Progress - {!open && progressPercentage ? ( - +
+ +
+ {progressPercentage ? ( + {progressPercentage ? `${progressPercentage}%` : ""} ) : ( "" )} + {isStartValid && isEndValid ? ( + + + ) : ( +
+ + + Invalid date. Please enter valid date. + +
+ )}
- {isStartValid && isEndValid ? ( - - - ) : ( -
- - - {cycleStatus === "upcoming" - ? "Cycle is yet to start." - : "Invalid date. Please enter valid date."} - -
- )}
- {isStartValid && isEndValid ? ( -
-
-
- - - - - Pending Issues -{" "} - {cycleDetails.total_issues - - (cycleDetails.completed_issues + cycleDetails.cancelled_issues)} - +
+ {isStartValid && isEndValid ? ( +
+
+
+
+ + Ideal +
+
+ + Current +
+
- -
-
- - Ideal -
-
- - Current -
+
+
-
- 0 && ( +
+
-
- ) : ( - "" - )} - - -
- )} - -
-
- - {({ open }) => ( -
-
-
- Other Information -
- - {cycleDetails.total_issues > 0 ? ( - - - ) : ( -
- - - No issues found. Please add issue. - + )}
- )} -
- - - {cycleDetails.total_issues > 0 ? ( -
- -
- ) : ( - "" - )}
)}
- - ) : ( - -
- - -
-
- - - -
-
- )} -
+
+ + ) : ( + +
+ + +
+
+ + + +
+
+ )} ); }); diff --git a/web/components/modules/module-card-item.tsx b/web/components/modules/module-card-item.tsx index 620333f8e..b3625df6c 100644 --- a/web/components/modules/module-card-item.tsx +++ b/web/components/modules/module-card-item.tsx @@ -28,8 +28,8 @@ type Props = { export const ModuleCardItem: React.FC = observer((props) => { const { module } = props; - const [editModuleModal, setEditModuleModal] = useState(false); - const [moduleDeleteModal, setModuleDeleteModal] = useState(false); + const [editModal, setEditModal] = useState(false); + const [deleteModal, setDeleteModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -38,50 +38,7 @@ export const ModuleCardItem: React.FC = observer((props) => { const { module: moduleStore } = useMobxStore(); - const completionPercentage = ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100; - - const handleAddToFavorites = () => { - if (!workspaceSlug || !projectId) return; - - moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't add the module to favorites. Please try again.", - }); - }); - }; - - const handleRemoveFromFavorites = () => { - if (!workspaceSlug || !projectId) return; - - moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't remove the module from favorites. Please try again.", - }); - }); - }; - - const handleCopyText = () => { - copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => { - setToastAlert({ - type: "success", - title: "Link Copied!", - message: "Module link copied to clipboard.", - }); - }); - }; - - const openModuleOverview = () => { - const { query } = router; - - router.push({ - pathname: router.pathname, - query: { ...query, peekModule: module.id }, - }); - }; + const completionPercentage = (module.completed_issues / module.total_issues) * 100; const endDate = new Date(module.target_date ?? ""); const startDate = new Date(module.start_date ?? ""); @@ -101,23 +58,86 @@ export const ModuleCardItem: React.FC = observer((props) => { : `${module.completed_issues}/${module.total_issues} Issues` : "0 Issue"; + const handleAddToFavorites = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (!workspaceSlug || !projectId) return; + + moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't add the module to favorites. Please try again.", + }); + }); + }; + + const handleRemoveFromFavorites = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (!workspaceSlug || !projectId) return; + + moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't remove the module from favorites. Please try again.", + }); + }); + }; + + const handleCopyText = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => { + setToastAlert({ + type: "success", + title: "Link Copied!", + message: "Module link copied to clipboard.", + }); + }); + }; + + const handleEditModule = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setEditModal(true); + }; + + const handleDeleteModule = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDeleteModal(true); + }; + + const openModuleOverview = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + const { query } = router; + + router.push({ + pathname: router.pathname, + query: { ...query, peekModule: module.id }, + }); + }; + return ( <> {workspaceSlug && projectId && ( setEditModuleModal(false)} + isOpen={editModal} + onClose={() => setEditModal(false)} data={module} projectId={projectId.toString()} workspaceSlug={workspaceSlug.toString()} /> )} - setModuleDeleteModal(false)} /> + setDeleteModal(false)} />
- + {module.name}
@@ -128,13 +148,7 @@ export const ModuleCardItem: React.FC = observer((props) => { {moduleStatus.label} )} -
@@ -184,60 +198,28 @@ export const ModuleCardItem: React.FC = observer((props) => {
{module.is_favorite ? ( - ) : ( - )} - { - e.preventDefault(); - e.stopPropagation(); - setEditModuleModal(true); - }} - > + Edit module - { - e.preventDefault(); - e.stopPropagation(); - setModuleDeleteModal(true); - }} - > + Delete module - { - e.preventDefault(); - e.stopPropagation(); - handleCopyText(); - }} - > + Copy module link diff --git a/web/components/modules/module-list-item.tsx b/web/components/modules/module-list-item.tsx index 8b1271cc8..745d15c9d 100644 --- a/web/components/modules/module-list-item.tsx +++ b/web/components/modules/module-list-item.tsx @@ -28,8 +28,8 @@ type Props = { export const ModuleListItem: React.FC = observer((props) => { const { module } = props; - const [editModuleModal, setEditModuleModal] = useState(false); - const [moduleDeleteModal, setModuleDeleteModal] = useState(false); + const [editModal, setEditModal] = useState(false); + const [deleteModal, setDeleteModal] = useState(false); const router = useRouter(); const { workspaceSlug, projectId } = router.query; @@ -40,40 +40,6 @@ export const ModuleListItem: React.FC = observer((props) => { const completionPercentage = ((module.completed_issues + module.cancelled_issues) / module.total_issues) * 100; - const handleAddToFavorites = () => { - if (!workspaceSlug || !projectId) return; - - moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't add the module to favorites. Please try again.", - }); - }); - }; - - const handleRemoveFromFavorites = () => { - if (!workspaceSlug || !projectId) return; - - moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { - setToastAlert({ - type: "error", - title: "Error!", - message: "Couldn't remove the module from favorites. Please try again.", - }); - }); - }; - - const handleCopyText = () => { - copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => { - setToastAlert({ - type: "success", - title: "Link Copied!", - message: "Module link copied to clipboard.", - }); - }); - }; - const endDate = new Date(module.target_date ?? ""); const startDate = new Date(module.start_date ?? ""); @@ -87,7 +53,61 @@ export const ModuleListItem: React.FC = observer((props) => { const completedModuleCheck = module.status === "completed" && module.total_issues - module.completed_issues; - const openModuleOverview = () => { + const handleAddToFavorites = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (!workspaceSlug || !projectId) return; + + moduleStore.addModuleToFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't add the module to favorites. Please try again.", + }); + }); + }; + + const handleRemoveFromFavorites = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + if (!workspaceSlug || !projectId) return; + + moduleStore.removeModuleFromFavorites(workspaceSlug.toString(), projectId.toString(), module.id).catch(() => { + setToastAlert({ + type: "error", + title: "Error!", + message: "Couldn't remove the module from favorites. Please try again.", + }); + }); + }; + + const handleCopyText = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + copyUrlToClipboard(`${workspaceSlug}/projects/${projectId}/modules/${module.id}`).then(() => { + setToastAlert({ + type: "success", + title: "Link Copied!", + message: "Module link copied to clipboard.", + }); + }); + }; + + const handleEditModule = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setEditModal(true); + }; + + const handleDeleteModule = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + setDeleteModal(true); + }; + + const openModuleOverview = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); const { query } = router; router.push({ @@ -100,14 +120,14 @@ export const ModuleListItem: React.FC = observer((props) => { <> {workspaceSlug && projectId && ( setEditModuleModal(false)} + isOpen={editModal} + onClose={() => setEditModal(false)} data={module} projectId={projectId.toString()} workspaceSlug={workspaceSlug.toString()} /> )} - setModuleDeleteModal(false)} /> + setDeleteModal(false)} />
@@ -123,18 +143,11 @@ export const ModuleListItem: React.FC = observer((props) => { )} - + {module.name}
-
@@ -171,63 +184,29 @@ export const ModuleListItem: React.FC = observer((props) => { {module.is_favorite ? ( - ) : ( - )} - { - e.preventDefault(); - e.stopPropagation(); - setEditModuleModal(true); - }} - > + Edit module - { - e.preventDefault(); - e.stopPropagation(); - setModuleDeleteModal(true); - }} - > + Delete module - { - e.preventDefault(); - e.stopPropagation(); - handleCopyText(); - }} - > + Copy module link diff --git a/web/components/modules/sidebar.tsx b/web/components/modules/sidebar.tsx index aa5462e08..f931f3fc1 100644 --- a/web/components/modules/sidebar.tsx +++ b/web/components/modules/sidebar.tsx @@ -400,7 +400,7 @@ export const ModuleDetailsSidebar: React.FC = observer((props) => { }} totalIssues={moduleDetails.total_issues} module={moduleDetails} - isPeekModuleDetails={Boolean(peekModule)} + isPeekView={Boolean(peekModule)} />
)} diff --git a/web/constants/cycle.tsx b/web/constants/cycle.tsx index a2433d70c..78d1e0a54 100644 --- a/web/constants/cycle.tsx +++ b/web/constants/cycle.tsx @@ -37,3 +37,40 @@ export const CYCLE_VIEWS = [ icon: , }, ]; + +export const CYCLE_STATUS: { + label: string; + value: "current" | "upcoming" | "completed" | "draft"; + color: string; + textColor: string; + bgColor: string; +}[] = [ + { + label: "day left", + value: "current", + color: "#F59E0B", + textColor: "text-amber-500", + bgColor: "bg-amber-50", + }, + { + label: "Yet to start", + value: "upcoming", + color: "#3F76FF", + textColor: "text-blue-500", + bgColor: "bg-indigo-50", + }, + { + label: "Completed", + value: "completed", + color: "#16A34A", + textColor: "text-green-600", + bgColor: "bg-green-50", + }, + { + label: "Draft", + value: "draft", + color: "#525252", + textColor: "text-custom-text-300", + bgColor: "bg-custom-background-90", + }, +]; diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index 6f8384297..e96038b7f 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -25,7 +25,7 @@ const SingleCycle: React.FC = () => { const { cycle: cycleStore } = useMobxStore(); - const { storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false"); + const { setValue, storedValue } = useLocalStorage("cycle_sidebar_collapsed", "false"); const isSidebarCollapsed = storedValue ? (storedValue === "true" ? true : false) : false; const { error } = useSWR( @@ -35,6 +35,10 @@ const SingleCycle: React.FC = () => { : null ); + const toggleSidebar = () => { + setValue(`${!isSidebarCollapsed}`); + }; + // TODO: add this function to bulk add issues to cycle // const handleAddIssuesToCycle = async (data: ISearchIssueResponse[]) => { // if (!workspaceSlug || !projectId) return; @@ -75,11 +79,21 @@ const SingleCycle: React.FC = () => { /> ) : ( <> -
-
+
+
- {cycleId && } + {cycleId && !isSidebarCollapsed && ( +
+ +
+ )}
)} diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx index 5fb79fb36..5f9988e52 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/index.tsx @@ -29,7 +29,11 @@ const ProjectCyclesPage: NextPage = observer(() => { const { project: projectStore, cycle: cycleStore } = useMobxStore(); // router const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + const { workspaceSlug, projectId, peekCycle } = router.query as { + workspaceSlug: string; + projectId: string; + peekCycle: string; + }; // fetching project details useSWR( workspaceSlug && projectId ? `PROJECT_DETAILS_${projectId}` : null, @@ -150,13 +154,14 @@ const ProjectCyclesPage: NextPage = observer(() => {
- + {cycleView && cycleLayout && workspaceSlug && projectId && ( )} @@ -165,35 +170,38 @@ const ProjectCyclesPage: NextPage = observer(() => { - + {cycleView && cycleLayout && workspaceSlug && projectId && ( )} - + {cycleView && cycleLayout && workspaceSlug && projectId && ( )} - + {cycleView && cycleLayout && workspaceSlug && projectId && ( )} diff --git a/yarn.lock b/yarn.lock index e95d031bf..7f729677e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2755,7 +2755,7 @@ dependencies: "@types/react" "*" -"@types/react-color@^3.0.6": +"@types/react-color@^3.0.6", "@types/react-color@^3.0.9": version "3.0.9" resolved "https://registry.yarnpkg.com/@types/react-color/-/react-color-3.0.9.tgz#8dbb0d798f2979c3d7e2e26dd46321e80da950b4" integrity sha512-Ojyc6jySSKvM6UYQrZxaYe0JZXtgHHXwR2q9H4MhcNCswFdeZH1owYZCvPtdHtMOfh7t8h1fY0Gd0nvU1JGDkQ== @@ -7393,7 +7393,7 @@ react-markdown@^8.0.7: unist-util-visit "^4.0.0" vfile "^5.0.0" -react-moveable@^0.54.1: +react-moveable@^0.54.1, react-moveable@^0.54.2: version "0.54.2" resolved "https://registry.yarnpkg.com/react-moveable/-/react-moveable-0.54.2.tgz#87ce9af3499dc1c8218bce7e174b10264c1bbecf" integrity sha512-NGaVLbn0i9pb3+BWSKGWFqI/Mgm4+WMeWHxXXQ4Qi1tHxWCXrUrbGvpxEpt69G/hR7dez+/m68ex+fabjnvcUg==