From 1a7b5d72226e29f0084396c1008d23cd8fc5e09c Mon Sep 17 00:00:00 2001 From: Manish Gupta <59428681+mguptahub@users.noreply.github.com> Date: Thu, 8 Feb 2024 12:26:55 +0530 Subject: [PATCH 1/7] build fix (#3594) --- .github/workflows/build-branch.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-branch.yml b/.github/workflows/build-branch.yml index c43305fc0..603f08e94 100644 --- a/.github/workflows/build-branch.yml +++ b/.github/workflows/build-branch.yml @@ -37,7 +37,7 @@ jobs: echo "BUILDX_DRIVER=docker-container" >> $GITHUB_OUTPUT echo "BUILDX_VERSION=latest" >> $GITHUB_OUTPUT echo "BUILDX_PLATFORMS=linux/amd64" >> $GITHUB_OUTPUT - echo "BUILDX_ENDPOINT=local" >> $GITHUB_OUTPUT + echo "BUILDX_ENDPOINT=" >> $GITHUB_OUTPUT fi echo "TARGET_BRANCH=${{ env.TARGET_BRANCH }}" >> $GITHUB_OUTPUT From c1c0297b6d2ae6562d948c3da08c2904702cd459 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Thu, 8 Feb 2024 13:08:19 +0530 Subject: [PATCH 2/7] fix: handled issue create modal submission on clicking enter key (#3593) --- web/components/issues/issue-modal/form.tsx | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/web/components/issues/issue-modal/form.tsx b/web/components/issues/issue-modal/form.tsx index 430aa4920..544ebeb15 100644 --- a/web/components/issues/issue-modal/form.tsx +++ b/web/components/issues/issue-modal/form.tsx @@ -250,7 +250,7 @@ export const IssueFormRoot: FC = observer((props) => { }} /> )} -
+ handleFormSubmit(data))}>
{/* Don't show project selection if editing an issue */} @@ -699,13 +699,7 @@ export const IssueFormRoot: FC = observer((props) => { )} -
From 9545dc77d6173059390da8bf842a2de43fea2676 Mon Sep 17 00:00:00 2001 From: Aaryan Khandelwal <65252264+aaryan610@users.noreply.github.com> Date: Thu, 8 Feb 2024 13:30:16 +0530 Subject: [PATCH 3/7] fix: cycle and module reordering in the gantt chart (#3570) * fix: cycle and module reordering in the gantt chart * chore: hide duration from sidebar if no dates are assigned * chore: updated date helper functions to accept undefined params * chore: update cycle sidebar condition --- .../cycles/active-cycle-details.tsx | 2 +- web/components/cycles/cycles-board-card.tsx | 2 +- web/components/cycles/cycles-list-item.tsx | 2 +- .../cycles/gantt-chart/cycles-list-layout.tsx | 44 ++-------- web/components/cycles/sidebar.tsx | 5 +- .../widgets/issue-panels/issue-list-item.tsx | 4 +- .../gantt-chart/helpers/block-structure.tsx | 3 +- .../gantt-chart/sidebar/cycle-sidebar.tsx | 10 ++- .../gantt-chart/sidebar/module-sidebar.tsx | 10 ++- .../sidebar/project-view-sidebar.tsx | 10 ++- .../gantt-chart/sidebar/sidebar.tsx | 13 ++- web/components/issues/index.ts | 1 - .../issues/view-select/due-date.tsx | 81 ------------------- .../issues/view-select/estimate.tsx | 64 --------------- web/components/issues/view-select/index.ts | 3 - .../issues/view-select/start-date.tsx | 72 ----------------- .../gantt-chart/modules-list-layout.tsx | 42 +++++----- web/helpers/date-time.helper.ts | 15 ++-- web/store/cycle.store.ts | 12 +-- web/store/module.store.ts | 4 +- 20 files changed, 75 insertions(+), 324 deletions(-) delete mode 100644 web/components/issues/view-select/due-date.tsx delete mode 100644 web/components/issues/view-select/estimate.tsx delete mode 100644 web/components/issues/view-select/index.ts delete mode 100644 web/components/issues/view-select/start-date.tsx diff --git a/web/components/cycles/active-cycle-details.tsx b/web/components/cycles/active-cycle-details.tsx index a0101b1c1..2fa79ec3a 100644 --- a/web/components/cycles/active-cycle-details.tsx +++ b/web/components/cycles/active-cycle-details.tsx @@ -150,7 +150,7 @@ export const ActiveCycleDetails: React.FC = observer((props color: group.color, })); - const daysLeft = findHowManyDaysLeft(activeCycle.end_date ?? new Date()); + const daysLeft = findHowManyDaysLeft(activeCycle.end_date) ?? 0; return (
diff --git a/web/components/cycles/cycles-board-card.tsx b/web/components/cycles/cycles-board-card.tsx index 8da2be9ec..bad7df0e5 100644 --- a/web/components/cycles/cycles-board-card.tsx +++ b/web/components/cycles/cycles-board-card.tsx @@ -137,7 +137,7 @@ export const CyclesBoardCard: FC = (props) => { }); }; - const daysLeft = findHowManyDaysLeft(cycleDetails.end_date ?? new Date()); + const daysLeft = findHowManyDaysLeft(cycleDetails.end_date) ?? 0; return (
diff --git a/web/components/cycles/cycles-list-item.tsx b/web/components/cycles/cycles-list-item.tsx index a6d467091..725480241 100644 --- a/web/components/cycles/cycles-list-item.tsx +++ b/web/components/cycles/cycles-list-item.tsx @@ -140,7 +140,7 @@ export const CyclesListItem: FC = (props) => { const currentCycle = CYCLE_STATUS.find((status) => status.value === cycleStatus); - const daysLeft = findHowManyDaysLeft(cycleDetails.end_date ?? new Date()); + const daysLeft = findHowManyDaysLeft(cycleDetails.end_date) ?? 0; return ( <> diff --git a/web/components/cycles/gantt-chart/cycles-list-layout.tsx b/web/components/cycles/gantt-chart/cycles-list-layout.tsx index 26d04e103..797fc9e39 100644 --- a/web/components/cycles/gantt-chart/cycles-list-layout.tsx +++ b/web/components/cycles/gantt-chart/cycles-list-layout.tsx @@ -1,11 +1,8 @@ import { FC } from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; -import { KeyedMutator } from "swr"; // hooks import { useCycle, useUser } from "hooks/store"; -// services -import { CycleService } from "services/cycle.service"; // components import { GanttChartRoot, IBlockUpdateData, CycleGanttSidebar } from "components/gantt-chart"; import { CycleGanttBlock } from "components/cycles"; @@ -17,14 +14,10 @@ import { EUserProjectRoles } from "constants/project"; type Props = { workspaceSlug: string; cycleIds: string[]; - mutateCycles?: KeyedMutator; }; -// services -const cycleService = new CycleService(); - export const CyclesListGanttChartView: FC = observer((props) => { - const { cycleIds, mutateCycles } = props; + const { cycleIds } = props; // router const router = useRouter(); const { workspaceSlug } = router.query; @@ -32,38 +25,15 @@ export const CyclesListGanttChartView: FC = observer((props) => { const { membership: { currentProjectRole }, } = useUser(); - const { getCycleById } = useCycle(); + const { getCycleById, updateCycleDetails } = useCycle(); - const handleCycleUpdate = (cycle: ICycle, payload: IBlockUpdateData) => { - if (!workspaceSlug) return; - mutateCycles && - mutateCycles((prevData: any) => { - if (!prevData) return prevData; + const handleCycleUpdate = async (cycle: ICycle, data: IBlockUpdateData) => { + if (!workspaceSlug || !cycle) return; - const newList = prevData.map((p: any) => ({ - ...p, - ...(p.id === cycle.id - ? { - start_date: payload.start_date ? payload.start_date : p.start_date, - target_date: payload.target_date ? payload.target_date : p.end_date, - sort_order: payload.sort_order ? payload.sort_order.newSortOrder : p.sort_order, - } - : {}), - })); + const payload: any = { ...data }; + if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder; - if (payload.sort_order) { - const removedElement = newList.splice(payload.sort_order.sourceIndex, 1)[0]; - newList.splice(payload.sort_order.destinationIndex, 0, removedElement); - } - - return newList; - }, false); - - const newPayload: any = { ...payload }; - - if (newPayload.sort_order && payload.sort_order) newPayload.sort_order = payload.sort_order.newSortOrder; - - cycleService.patchCycle(workspaceSlug.toString(), cycle.project, cycle.id, newPayload); + await updateCycleDetails(workspaceSlug.toString(), cycle.project, cycle.id, payload); }; const blockFormat = (blocks: (ICycle | null)[]) => { diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index c61679304..299c71008 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -318,6 +318,7 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { const issueCount = cycleDetails.total_issues === 0 ? "0 Issue" : `${cycleDetails.completed_issues}/${cycleDetails.total_issues}`; + const daysLeft = findHowManyDaysLeft(cycleDetails.end_date); const isEditingAllowed = !!currentProjectRole && currentProjectRole >= EUserWorkspaceRoles.MEMBER; @@ -375,8 +376,8 @@ export const CycleDetailsSidebar: React.FC = observer((props) => { backgroundColor: `${currentCycle.color}20`, }} > - {currentCycle.value === "current" - ? `${findHowManyDaysLeft(cycleDetails.end_date ?? new Date())} ${currentCycle.label}` + {currentCycle.value === "current" && daysLeft !== undefined + ? `${daysLeft} ${currentCycle.label}` : `${currentCycle.label}`} )} diff --git a/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx b/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx index 3da862d91..fe003e167 100644 --- a/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx +++ b/web/components/dashboard/widgets/issue-panels/issue-list-item.tsx @@ -83,7 +83,7 @@ export const AssignedOverdueIssueListItem: React.FC = observ const blockedByIssueProjectDetails = blockedByIssues.length === 1 ? getProjectById(blockedByIssues[0]?.project_id ?? "") : null; - const dueBy = findTotalDaysInRange(new Date(issueDetails.target_date ?? ""), new Date(), false); + const dueBy = findTotalDaysInRange(new Date(issueDetails.target_date ?? ""), new Date(), false) ?? 0; return ( = observe const projectDetails = getProjectById(issue.project_id); - const dueBy = findTotalDaysInRange(new Date(issue.target_date ?? ""), new Date(), false); + const dueBy = findTotalDaysInRange(new Date(issue.target_date ?? ""), new Date(), false) ?? 0; return ( - blocks && - blocks.map((block) => ({ + blocks?.map((block) => ({ data: block, id: block.id, sort_order: block.sort_order, diff --git a/web/components/gantt-chart/sidebar/cycle-sidebar.tsx b/web/components/gantt-chart/sidebar/cycle-sidebar.tsx index 1af1529c2..dddccda5a 100644 --- a/web/components/gantt-chart/sidebar/cycle-sidebar.tsx +++ b/web/components/gantt-chart/sidebar/cycle-sidebar.tsx @@ -93,7 +93,7 @@ export const CycleGanttSidebar: React.FC = (props) => { <> {blocks ? ( blocks.map((block, index) => { - const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? ""); + const duration = findTotalDaysInRange(block.start_date, block.target_date); return ( = (props) => {
-
- {duration} day{duration > 1 ? "s" : ""} -
+ {duration !== undefined && ( +
+ {duration} day{duration > 1 ? "s" : ""} +
+ )}
diff --git a/web/components/gantt-chart/sidebar/module-sidebar.tsx b/web/components/gantt-chart/sidebar/module-sidebar.tsx index 30f146dc5..8f8788787 100644 --- a/web/components/gantt-chart/sidebar/module-sidebar.tsx +++ b/web/components/gantt-chart/sidebar/module-sidebar.tsx @@ -93,7 +93,7 @@ export const ModuleGanttSidebar: React.FC = (props) => { <> {blocks ? ( blocks.map((block, index) => { - const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? ""); + const duration = findTotalDaysInRange(block.start_date, block.target_date); return ( = (props) => {
-
- {duration} day{duration > 1 ? "s" : ""} -
+ {duration !== undefined && ( +
+ {duration} day{duration > 1 ? "s" : ""} +
+ )} diff --git a/web/components/gantt-chart/sidebar/project-view-sidebar.tsx b/web/components/gantt-chart/sidebar/project-view-sidebar.tsx index da7382859..6e31215c1 100644 --- a/web/components/gantt-chart/sidebar/project-view-sidebar.tsx +++ b/web/components/gantt-chart/sidebar/project-view-sidebar.tsx @@ -94,7 +94,7 @@ export const ProjectViewGanttSidebar: React.FC = (props) => { <> {blocks ? ( blocks.map((block, index) => { - const duration = findTotalDaysInRange(block.start_date ?? "", block.target_date ?? ""); + const duration = findTotalDaysInRange(block.start_date, block.target_date); return ( = (props) => {
-
- {duration} day{duration > 1 ? "s" : ""} -
+ {duration !== undefined && ( +
+ {duration} day{duration > 1 ? "s" : ""} +
+ )} diff --git a/web/components/gantt-chart/sidebar/sidebar.tsx b/web/components/gantt-chart/sidebar/sidebar.tsx index bca39a0bd..12de8e127 100644 --- a/web/components/gantt-chart/sidebar/sidebar.tsx +++ b/web/components/gantt-chart/sidebar/sidebar.tsx @@ -119,10 +119,7 @@ export const IssueGanttSidebar: React.FC = (props) => { // hide the block if it doesn't have start and target dates and showAllBlocks is false if (!showAllBlocks && !isBlockVisibleOnSidebar) return; - const duration = - !block.start_date || !block.target_date - ? null - : findTotalDaysInRange(block.start_date, block.target_date); + const duration = findTotalDaysInRange(block.start_date, block.target_date); return ( = (props) => {
-
- {duration && ( + {duration !== undefined && ( +
{duration} day{duration > 1 ? "s" : ""} - )} -
+
+ )} diff --git a/web/components/issues/index.ts b/web/components/issues/index.ts index 3cf88cb7c..3904049e9 100644 --- a/web/components/issues/index.ts +++ b/web/components/issues/index.ts @@ -1,6 +1,5 @@ export * from "./attachment"; export * from "./issue-modal"; -export * from "./view-select"; export * from "./delete-issue-modal"; export * from "./description-form"; export * from "./issue-layouts"; diff --git a/web/components/issues/view-select/due-date.tsx b/web/components/issues/view-select/due-date.tsx deleted file mode 100644 index d61e7586a..000000000 --- a/web/components/issues/view-select/due-date.tsx +++ /dev/null @@ -1,81 +0,0 @@ -// ui -import { CustomDatePicker } from "components/ui"; -import { Tooltip } from "@plane/ui"; -import { CalendarCheck } from "lucide-react"; -// helpers -import { findHowManyDaysLeft, renderFormattedDate } from "helpers/date-time.helper"; -// types -import { TIssue } from "@plane/types"; - -type Props = { - issue: TIssue; - onChange: (date: string | null) => void; - handleOnOpen?: () => void; - handleOnClose?: () => void; - tooltipPosition?: "top" | "bottom"; - className?: string; - noBorder?: boolean; - disabled: boolean; -}; - -export const ViewDueDateSelect: React.FC = ({ - issue, - onChange, - handleOnOpen, - handleOnClose, - tooltipPosition = "top", - className = "", - noBorder = false, - disabled, -}) => { - const minDate = issue.start_date ? new Date(issue.start_date) : null; - minDate?.setDate(minDate.getDate()); - - return ( - -
- - {issue.target_date ? ( - <> - - {renderFormattedDate(issue.target_date) ?? "_ _"} - - ) : ( - <> - - Due Date - - )} -
- } - minDate={minDate ?? undefined} - noBorder={noBorder} - handleOnOpen={handleOnOpen} - handleOnClose={handleOnClose} - disabled={disabled} - /> - -
- ); -}; diff --git a/web/components/issues/view-select/estimate.tsx b/web/components/issues/view-select/estimate.tsx deleted file mode 100644 index 1739f3aaa..000000000 --- a/web/components/issues/view-select/estimate.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React from "react"; -import { observer } from "mobx-react-lite"; -import { Triangle } from "lucide-react"; -import sortBy from "lodash/sortBy"; -// store hooks -import { useEstimate } from "hooks/store"; -// ui -import { CustomSelect, Tooltip } from "@plane/ui"; -// types -import { TIssue } from "@plane/types"; - -type Props = { - issue: TIssue; - onChange: (data: number) => void; - tooltipPosition?: "top" | "bottom"; - customButton?: boolean; - disabled: boolean; -}; - -export const ViewEstimateSelect: React.FC = observer((props) => { - const { issue, onChange, tooltipPosition = "top", customButton = false, disabled } = props; - const { areEstimatesEnabledForCurrentProject, activeEstimateDetails, getEstimatePointValue } = useEstimate(); - - const estimateValue = getEstimatePointValue(issue.estimate_point, issue.project_id); - - const estimateLabels = ( - -
- - {estimateValue ?? "None"} -
-
- ); - - if (!areEstimatesEnabledForCurrentProject) return null; - - return ( - - - <> - - - - None - - - {sortBy(activeEstimateDetails?.points, "key")?.map((estimate) => ( - - <> - - {estimate.value} - - - ))} - - ); -}); diff --git a/web/components/issues/view-select/index.ts b/web/components/issues/view-select/index.ts deleted file mode 100644 index 8eb88cb0d..000000000 --- a/web/components/issues/view-select/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from "./due-date"; -export * from "./estimate"; -export * from "./start-date"; diff --git a/web/components/issues/view-select/start-date.tsx b/web/components/issues/view-select/start-date.tsx deleted file mode 100644 index 039bc0cb5..000000000 --- a/web/components/issues/view-select/start-date.tsx +++ /dev/null @@ -1,72 +0,0 @@ -// ui -import { CustomDatePicker } from "components/ui"; -import { Tooltip } from "@plane/ui"; -import { CalendarClock } from "lucide-react"; -// helpers -import { renderFormattedDate } from "helpers/date-time.helper"; -// types -import { TIssue } from "@plane/types"; - -type Props = { - issue: TIssue; - onChange: (date: string | null) => void; - handleOnOpen?: () => void; - handleOnClose?: () => void; - tooltipPosition?: "top" | "bottom"; - className?: string; - noBorder?: boolean; - disabled: boolean; -}; - -export const ViewStartDateSelect: React.FC = ({ - issue, - onChange, - handleOnOpen, - handleOnClose, - tooltipPosition = "top", - className = "", - noBorder = false, - disabled, -}) => { - const maxDate = issue.target_date ? new Date(issue.target_date) : null; - maxDate?.setDate(maxDate.getDate()); - - return ( - -
- - {issue?.start_date ? ( - <> - - {renderFormattedDate(issue?.start_date ?? "_ _")} - - ) : ( - <> - - Start Date - - )} -
- } - handleOnClose={handleOnClose} - disabled={disabled} - /> - -
- ); -}; diff --git a/web/components/modules/gantt-chart/modules-list-layout.tsx b/web/components/modules/gantt-chart/modules-list-layout.tsx index d1cbd0dfa..53948f71d 100644 --- a/web/components/modules/gantt-chart/modules-list-layout.tsx +++ b/web/components/modules/gantt-chart/modules-list-layout.tsx @@ -13,37 +13,32 @@ export const ModulesListGanttChartView: React.FC = observer(() => { const router = useRouter(); const { workspaceSlug } = router.query; // store - const { projectModuleIds, moduleMap } = useModule(); const { currentProjectDetails } = useProject(); + const { projectModuleIds, moduleMap, updateModuleDetails } = useModule(); - const handleModuleUpdate = (module: IModule, payload: IBlockUpdateData) => { - if (!workspaceSlug) return; - // FIXME - //updateModuleGanttStructure(workspaceSlug.toString(), module.project, module, payload); + const handleModuleUpdate = async (module: IModule, data: IBlockUpdateData) => { + if (!workspaceSlug || !module) return; + + const payload: any = { ...data }; + if (data.sort_order) payload.sort_order = data.sort_order.newSortOrder; + + await updateModuleDetails(workspaceSlug.toString(), module.project, module.id, payload); }; const blockFormat = (blocks: string[]) => - blocks && blocks.length > 0 - ? blocks - .filter((blockId) => { - const block = moduleMap[blockId]; - return block.start_date && block.target_date && new Date(block.start_date) <= new Date(block.target_date); - }) - .map((blockId) => { - const block = moduleMap[blockId]; - return { - data: block, - id: block.id, - sort_order: block.sort_order, - start_date: new Date(block.start_date ?? ""), - target_date: new Date(block.target_date ?? ""), - }; - }) - : []; + blocks?.map((blockId) => { + const block = moduleMap[blockId]; + return { + data: block, + id: block.id, + sort_order: block.sort_order, + start_date: block.start_date ? new Date(block.start_date) : null, + target_date: block.target_date ? new Date(block.target_date) : null, + }; + }); const isAllowed = currentProjectDetails?.member_role === 20 || currentProjectDetails?.member_role === 15; - const modules = projectModuleIds; return (
{ enableBlockRightResize={isAllowed} enableBlockMove={isAllowed} enableReorder={isAllowed} + showAllBlocks />
); diff --git a/web/helpers/date-time.helper.ts b/web/helpers/date-time.helper.ts index bc5daa2a3..b629e60ec 100644 --- a/web/helpers/date-time.helper.ts +++ b/web/helpers/date-time.helper.ts @@ -87,11 +87,11 @@ export const renderFormattedTime = (date: string | Date, timeFormat: "12-hour" | * @example checkIfStringIsDate("2021-01-01", "2021-01-08") // 8 */ export const findTotalDaysInRange = ( - startDate: Date | string, - endDate: Date | string, + startDate: Date | string | undefined | null, + endDate: Date | string | undefined | null, inclusive: boolean = true -): number => { - if (!startDate || !endDate) return 0; +): number | undefined => { + if (!startDate || !endDate) return undefined; // Parse the dates to check if they are valid const parsedStartDate = new Date(startDate); const parsedEndDate = new Date(endDate); @@ -110,8 +110,11 @@ export const findTotalDaysInRange = ( * @param {boolean} inclusive (optional) // default true * @example findHowManyDaysLeft("2024-01-01") // 3 */ -export const findHowManyDaysLeft = (date: string | Date, inclusive: boolean = true): number => { - if (!date) return 0; +export const findHowManyDaysLeft = ( + date: Date | string | undefined | null, + inclusive: boolean = true +): number | undefined => { + if (!date) return undefined; // Pass the date to findTotalDaysInRange function to find the total number of days in range from today return findTotalDaysInRange(new Date(), date, inclusive); }; diff --git a/web/store/cycle.store.ts b/web/store/cycle.store.ts index bb6824c08..51340d740 100644 --- a/web/store/cycle.store.ts +++ b/web/store/cycle.store.ts @@ -103,7 +103,7 @@ export class CycleStore implements ICycleStore { const projectId = this.rootStore.app.router.projectId; if (!projectId || !this.fetchedMap[projectId]) return null; let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project === projectId); - allCycles = sortBy(allCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + allCycles = sortBy(allCycles, [(c) => c.sort_order]); const allCycleIds = allCycles.map((c) => c.id); return allCycleIds; } @@ -118,7 +118,7 @@ export class CycleStore implements ICycleStore { const hasEndDatePassed = isPast(new Date(c.end_date ?? "")); return c.project === projectId && hasEndDatePassed; }); - completedCycles = sortBy(completedCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + completedCycles = sortBy(completedCycles, [(c) => c.sort_order]); const completedCycleIds = completedCycles.map((c) => c.id); return completedCycleIds; } @@ -133,7 +133,7 @@ export class CycleStore implements ICycleStore { const isStartDateUpcoming = isFuture(new Date(c.start_date ?? "")); return c.project === projectId && isStartDateUpcoming; }); - upcomingCycles = sortBy(upcomingCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + upcomingCycles = sortBy(upcomingCycles, [(c) => c.sort_order]); const upcomingCycleIds = upcomingCycles.map((c) => c.id); return upcomingCycleIds; } @@ -148,7 +148,7 @@ export class CycleStore implements ICycleStore { const hasEndDatePassed = isPast(new Date(c.end_date ?? "")); return c.project === projectId && !hasEndDatePassed; }); - incompleteCycles = sortBy(incompleteCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + incompleteCycles = sortBy(incompleteCycles, [(c) => c.sort_order]); const incompleteCycleIds = incompleteCycles.map((c) => c.id); return incompleteCycleIds; } @@ -162,7 +162,7 @@ export class CycleStore implements ICycleStore { let draftCycles = Object.values(this.cycleMap ?? {}).filter( (c) => c.project === projectId && !c.start_date && !c.end_date ); - draftCycles = sortBy(draftCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + draftCycles = sortBy(draftCycles, [(c) => c.sort_order]); const draftCycleIds = draftCycles.map((c) => c.id); return draftCycleIds; } @@ -203,7 +203,7 @@ export class CycleStore implements ICycleStore { if (!this.fetchedMap[projectId]) return null; let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project === projectId); - cycles = sortBy(cycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]); + cycles = sortBy(cycles, [(c) => c.sort_order]); const cycleIds = cycles.map((c) => c.id); return cycleIds || null; }); diff --git a/web/store/module.store.ts b/web/store/module.store.ts index 7ebccc23c..5c80e39d0 100644 --- a/web/store/module.store.ts +++ b/web/store/module.store.ts @@ -100,7 +100,7 @@ export class ModulesStore implements IModuleStore { const projectId = this.rootStore.app.router.projectId; if (!projectId || !this.fetchedMap[projectId]) return null; let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId); - projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]); + projectModules = sortBy(projectModules, [(m) => m.sort_order]); const projectModuleIds = projectModules.map((m) => m.id); return projectModuleIds || null; } @@ -120,7 +120,7 @@ export class ModulesStore implements IModuleStore { if (!this.fetchedMap[projectId]) return null; let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId); - projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]); + projectModules = sortBy(projectModules, [(m) => m.sort_order]); const projectModuleIds = projectModules.map((m) => m.id); return projectModuleIds; }); From 55afef204d415096dfad1a27899aaf19557adcc9 Mon Sep 17 00:00:00 2001 From: Ramesh Kumar Chandra <31303617+rameshkumarchandra@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:49:26 +0530 Subject: [PATCH 4/7] style: responsive profile (#3596) * style: responsive profile * style: profile header drop down, sidebar auto show hide depending on the screen width * fix: item tap on white space in the drop down menu in profile header * fix: profile layout breaking on big screens in page visit --- .../sidebar/sidebar-menu-hamburger-toggle.tsx | 2 +- web/components/headers/user-profile.tsx | 72 ++- web/components/profile/navbar.tsx | 11 +- web/components/profile/sidebar.tsx | 43 +- .../profile/preferences/layout.tsx | 55 +- .../profile/preferences/sidebar.tsx | 29 +- .../settings-layout/profile/sidebar.tsx | 92 ++- web/layouts/user-profile-layout/layout.tsx | 7 +- .../profile/[userId]/assigned.tsx | 2 +- .../profile/[userId]/created.tsx | 2 +- .../profile/[userId]/index.tsx | 2 +- .../profile/[userId]/subscribed.tsx | 2 +- web/pages/profile/activity.tsx | 20 +- web/pages/profile/change-password.tsx | 6 + web/pages/profile/index.tsx | 575 +++++++++--------- web/pages/profile/preferences/theme.tsx | 2 +- web/store/application/theme.store.ts | 18 + 17 files changed, 571 insertions(+), 369 deletions(-) diff --git a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx index 0e34eac2c..fe7b8d177 100644 --- a/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx +++ b/web/components/core/sidebar/sidebar-menu-hamburger-toggle.tsx @@ -3,7 +3,7 @@ import { Menu } from "lucide-react"; import { useApplication } from "hooks/store"; import { observer } from "mobx-react"; -export const SidebarHamburgerToggle: FC = observer (() => { +export const SidebarHamburgerToggle: FC = observer(() => { const { theme: themStore } = useApplication(); return (
( -
+type TUserProfileHeader = { + type?: string | undefined +} + +export const UserProfileHeader: FC = observer((props) => { + const { type = undefined } = props + + const router = useRouter(); + const { workspaceSlug, userId } = router.query; + + const AUTHORIZED_ROLES = [20, 15, 10]; + const { + membership: { currentWorkspaceRole }, + } = useUser(); + + if (!currentWorkspaceRole) return null; + + const isAuthorized = AUTHORIZED_ROLES.includes(currentWorkspaceRole); + const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB; + + const { theme: themStore } = useApplication(); + + return (
-
+
} /> +
+ + {type} + +
+ } + customButtonClassName="flex flex-grow justify-center text-custom-text-200 text-sm" + closeOnSelect + > + <> + {tabsList.map((tab) => ( + + {tab.label} + + ))} + + +
-
-); +
) +}); + + diff --git a/web/components/profile/navbar.tsx b/web/components/profile/navbar.tsx index 44dfe57d1..4361b7a9d 100644 --- a/web/components/profile/navbar.tsx +++ b/web/components/profile/navbar.tsx @@ -22,16 +22,15 @@ export const ProfileNavbar: React.FC = (props) => { const tabsList = isAuthorized ? [...PROFILE_VIEWER_TAB, ...PROFILE_ADMINS_TAB] : PROFILE_VIEWER_TAB; return ( -
+
{tabsList.map((tab) => ( {tab.label} diff --git a/web/components/profile/sidebar.tsx b/web/components/profile/sidebar.tsx index 3ce7747c9..b356b5adb 100644 --- a/web/components/profile/sidebar.tsx +++ b/web/components/profile/sidebar.tsx @@ -4,7 +4,7 @@ import useSWR from "swr"; import { Disclosure, Transition } from "@headlessui/react"; import { observer } from "mobx-react-lite"; // hooks -import { useUser } from "hooks/store"; +import { useApplication, useUser } from "hooks/store"; // services import { UserService } from "services/user.service"; // components @@ -18,6 +18,8 @@ import { renderFormattedDate } from "helpers/date-time.helper"; import { renderEmoji } from "helpers/emoji.helper"; // fetch-keys import { USER_PROFILE_PROJECT_SEGREGATION } from "constants/fetch-keys"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; +import { useEffect, useRef } from "react"; // services const userService = new UserService(); @@ -28,6 +30,8 @@ export const ProfileSidebar = observer(() => { const { workspaceSlug, userId } = router.query; // store hooks const { currentUser } = useUser(); + const { theme: themStore } = useApplication(); + const ref = useRef(null); const { data: userProjectsData } = useSWR( workspaceSlug && userId ? USER_PROFILE_PROJECT_SEGREGATION(workspaceSlug.toString(), userId.toString()) : null, @@ -36,6 +40,14 @@ export const ProfileSidebar = observer(() => { : null ); + useOutsideClickDetector(ref, () => { + if (themStore.profileSidebarCollapsed === false) { + if (window.innerWidth < 768) { + themStore.toggleProfileSidebar(); + } + } + }); + const userDetails = [ { label: "Joined on", @@ -47,8 +59,26 @@ export const ProfileSidebar = observer(() => { }, ]; + useEffect(() => { + const handleToggleProfileSidebar = () => { + if (window && window.innerWidth < 768) { + themStore.toggleProfileSidebar(true); + } + if (window && themStore.profileSidebarCollapsed && window.innerWidth >= 768) { + themStore.toggleProfileSidebar(false); + } + }; + + window.addEventListener("resize", handleToggleProfileSidebar); + handleToggleProfileSidebar(); + return () => window.removeEventListener("resize", handleToggleProfileSidebar); + }, [themStore]); + return ( -
+
{userProjectsData ? ( <>
@@ -132,13 +162,12 @@ export const ProfileSidebar = observer(() => { {project.assigned_issues > 0 && (
{completedIssuePercentage}%
diff --git a/web/layouts/settings-layout/profile/preferences/layout.tsx b/web/layouts/settings-layout/profile/preferences/layout.tsx index 9d17350a9..b25935f4e 100644 --- a/web/layouts/settings-layout/profile/preferences/layout.tsx +++ b/web/layouts/settings-layout/profile/preferences/layout.tsx @@ -2,6 +2,11 @@ import { FC, ReactNode } from "react"; // layout import { ProfileSettingsLayout } from "layouts/settings-layout"; import { ProfilePreferenceSettingsSidebar } from "./sidebar"; +import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; +import { CustomMenu } from "@plane/ui"; +import { ChevronDown } from "lucide-react"; +import Link from "next/link"; +import { useRouter } from "next/router"; interface IProfilePreferenceSettingsLayout { children: ReactNode; @@ -10,9 +15,57 @@ interface IProfilePreferenceSettingsLayout { export const ProfilePreferenceSettingsLayout: FC = (props) => { const { children, header } = props; + const router = useRouter(); + + const showMenuItem = () => { + const item = router.asPath.split('/'); + let splittedItem = item[item.length - 1]; + splittedItem = splittedItem.replace(splittedItem[0], splittedItem[0].toUpperCase()); + console.log(splittedItem); + return splittedItem; + } + + const profilePreferenceLinks: Array<{ + label: string; + href: string; + }> = [ + { + label: "Theme", + href: `/profile/preferences/theme`, + }, + { + label: "Email", + href: `/profile/preferences/email`, + }, + ]; return ( - + + + + {showMenuItem()} + +
+ } + customButtonClassName="flex flex-grow justify-start text-custom-text-200 text-sm" + > + <> + {profilePreferenceLinks.map((link) => ( + + {link.label} + + ))} + +
+ }>
diff --git a/web/layouts/settings-layout/profile/preferences/sidebar.tsx b/web/layouts/settings-layout/profile/preferences/sidebar.tsx index d1eec1233..7f43f3cad 100644 --- a/web/layouts/settings-layout/profile/preferences/sidebar.tsx +++ b/web/layouts/settings-layout/profile/preferences/sidebar.tsx @@ -9,28 +9,27 @@ export const ProfilePreferenceSettingsSidebar = () => { label: string; href: string; }> = [ - { - label: "Theme", - href: `/profile/preferences/theme`, - }, - { - label: "Email", - href: `/profile/preferences/email`, - }, - ]; + { + label: "Theme", + href: `/profile/preferences/theme`, + }, + { + label: "Email", + href: `/profile/preferences/email`, + }, + ]; return ( -
+
Preference
{profilePreferenceLinks.map((link) => (
{link.label}
diff --git a/web/layouts/settings-layout/profile/sidebar.tsx b/web/layouts/settings-layout/profile/sidebar.tsx index 0a97b3364..4b8a1b854 100644 --- a/web/layouts/settings-layout/profile/sidebar.tsx +++ b/web/layouts/settings-layout/profile/sidebar.tsx @@ -1,4 +1,4 @@ -import { useState } from "react"; +import { useEffect, useRef, useState } from "react"; import { mutate } from "swr"; import Link from "next/link"; import { useRouter } from "next/router"; @@ -12,6 +12,7 @@ import useToast from "hooks/use-toast"; import { Tooltip } from "@plane/ui"; // constants import { PROFILE_ACTION_LINKS } from "constants/profile"; +import useOutsideClickDetector from "hooks/use-outside-click-detector"; const WORKSPACE_ACTION_LINKS = [ { @@ -52,6 +53,35 @@ export const ProfileLayoutSidebar = observer(() => { currentUserSettings?.workspace?.fallback_workspace_slug || ""; + const ref = useRef(null); + + useOutsideClickDetector(ref, () => { + if (sidebarCollapsed === false) { + if (window.innerWidth < 768) { + toggleSidebar(); + } + } + }); + + useEffect(() => { + const handleResize = () => { + if (window.innerWidth <= 768) { + toggleSidebar(true); + } + }; + handleResize(); + window.addEventListener("resize", handleResize); + return () => { + window.removeEventListener("resize", handleResize); + }; + }, [toggleSidebar]); + + const handleItemClick = () => { + if (window.innerWidth < 768) { + toggleSidebar(); + } + }; + const handleSignOut = async () => { setIsSigningOut(true); @@ -73,16 +103,18 @@ export const ProfileLayoutSidebar = observer(() => { return (
-
+
@@ -101,14 +133,13 @@ export const ProfileLayoutSidebar = observer(() => { if (link.key === "change-password" && currentUser?.is_password_autoset) return null; return ( - +
{} {!sidebarCollapsed && link.label} @@ -129,19 +160,17 @@ export const ProfileLayoutSidebar = observer(() => { {workspace?.logo && workspace.logo !== "" ? ( { )}
{WORKSPACE_ACTION_LINKS.map((link) => ( - +
{} {!sidebarCollapsed && link.label} @@ -180,9 +208,8 @@ export const ProfileLayoutSidebar = observer(() => {
+
); }); diff --git a/web/pages/profile/index.tsx b/web/pages/profile/index.tsx index 294ef3574..655d6a4bd 100644 --- a/web/pages/profile/index.tsx +++ b/web/pages/profile/index.tsx @@ -23,6 +23,7 @@ import type { NextPageWithLayout } from "lib/types"; // constants import { USER_ROLES } from "constants/workspace"; import { TIME_ZONES } from "constants/timezones"; +import { SidebarHamburgerToggle } from "components/core/sidebar/sidebar-menu-hamburger-toggle"; const defaultValues: Partial = { avatar: "", @@ -56,7 +57,7 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { // store hooks const { currentUser: myProfile, updateCurrentUser, currentUserLoader } = useUser(); // custom hooks - const {} = useUserAuth({ user: myProfile, isLoading: currentUserLoader }); + const { } = useUserAuth({ user: myProfile, isLoading: currentUserLoader }); useEffect(() => { reset({ ...defaultValues, ...myProfile }); @@ -136,304 +137,310 @@ const ProfileSettingsPage: NextPageWithLayout = observer(() => { return ( <> - ( - setIsImageUploadModalOpen(false)} - isRemoving={isRemoving} - handleDelete={() => handleDelete(myProfile?.avatar, true)} - onSuccess={(url) => { - onChange(url); - handleSubmit(onSubmit)(); - setIsImageUploadModalOpen(false); - }} - value={value && value.trim() !== "" ? value : null} - /> - )} - /> - setDeactivateAccountModal(false)} /> -
-
-
-
- {myProfile?.first_name +
+ +
+
+ ( + setIsImageUploadModalOpen(false)} + isRemoving={isRemoving} + handleDelete={() => handleDelete(myProfile?.avatar, true)} + onSuccess={(url) => { + onChange(url); + handleSubmit(onSubmit)(); + setIsImageUploadModalOpen(false); + }} + value={value && value.trim() !== "" ? value : null} /> -
-
-
- +
+
+
+ +
+ ( + onChange(imageUrl)} + control={control} + value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} + /> )} - + />
-
-
- ( - onChange(imageUrl)} - control={control} - value={value ?? "https://images.unsplash.com/photo-1506383796573-caf02b4a79ab"} - /> - )} - /> -
-
+
+
+
+ {`${watch("first_name")} ${watch("last_name")}`} +
+ {watch("email")} +
-
-
-
- {`${watch("first_name")} ${watch("last_name")}`} -
- {watch("email")} -
- - {/* + {/* Activity Overview */} -
+
-
-
-

- First name* -

- ( - +
+

+ First name* +

+ ( + + )} /> - )} - /> - {errors.first_name && Please enter first name} -
- -
-

Last name

- - ( - - )} - /> -
- -
-

- Email* -

- ( - - )} - /> -
- -
-

- Role* -

- ( - - {USER_ROLES.map((item) => ( - - {item.label} - - ))} - - )} - /> - {errors.role && Please select a role} -
- -
-

- Display name* -

- { - if (value.trim().length < 1) return "Display name can't be empty."; - - if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; - - if (value.replace(/\s/g, "").length < 1) - return "Display name must be at least 1 characters long."; - - if (value.replace(/\s/g, "").length > 20) - return "Display name must be less than 20 characters long."; - - return true; - }, - }} - render={({ field: { value, onChange, ref } }) => ( - - )} - /> - {errors.display_name && Please enter display name} -
- -
-

- Timezone* -

- - ( - t.value === value)?.label ?? value : "Select a timezone"} - options={timeZoneOptions} - onChange={onChange} - optionsClassName="w-full" - buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} - className="rounded-md border-[0.5px] !border-custom-border-200" - input - /> - )} - /> - {errors.role && Please select a time zone} -
- -
- -
-
-
-
- - {({ open }) => ( - <> - - Deactivate account - - - - -
- - The danger zone of the profile page is a critical area that requires careful consideration and - attention. When deactivating an account, all of the data and resources within that account will be - permanently removed and cannot be recovered. - -
- -
+ {errors.first_name && Please enter first name}
-
-
- - )} -
+ +
+

Last name

+ + ( + + )} + /> +
+ +
+

+ Email* +

+ ( + + )} + /> +
+ +
+

+ Role* +

+ ( + + {USER_ROLES.map((item) => ( + + {item.label} + + ))} + + )} + /> + {errors.role && Please select a role} +
+ +
+

+ Display name* +

+ { + if (value.trim().length < 1) return "Display name can't be empty."; + + if (value.split(" ").length > 1) return "Display name can't have two consecutive spaces."; + + if (value.replace(/\s/g, "").length < 1) + return "Display name must be at least 1 characters long."; + + if (value.replace(/\s/g, "").length > 20) + return "Display name must be less than 20 characters long."; + + return true; + }, + }} + render={({ field: { value, onChange, ref } }) => ( + + )} + /> + {errors.display_name && Please enter display name} +
+ +
+

+ Timezone* +

+ + ( + t.value === value)?.label ?? value : "Select a timezone"} + options={timeZoneOptions} + onChange={onChange} + optionsClassName="w-full" + buttonClassName={errors.user_timezone ? "border-red-500" : "border-none"} + className="rounded-md border-[0.5px] !border-custom-border-200" + input + /> + )} + /> + {errors.role && Please select a time zone} +
+ +
+ +
+
+
+ + + {({ open }) => ( + <> + + Deactivate account + + + + +
+ + The danger zone of the profile page is a critical area that requires careful consideration and + attention. When deactivating an account, all of the data and resources within that account will be + permanently removed and cannot be recovered. + +
+ +
+
+
+
+ + )} +
+
+
); diff --git a/web/pages/profile/preferences/theme.tsx b/web/pages/profile/preferences/theme.tsx index 51386bc29..0885ff6c8 100644 --- a/web/pages/profile/preferences/theme.tsx +++ b/web/pages/profile/preferences/theme.tsx @@ -48,7 +48,7 @@ const ProfilePreferencesThemePage: NextPageWithLayout = observer(() => { return ( <> {currentUser ? ( -
+

Preferences

diff --git a/web/store/application/theme.store.ts b/web/store/application/theme.store.ts index 1c6f792eb..7ecc0e770 100644 --- a/web/store/application/theme.store.ts +++ b/web/store/application/theme.store.ts @@ -7,15 +7,18 @@ export interface IThemeStore { // observables theme: string | null; sidebarCollapsed: boolean | undefined; + profileSidebarCollapsed: boolean | undefined; // actions toggleSidebar: (collapsed?: boolean) => void; setTheme: (theme: any) => void; + toggleProfileSidebar: (collapsed?: boolean) => void; } export class ThemeStore implements IThemeStore { // observables sidebarCollapsed: boolean | undefined = undefined; theme: string | null = null; + profileSidebarCollapsed: boolean | undefined = undefined; // root store rootStore; @@ -24,9 +27,11 @@ export class ThemeStore implements IThemeStore { // observable sidebarCollapsed: observable.ref, theme: observable.ref, + profileSidebarCollapsed: observable.ref, // action toggleSidebar: action, setTheme: action, + toggleProfileSidebar: action, // computed }); // root store @@ -46,6 +51,19 @@ export class ThemeStore implements IThemeStore { localStorage.setItem("app_sidebar_collapsed", this.sidebarCollapsed.toString()); }; + /** + * Toggle the profile sidebar collapsed state + * @param collapsed + */ + toggleProfileSidebar = (collapsed?: boolean) => { + if (collapsed === undefined) { + this.profileSidebarCollapsed = !this.profileSidebarCollapsed; + } else { + this.profileSidebarCollapsed = collapsed; + } + localStorage.setItem("profile_sidebar_collapsed", this.profileSidebarCollapsed.toString()); + }; + /** * Sets the user theme and applies it to the platform * @param _theme From e69fcd410c0848c47cc7f13d7ed1c142c4b23e6b Mon Sep 17 00:00:00 2001 From: Anmol Singh Bhatia <121005188+anmolsinghbhatia@users.noreply.github.com> Date: Thu, 8 Feb 2024 17:53:36 +0530 Subject: [PATCH 5/7] chore: custom menu dropdown improvement (#3599) * conflict: merge conflict resolved * chore: breadcrumbs component improvement --- packages/ui/src/breadcrumbs/breadcrumbs.tsx | 2 +- packages/ui/src/dropdowns/custom-menu.tsx | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/breadcrumbs/breadcrumbs.tsx b/packages/ui/src/breadcrumbs/breadcrumbs.tsx index 0f09764ac..a2ae1d680 100644 --- a/packages/ui/src/breadcrumbs/breadcrumbs.tsx +++ b/packages/ui/src/breadcrumbs/breadcrumbs.tsx @@ -10,7 +10,7 @@ type BreadcrumbsProps = { const Breadcrumbs = ({ children }: BreadcrumbsProps) => (
{React.Children.map(children, (child, index) => ( -
+
{child} {index !== React.Children.count(children) - 1 && (
diff --git a/web/components/dashboard/widgets/issue-panels/tabs-list.tsx b/web/components/dashboard/widgets/issue-panels/tabs-list.tsx index 9ce00a03c..306c2fdeb 100644 --- a/web/components/dashboard/widgets/issue-panels/tabs-list.tsx +++ b/web/components/dashboard/widgets/issue-panels/tabs-list.tsx @@ -16,42 +16,40 @@ export const TabsList: React.FC = observer((props) => { const { durationFilter, selectedTab } = props; const tabsList = durationFilter === "none" ? UNFILTERED_ISSUES_TABS_LIST : FILTERED_ISSUES_TABS_LIST; - const selectedTabIndex = tabsList.findIndex((tab) => tab.key === (selectedTab ?? "pending")); + const selectedTabIndex = tabsList.findIndex((tab) => tab.key === selectedTab); return (
{tabsList.map((tab) => ( diff --git a/web/components/dashboard/widgets/issues-by-priority.tsx b/web/components/dashboard/widgets/issues-by-priority.tsx index 97884bccc..91e321b05 100644 --- a/web/components/dashboard/widgets/issues-by-priority.tsx +++ b/web/components/dashboard/widgets/issues-by-priority.tsx @@ -73,8 +73,10 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => const { dashboardId, workspaceSlug } = props; // store hooks const { fetchWidgetStats, getWidgetDetails, getWidgetStats, updateDashboardWidgetFilters } = useDashboard(); + // derived values const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); + const selectedDuration = widgetDetails?.widget_filters.duration ?? "none"; const handleUpdateFilters = async (filters: Partial) => { if (!widgetDetails) return; @@ -84,7 +86,7 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => filters, }); - const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none"); + const filterDates = getCustomDates(filters.duration ?? selectedDuration); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), @@ -92,7 +94,7 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => }; useEffect(() => { - const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none"); + const filterDates = getCustomDates(selectedDuration); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), @@ -139,10 +141,10 @@ export const IssuesByPriorityWidget: React.FC = observer((props) => Assigned by priority handleUpdateFilters({ - target_date: val, + duration: val, }) } /> diff --git a/web/components/dashboard/widgets/issues-by-state-group.tsx b/web/components/dashboard/widgets/issues-by-state-group.tsx index 2f7f6ffae..a0eb6c70f 100644 --- a/web/components/dashboard/widgets/issues-by-state-group.tsx +++ b/web/components/dashboard/widgets/issues-by-state-group.tsx @@ -34,6 +34,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) // derived values const widgetDetails = getWidgetDetails(workspaceSlug, dashboardId, WIDGET_KEY); const widgetStats = getWidgetStats(workspaceSlug, dashboardId, WIDGET_KEY); + const selectedDuration = widgetDetails?.widget_filters.duration ?? "none"; const handleUpdateFilters = async (filters: Partial) => { if (!widgetDetails) return; @@ -43,7 +44,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) filters, }); - const filterDates = getCustomDates(filters.target_date ?? widgetDetails.widget_filters.target_date ?? "none"); + const filterDates = getCustomDates(filters.duration ?? selectedDuration); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), @@ -52,7 +53,7 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) // fetch widget stats useEffect(() => { - const filterDates = getCustomDates(widgetDetails?.widget_filters.target_date ?? "none"); + const filterDates = getCustomDates(selectedDuration); fetchWidgetStats(workspaceSlug, dashboardId, { widget_key: WIDGET_KEY, ...(filterDates.trim() !== "" ? { target_date: filterDates } : {}), @@ -138,10 +139,10 @@ export const IssuesByStateGroupWidget: React.FC = observer((props) Assigned by state handleUpdateFilters({ - target_date: val, + duration: val, }) } /> diff --git a/web/helpers/dashboard.helper.ts b/web/helpers/dashboard.helper.ts index 8003f15e3..90319a90b 100644 --- a/web/helpers/dashboard.helper.ts +++ b/web/helpers/dashboard.helper.ts @@ -4,6 +4,10 @@ import { renderFormattedPayloadDate } from "./date-time.helper"; // types import { TDurationFilterOptions, TIssuesListTypes } from "@plane/types"; +/** + * @description returns date range based on the duration filter + * @param duration + */ export const getCustomDates = (duration: TDurationFilterOptions): string => { const today = new Date(); let firstDay, lastDay; @@ -30,6 +34,10 @@ export const getCustomDates = (duration: TDurationFilterOptions): string => { } }; +/** + * @description returns redirection filters for the issues list + * @param type + */ export const getRedirectionFilters = (type: TIssuesListTypes): string => { const today = renderFormattedPayloadDate(new Date()); @@ -44,3 +52,20 @@ export const getRedirectionFilters = (type: TIssuesListTypes): string => { return filterParams; }; + +/** + * @description returns the tab key based on the duration filter + * @param duration + * @param tab + */ +export const getTabKey = (duration: TDurationFilterOptions, tab: TIssuesListTypes | undefined): TIssuesListTypes => { + if (!tab) return "completed"; + + if (tab === "completed") return tab; + + if (duration === "none") return "pending"; + else { + if (["upcoming", "overdue"].includes(tab)) return tab; + else return "upcoming"; + } +};