From 629342c86a3851ad217a70ecfe03f7ce0c27475a Mon Sep 17 00:00:00 2001 From: gurusainath Date: Wed, 4 Oct 2023 18:29:24 +0530 Subject: [PATCH 1/3] chore: cycle, cycle-issue, cycle-filters, cycle-kanban, cycle layout setup --- .../issue-layouts/cycle-layout-root.tsx | 49 +++ web/components/issues/issue-layouts/index.ts | 10 +- .../issue-layouts/kanban/cycle-root.tsx | 77 ++++ .../issue-layouts/kanban/module-root.tsx | 77 ++++ .../issues/issue-layouts/kanban/view-root.tsx | 77 ++++ .../issues/issue-layouts/list/cycle-root.tsx | 30 ++ .../issues/issue-layouts/list/module-root.tsx | 30 ++ .../issues/issue-layouts/list/view-root.tsx | 30 ++ .../projects/[projectId]/cycles/[cycleId].tsx | 9 +- web/store/cycle_filters.ts | 233 ++++++++++ web/store/cycle_issue.ts | 179 ++++++++ web/store/cycle_kanban_view.ts | 404 ++++++++++++++++++ web/store/cycles.ts | 64 ++- web/store/root.ts | 13 + 14 files changed, 1276 insertions(+), 6 deletions(-) create mode 100644 web/components/issues/issue-layouts/cycle-layout-root.tsx create mode 100644 web/components/issues/issue-layouts/kanban/cycle-root.tsx create mode 100644 web/components/issues/issue-layouts/kanban/module-root.tsx create mode 100644 web/components/issues/issue-layouts/kanban/view-root.tsx create mode 100644 web/components/issues/issue-layouts/list/cycle-root.tsx create mode 100644 web/components/issues/issue-layouts/list/module-root.tsx create mode 100644 web/components/issues/issue-layouts/list/view-root.tsx create mode 100644 web/store/cycle_filters.ts create mode 100644 web/store/cycle_issue.ts create mode 100644 web/store/cycle_kanban_view.ts diff --git a/web/components/issues/issue-layouts/cycle-layout-root.tsx b/web/components/issues/issue-layouts/cycle-layout-root.tsx new file mode 100644 index 00000000000..fff1868b3af --- /dev/null +++ b/web/components/issues/issue-layouts/cycle-layout-root.tsx @@ -0,0 +1,49 @@ +import React from "react"; +// next imports +import { useRouter } from "next/router"; +// swr +import useSWR from "swr"; +// mobx react lite +import { observer } from "mobx-react-lite"; +// components +import { CycleListLayout } from "./list/cycle-root"; +import { CycleKanBanLayout } from "./kanban/cycle-root"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; + +export const CycleLayoutRoot: React.FC = observer(() => { + const router = useRouter(); + const { workspaceSlug, projectId, cycleId } = router.query as { + workspaceSlug: string; + projectId: string; + cycleId: string; + }; + + const { + project: projectStore, + cycle: cycleStore, + cycleFilter: cycleFilterStore, + cycleIssue: cycleIssueStore, + } = useMobxStore(); + + useSWR(workspaceSlug && projectId ? `MODULE_ISSUES` : null, async () => { + if (workspaceSlug && projectId && cycleId) { + await cycleFilterStore.fetchUserProjectFilters(workspaceSlug, projectId); + await cycleStore.fetchCycleWithId(workspaceSlug, projectId, cycleId); + + await projectStore.fetchProjectStates(workspaceSlug, projectId); + await projectStore.fetchProjectLabels(workspaceSlug, projectId); + await projectStore.fetchProjectMembers(workspaceSlug, projectId); + + await cycleIssueStore.fetchIssues(workspaceSlug, projectId, cycleId); + } + }); + + const activeLayout = cycleFilterStore.userDisplayFilters.layout; + + return ( +
+ {activeLayout === "list" ? : activeLayout === "kanban" ? : null} +
+ ); +}); diff --git a/web/components/issues/issue-layouts/index.ts b/web/components/issues/issue-layouts/index.ts index 52dc274f81d..e8439b236e0 100644 --- a/web/components/issues/issue-layouts/index.ts +++ b/web/components/issues/issue-layouts/index.ts @@ -1,7 +1,15 @@ +// filters +export * from "./filters"; + +// layouts export * from "./list"; export * from "./calendar"; -export * from "./filters"; export * from "./gantt"; export * from "./kanban"; export * from "./spreadsheet"; + +// cycle root layout +export * from "./cycle-layout-root"; + +// module root layout export * from "./module-all-layouts"; diff --git a/web/components/issues/issue-layouts/kanban/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/cycle-root.tsx new file mode 100644 index 00000000000..dd44ec8afb1 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/cycle-root.tsx @@ -0,0 +1,77 @@ +import React from "react"; +// react beautiful dnd +import { DragDropContext } from "@hello-pangea/dnd"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { KanBanSwimLanes } from "./swimlanes"; +import { KanBan } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface ICycleKanBanLayout {} + +export const CycleKanBanLayout: React.FC = observer(() => { + const { + issue: issueStore, + issueFilter: issueFilterStore, + issueKanBanView: issueKanBanViewStore, + }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by + ? "swimlanes" + : "default"; + + const onDragEnd = (result: any) => { + if (!result) return; + + if ( + result.destination && + result.source && + result.destination.droppableId === result.source.droppableId && + result.destination.index === result.source.index + ) + return; + + currentKanBanView === "default" + ? issueKanBanViewStore?.handleDragDrop(result.source, result.destination) + : issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); + }; + + const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, sub_group_by, issue); + }; + + return ( +
+ + {currentKanBanView === "default" ? ( + + ) : ( + + )} + +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/module-root.tsx b/web/components/issues/issue-layouts/kanban/module-root.tsx new file mode 100644 index 00000000000..5fa0a0f67a5 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/module-root.tsx @@ -0,0 +1,77 @@ +import React from "react"; +// react beautiful dnd +import { DragDropContext } from "@hello-pangea/dnd"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { KanBanSwimLanes } from "./swimlanes"; +import { KanBan } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IModuleKanBanLayout {} + +export const ModuleKanBanLayout: React.FC = observer(() => { + const { + issue: issueStore, + issueFilter: issueFilterStore, + issueKanBanView: issueKanBanViewStore, + }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by + ? "swimlanes" + : "default"; + + const onDragEnd = (result: any) => { + if (!result) return; + + if ( + result.destination && + result.source && + result.destination.droppableId === result.source.droppableId && + result.destination.index === result.source.index + ) + return; + + currentKanBanView === "default" + ? issueKanBanViewStore?.handleDragDrop(result.source, result.destination) + : issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); + }; + + const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, sub_group_by, issue); + }; + + return ( +
+ + {currentKanBanView === "default" ? ( + + ) : ( + + )} + +
+ ); +}); diff --git a/web/components/issues/issue-layouts/kanban/view-root.tsx b/web/components/issues/issue-layouts/kanban/view-root.tsx new file mode 100644 index 00000000000..ed96306392e --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/view-root.tsx @@ -0,0 +1,77 @@ +import React from "react"; +// react beautiful dnd +import { DragDropContext } from "@hello-pangea/dnd"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { KanBanSwimLanes } from "./swimlanes"; +import { KanBan } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IViewKanBanLayout {} + +export const ViewKanBanLayout: React.FC = observer(() => { + const { + issue: issueStore, + issueFilter: issueFilterStore, + issueKanBanView: issueKanBanViewStore, + }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by + ? "swimlanes" + : "default"; + + const onDragEnd = (result: any) => { + if (!result) return; + + if ( + result.destination && + result.source && + result.destination.droppableId === result.source.droppableId && + result.destination.index === result.source.index + ) + return; + + currentKanBanView === "default" + ? issueKanBanViewStore?.handleDragDrop(result.source, result.destination) + : issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); + }; + + const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, sub_group_by, issue); + }; + + return ( +
+ + {currentKanBanView === "default" ? ( + + ) : ( + + )} + +
+ ); +}); diff --git a/web/components/issues/issue-layouts/list/cycle-root.tsx b/web/components/issues/issue-layouts/list/cycle-root.tsx new file mode 100644 index 00000000000..8f630d692ac --- /dev/null +++ b/web/components/issues/issue-layouts/list/cycle-root.tsx @@ -0,0 +1,30 @@ +import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { List } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface ICycleListLayout {} + +export const CycleListLayout: React.FC = observer(() => { + const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const updateIssue = (group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, null, issue); + }; + + return ( +
+ +
+ ); +}); diff --git a/web/components/issues/issue-layouts/list/module-root.tsx b/web/components/issues/issue-layouts/list/module-root.tsx new file mode 100644 index 00000000000..c4929423be2 --- /dev/null +++ b/web/components/issues/issue-layouts/list/module-root.tsx @@ -0,0 +1,30 @@ +import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { List } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IModuleListLayout {} + +export const ModuleListLayout: React.FC = observer(() => { + const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const updateIssue = (group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, null, issue); + }; + + return ( +
+ +
+ ); +}); diff --git a/web/components/issues/issue-layouts/list/view-root.tsx b/web/components/issues/issue-layouts/list/view-root.tsx new file mode 100644 index 00000000000..0e3db56c785 --- /dev/null +++ b/web/components/issues/issue-layouts/list/view-root.tsx @@ -0,0 +1,30 @@ +import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +// components +import { List } from "./default"; +// store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export interface IViewListLayout {} + +export const ViewListLayout: React.FC = observer(() => { + const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + + const issues = issueStore?.getIssues; + + const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; + + const display_properties = issueFilterStore?.userDisplayProperties || null; + + const updateIssue = (group_by: string | null, issue: any) => { + issueStore.updateIssueStructure(group_by, null, issue); + }; + + return ( +
+ +
+ ); +}); diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index 9d5194ef79d..d924398e27a 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -13,6 +13,7 @@ import { IssueViewContextProvider } from "contexts/issue-view.context"; // components import { ExistingIssuesListModal, IssuesFilterView, IssuesView } from "components/core"; import { CycleDetailsSidebar, TransferIssues, TransferIssuesModal } from "components/cycles"; +import { CycleLayoutRoot } from "components/issues/issue-layouts"; // services import issuesService from "services/issue.service"; import cycleServices from "services/cycles.service"; @@ -170,10 +171,10 @@ const SingleCycle: React.FC = () => { } duration-300`} > {cycleStatus === "completed" && setTransferIssuesModal(true)} />} - + +
+ +
Promise; + updateUserFilters: ( + workspaceSlug: string, + projectId: string, + filterToUpdate: Partial + ) => Promise; + updateDisplayProperties: ( + workspaceSlug: string, + projectId: string, + properties: Partial + ) => Promise; + + // computed + appliedFilters: TIssueParams[] | null; +} + +class CycleFilterStore implements ICycleFilterStore { + loader: boolean = false; + error: any | null = null; + + // observables + userDisplayProperties: any = {}; + userDisplayFilters: IIssueDisplayFilterOptions = {}; + userFilters: IIssueFilterOptions = {}; + defaultDisplayFilters: IIssueDisplayFilterOptions = {}; + defaultFilters: IIssueFilterOptions = {}; + defaultDisplayProperties: IIssueDisplayProperties = { + assignee: true, + start_date: true, + due_date: true, + labels: true, + key: true, + priority: true, + state: true, + sub_issue_count: true, + link: true, + attachment_count: true, + estimate: true, + created_on: true, + updated_on: true, + }; + + // root store + rootStore; + + // services + projectService; + issueService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + loader: observable.ref, + error: observable.ref, + + // observables + defaultDisplayFilters: observable.ref, + defaultFilters: observable.ref, + userDisplayProperties: observable.ref, + userDisplayFilters: observable.ref, + userFilters: observable.ref, + + // actions + fetchUserProjectFilters: action, + updateUserFilters: action, + updateDisplayProperties: action, + + // computed + appliedFilters: computed, + }); + + this.rootStore = _rootStore; + + this.projectService = new ProjectService(); + this.issueService = new IssueService(); + } + + computedFilter = (filters: any, filteredParams: any) => { + const computedFilters: any = {}; + Object.keys(filters).map((key) => { + if (filters[key] != undefined && filteredParams.includes(key)) + computedFilters[key] = + typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); + }); + + return computedFilters; + }; + + get appliedFilters(): TIssueParams[] | null { + if (!this.userFilters || !this.userDisplayFilters) return null; + + let filteredRouteParams: any = { + priority: this.userFilters?.priority || undefined, + state_group: this.userFilters?.state_group || undefined, + state: this.userFilters?.state || undefined, + assignees: this.userFilters?.assignees || undefined, + created_by: this.userFilters?.created_by || undefined, + labels: this.userFilters?.labels || undefined, + start_date: this.userFilters?.start_date || undefined, + target_date: this.userFilters?.target_date || undefined, + group_by: this.userDisplayFilters?.group_by || "state", + order_by: this.userDisplayFilters?.order_by || "-created_at", + sub_group_by: this.userDisplayFilters?.sub_group_by || undefined, + type: this.userDisplayFilters?.type || undefined, + sub_issue: this.userDisplayFilters?.sub_issue || true, + show_empty_groups: this.userDisplayFilters?.show_empty_groups || true, + start_target_date: this.userDisplayFilters?.start_target_date || true, + }; + + const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout, "issues"); + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (this.userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchUserProjectFilters = async (workspaceSlug: string, projectId: string) => { + try { + const memberResponse = await this.projectService.projectMemberMe(workspaceSlug, projectId); + const issueProperties = await this.issueService.getIssueProperties(workspaceSlug, projectId); + + runInAction(() => { + this.userFilters = memberResponse?.view_props?.filters; + this.userDisplayFilters = memberResponse?.view_props?.display_filters ?? {}; + this.userDisplayProperties = issueProperties?.properties || this.defaultDisplayProperties; + // default props from api + this.defaultFilters = memberResponse.default_props.filters; + this.defaultDisplayFilters = memberResponse.default_props.display_filters ?? {}; + }); + } catch (error) { + runInAction(() => { + this.error = error; + }); + + console.log("Failed to fetch user filters in issue filter store", error); + } + }; + + updateUserFilters = async (workspaceSlug: string, projectId: string, filterToUpdate: Partial) => { + const newViewProps = { + display_filters: { + ...this.userDisplayFilters, + ...filterToUpdate.display_filters, + }, + filters: { + ...this.userFilters, + ...filterToUpdate.filters, + }, + }; + + // set sub_group_by to null if group_by is set to null + if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null; + + // set group_by to state if layout is switched to kanban and group_by is null + if (newViewProps.display_filters.layout === "kanban" && newViewProps.display_filters.group_by === null) + newViewProps.display_filters.group_by = "state"; + + try { + runInAction(() => { + this.userFilters = newViewProps.filters; + this.userDisplayFilters = newViewProps.display_filters; + }); + + this.projectService.setProjectView(workspaceSlug, projectId, { + view_props: newViewProps, + }); + } catch (error) { + this.fetchUserProjectFilters(workspaceSlug, projectId); + + runInAction(() => { + this.error = error; + }); + + console.log("Failed to update user filters in issue filter store", error); + } + }; + + updateDisplayProperties = async ( + workspaceSlug: string, + projectId: string, + properties: Partial + ) => { + const newProperties = { + ...this.userDisplayProperties, + ...properties, + }; + + try { + runInAction(() => { + this.userDisplayProperties = newProperties; + }); + + // await this.issueService.patchIssueProperties(workspaceSlug, projectId, newProperties); + } catch (error) { + this.fetchUserProjectFilters(workspaceSlug, projectId); + + runInAction(() => { + this.error = error; + }); + + console.log("Failed to update user filters in issue filter store", error); + } + }; +} + +export default CycleFilterStore; diff --git a/web/store/cycle_issue.ts b/web/store/cycle_issue.ts new file mode 100644 index 00000000000..62f7f6f02de --- /dev/null +++ b/web/store/cycle_issue.ts @@ -0,0 +1,179 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// store +import { RootStore } from "./root"; +// types +import { IIssue } from "types"; +// services +import { CycleService } from "services/cycles.service"; + +export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped"; +export type IIssueGroupedStructure = { [group_id: string]: IIssue[] }; +export type IIssueGroupWithSubGroupsStructure = { + [group_id: string]: { + [sub_group_id: string]: IIssue[]; + }; +}; +export type IIssueUnGroupedStructure = IIssue[]; + +export interface ICycleIssueStore { + loader: boolean; + error: any | null; + // issues + issues: { + [cycleId: string]: { + grouped: IIssueGroupedStructure; + groupWithSubGroups: IIssueGroupWithSubGroupsStructure; + ungrouped: IIssueUnGroupedStructure; + }; + }; + // computed + getIssueType: IIssueType | null; + getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null; + // action + fetchIssues: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void; +} + +class CycleIssueStore implements ICycleIssueStore { + loader: boolean = false; + error: any | null = null; + issues: { + [cycle_id: string]: { + grouped: { + [group_id: string]: IIssue[]; + }; + groupWithSubGroups: { + [group_id: string]: { + [sub_group_id: string]: IIssue[]; + }; + }; + ungrouped: IIssue[]; + }; + } = {}; + // service + cycleService; + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + loader: observable.ref, + error: observable.ref, + issues: observable.ref, + // computed + getIssueType: computed, + getIssues: computed, + // actions + fetchIssues: action, + updateIssueStructure: action, + }); + this.rootStore = _rootStore; + this.cycleService = new CycleService(); + } + + get getIssueType() { + const groupedLayouts = ["kanban", "list", "calendar"]; + const ungroupedLayouts = ["spreadsheet", "gantt_chart"]; + + const issueLayout = this.rootStore?.cycleFilter?.userDisplayFilters?.layout || null; + const issueSubGroup = this.rootStore?.cycleFilter?.userDisplayFilters?.sub_group_by || null; + if (!issueLayout) return null; + + const _issueState = groupedLayouts.includes(issueLayout) + ? issueSubGroup + ? "groupWithSubGroups" + : "grouped" + : ungroupedLayouts.includes(issueLayout) + ? "ungrouped" + : null; + + return _issueState || null; + } + + get getIssues() { + const projectId: string | null = this.rootStore?.project?.projectId; + const issueType = this.getIssueType; + if (!projectId || !issueType) return null; + + return this.issues?.[projectId]?.[issueType] || null; + } + + updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => { + const projectId: string | null = issue?.project; + const issueType = this.getIssueType; + if (!projectId || !issueType) return null; + + let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null = + this.getIssues; + if (!issues) return null; + + if (issueType === "grouped" && group_id) { + issues = issues as IIssueGroupedStructure; + issues = { + ...issues, + [group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)), + }; + } + if (issueType === "groupWithSubGroups" && group_id && sub_group_id) { + issues = issues as IIssueGroupWithSubGroupsStructure; + issues = { + ...issues, + [sub_group_id]: { + ...issues[sub_group_id], + [group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)), + }, + }; + } + if (issueType === "ungrouped") { + issues = issues as IIssueUnGroupedStructure; + issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)); + } + + // reorder issues based on the issue update + + runInAction(() => { + this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } }; + }); + }; + + fetchIssues = async (workspaceSlug: string, projectId: string, cycleId: string) => { + try { + this.loader = true; + this.error = null; + + this.rootStore.workspace.setWorkspaceSlug(workspaceSlug); + this.rootStore.project.setProjectId(projectId); + this.rootStore.cycle.setCycleId(cycleId); + + const params = this.rootStore?.cycleFilter?.appliedFilters; + console.log("params", params); + + const issueResponse = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); + + const issueType = this.getIssueType; + if (issueType != null) { + const _issues = { + ...this.issues, + [projectId]: { + ...this.issues[projectId], + [issueType]: issueResponse, + }, + }; + runInAction(() => { + this.issues = _issues; + this.loader = false; + this.error = null; + }); + } + + return issueResponse; + } catch (error) { + console.error("Error: Fetching error in issues", error); + this.loader = false; + this.error = error; + return error; + } + }; +} + +export default CycleIssueStore; diff --git a/web/store/cycle_kanban_view.ts b/web/store/cycle_kanban_view.ts new file mode 100644 index 00000000000..d97b997f1ed --- /dev/null +++ b/web/store/cycle_kanban_view.ts @@ -0,0 +1,404 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +// types +import { RootStore } from "./root"; +import { IIssueType } from "./issue"; + +export interface ICycleKanBanViewStore { + kanBanToggle: { + groupByHeaderMinMax: string[]; + subgroupByIssuesVisibility: string[]; + }; + // computed + canUserDragDrop: boolean; + canUserDragDropVertically: boolean; + canUserDragDropHorizontally: boolean; + // actions + handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void; + handleSwimlaneDragDrop: (source: any, destination: any) => void; + handleDragDrop: (source: any, destination: any) => void; +} + +class CycleKanBanViewStore implements ICycleKanBanViewStore { + kanBanToggle: { + groupByHeaderMinMax: string[]; + subgroupByIssuesVisibility: string[]; + } = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] }; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + kanBanToggle: observable, + // computed + canUserDragDrop: computed, + canUserDragDropVertically: computed, + canUserDragDropHorizontally: computed, + + // actions + handleKanBanToggle: action, + handleSwimlaneDragDrop: action, + handleDragDrop: action, + }); + + this.rootStore = _rootStore; + } + + get canUserDragDrop() { + if ( + this.rootStore?.issueFilter?.userDisplayFilters?.order_by && + this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" && + this.rootStore?.issueFilter?.userDisplayFilters?.group_by && + ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) + ) { + if (this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by === null) return true; + if ( + this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by && + ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) + ) + return true; + } + return false; + } + + get canUserDragDropVertically() { + return false; + } + + get canUserDragDropHorizontally() { + return false; + } + + handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { + this.kanBanToggle = { + ...this.kanBanToggle, + [toggle]: this.kanBanToggle[toggle].includes(value) + ? this.kanBanToggle[toggle].filter((v) => v !== value) + : [...this.kanBanToggle[toggle], value], + }; + }; + + handleSwimlaneDragDrop = async (source: any, destination: any) => { + const workspaceSlug = this.rootStore?.workspace?.workspaceSlug; + const projectId = this.rootStore?.project?.projectId; + const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const currentIssues: any = this.rootStore.issue.getIssues; + + const sortOrderDefaultValue = 65535; + + if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) { + // update issue payload + let updateIssue: any = { + workspaceSlug: workspaceSlug, + projectId: projectId, + }; + + // source, destination group and sub group id + let droppableSourceColumnId = source.droppableId; + droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null; + let droppableDestinationColumnId = destination.droppableId; + droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null; + if (!droppableSourceColumnId || !droppableDestinationColumnId) return null; + + const source_group_id: string = droppableSourceColumnId[0]; + const source_sub_group_id: string = droppableSourceColumnId[1] === "null" ? null : droppableSourceColumnId[1]; + + const destination_group_id: string = droppableDestinationColumnId[0]; + const destination_sub_group_id: string = + droppableDestinationColumnId[1] === "null" ? null : droppableDestinationColumnId[1]; + + if (source_sub_group_id === destination_sub_group_id) { + if (source_group_id === destination_group_id) { + const _issues = currentIssues[source_sub_group_id][source_group_id]; + + // update the sort order + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _issues.length - 1) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2, + }; + } + + const [removed] = _issues.splice(source.index, 1); + _issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order }); + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_sub_group_id][source_group_id] = _issues; + } + + if (source_group_id != destination_group_id) { + const _sourceIssues = currentIssues[source_sub_group_id][source_group_id]; + let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + updateIssue = { ...updateIssue, issueId: removed?.id }; + + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + // updateIssue = { ...updateIssue, state: destination_group_id }; + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + // updateIssue = { ...updateIssue, priority: destination_group_id }; + + currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; + currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; + } + } + + if (source_sub_group_id != destination_sub_group_id) { + const _sourceIssues = currentIssues[source_sub_group_id][source_group_id]; + let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; + currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; + + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + // updateIssue = { ...updateIssue, state: destination_group_id }; + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + // updateIssue = { ...updateIssue, priority: destination_group_id }; + } + + const reorderedIssues = { + ...this.rootStore?.issue.issues, + [projectId]: { + ...this.rootStore?.issue.issues?.[projectId], + [issueType]: { + ...this.rootStore?.issue.issues?.[projectId]?.[issueType], + [issueType]: currentIssues, + }, + }, + }; + + runInAction(() => { + this.rootStore.issue.issues = { ...reorderedIssues }; + }); + + // console.log("updateIssue", updateIssue); + + // this.rootStore.issueDetail?.updateIssue( + // updateIssue.workspaceSlug, + // updateIssue.projectId, + // updateIssue.issueId, + // updateIssue, + // undefined + // ); + } + }; + + handleDragDrop = async (source: any, destination: any) => { + const workspaceSlug = this.rootStore?.workspace?.workspaceSlug; + const projectId = this.rootStore?.project?.projectId; + const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const currentIssues: any = this.rootStore.issue.getIssues; + + const sortOrderDefaultValue = 65535; + + if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) { + // update issue payload + let updateIssue: any = { + workspaceSlug: workspaceSlug, + projectId: projectId, + }; + + // source, destination group and sub group id + let droppableSourceColumnId = source.droppableId; + droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null; + let droppableDestinationColumnId = destination.droppableId; + droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null; + if (!droppableSourceColumnId || !droppableDestinationColumnId) return null; + + const source_group_id: string = droppableSourceColumnId[0]; + const destination_group_id: string = droppableDestinationColumnId[0]; + + if (this.canUserDragDrop) { + // vertical + if (source_group_id === destination_group_id) { + const _issues = currentIssues[source_group_id]; + + // update the sort order + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _issues.length - 1) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2, + }; + } + + const [removed] = _issues.splice(source.index, 1); + _issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order }); + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_group_id] = _issues; + } + + // horizontal + if (source_group_id != destination_group_id) { + const _sourceIssues = currentIssues[source_group_id]; + let _destinationIssues = currentIssues[destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + updateIssue = { ...updateIssue, issueId: removed?.id }; + + currentIssues[source_group_id] = _sourceIssues; + currentIssues[destination_group_id] = _destinationIssues; + } + + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + updateIssue = { ...updateIssue, state: destination_group_id }; + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + updateIssue = { ...updateIssue, priority: destination_group_id }; + } + + // user can drag the issues only vertically + if (this.canUserDragDropVertically && destination_group_id === destination_group_id) { + } + + // user can drag the issues only horizontally + if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) { + } + + const reorderedIssues = { + ...this.rootStore?.issue.issues, + [projectId]: { + ...this.rootStore?.issue.issues?.[projectId], + [issueType]: { + ...this.rootStore?.issue.issues?.[projectId]?.[issueType], + [issueType]: currentIssues, + }, + }, + }; + + runInAction(() => { + this.rootStore.issue.issues = { ...reorderedIssues }; + }); + + this.rootStore.issueDetail?.updateIssue( + updateIssue.workspaceSlug, + updateIssue.projectId, + updateIssue.issueId, + updateIssue, + undefined + ); + } + }; +} + +export default CycleKanBanViewStore; diff --git a/web/store/cycles.ts b/web/store/cycles.ts index bd761a29410..3e55ff944c6 100644 --- a/web/store/cycles.ts +++ b/web/store/cycles.ts @@ -11,20 +11,28 @@ export interface ICycleStore { loader: boolean; error: any | null; + cycleId: string | null; cycles: { [project_id: string]: ICycle[]; }; - cycle_details: { [cycle_id: string]: ICycle; }; + // computed + getCycleById: (cycleId: string) => ICycle | null; + + // actions + setCycleId: (cycleId: string) => void; + fetchCycles: ( workspaceSlug: string, projectId: string, params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete" ) => Promise; + fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise; + updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise; addCycleToFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; removeCycleFromFavorites: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; @@ -34,6 +42,7 @@ class CycleStore implements ICycleStore { loader: boolean = false; error: any | null = null; + cycleId: string | null = null; cycles: { [project_id: string]: ICycle[]; } = {}; @@ -54,12 +63,19 @@ class CycleStore implements ICycleStore { loader: observable, error: observable.ref, + cycleId: observable, cycles: observable.ref, + cycle_details: observable.ref, // computed projectCycles: computed, + // actions + setCycleId: action, + getCycleById: action, + fetchCycles: action, + fetchCycleWithId: action, createCycle: action, addCycleToFavorites: action, @@ -78,7 +94,13 @@ class CycleStore implements ICycleStore { return this.cycles[this.rootStore.project.projectId] || null; } + getCycleById = (cycleId: string) => this.cycle_details[cycleId] || null; + // actions + setCycleId = (cycleId: string) => { + this.cycleId = cycleId; + }; + fetchCycles = async ( workspaceSlug: string, projectId: string, @@ -105,6 +127,23 @@ class CycleStore implements ICycleStore { } }; + fetchCycleWithId = async (workspaceSlug: string, projectId: string, cycleId: string) => { + try { + const response = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId); + + runInAction(() => { + this.cycle_details = { + ...this.cycle_details, + [response?.id]: response, + }; + }); + + } catch (error) { + console.log("Failed to fetch cycle detail from cycle store"); + throw error; + } + }; + createCycle = async (workspaceSlug: string, projectId: string, data: any) => { try { const response = await this.cycleService.createCycle( @@ -128,6 +167,29 @@ class CycleStore implements ICycleStore { } }; + updateCycle = async (workspaceSlug: string, projectId: string, cycleId: string, data: any) => { + try { + const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, data, undefined); + + console.log("response", response); + + runInAction(() => { + this.cycles = { + ...this.cycles, + [projectId]: this.cycles[projectId].map((cycle) => { + if (cycle.id === cycleId) return { ...cycle, ...response }; + return cycle; + }), + }; + }); + + return response; + } catch (error) { + console.log("Failed to update cycle from cycle store"); + throw error; + } + }; + addCycleToFavorites = async (workspaceSlug: string, projectId: string, cycleId: string) => { try { runInAction(() => { diff --git a/web/store/root.ts b/web/store/root.ts index e365b195366..69723082b23 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -10,6 +10,9 @@ import WorkspaceStore, { IWorkspaceStore } from "./workspace"; import ProjectStore, { IProjectStore } from "./project"; import ModuleStore, { IModuleStore } from "./modules"; import CycleStore, { ICycleStore } from "./cycles"; +import CycleIssueStore, { ICycleIssueStore } from "./cycle_issue"; +import CycleFilterStore, { ICycleFilterStore } from "./cycle_filters"; +import CycleKanBanViewStore, { ICycleKanBanViewStore } from "./cycle_kanban_view"; import ViewStore, { IViewStore } from "./views"; import IssueFilterStore, { IIssueFilterStore } from "./issue_filters"; import IssueViewDetailStore from "./issue_detail"; @@ -29,7 +32,12 @@ export class RootStore { issue: IIssueStore; module: IModuleStore; moduleFilter: IModuleFilterStore; + cycle: ICycleStore; + cycleIssue: ICycleIssueStore; + cycleFilter: ICycleFilterStore; + cycleKanBanView: ICycleKanBanViewStore; + view: IViewStore; issueFilter: IIssueFilterStore; issueDetail: IssueViewDetailStore; @@ -44,7 +52,12 @@ export class RootStore { this.projectPublish = new ProjectPublishStore(this); this.module = new ModuleStore(this); this.moduleFilter = new ModuleFilterStore(this); + this.cycle = new CycleStore(this); + this.cycleIssue = new CycleIssueStore(this); + this.cycleFilter = new CycleFilterStore(this); + this.cycleKanBanView = new CycleKanBanViewStore(this); + this.view = new ViewStore(this); this.issue = new IssueStore(this); this.issueFilter = new IssueFilterStore(this); From c58daece00ad06153a4535fd50c5834d0c855657 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 5 Oct 2023 16:19:27 +0530 Subject: [PATCH 2/3] chore: cycles kanban and list view store --- web/components/cycles/sidebar.tsx | 77 ++---- .../issue-layouts/cycle-layout-root.tsx | 18 +- .../issue-layouts/kanban/cycle-root.tsx | 20 +- .../issues/issue-layouts/kanban/default.tsx | 35 ++- .../issue-layouts/kanban/headers/assignee.tsx | 8 +- .../kanban/headers/created_by.tsx | 8 +- .../kanban/headers/group-by-card.tsx | 13 +- .../kanban/headers/group-by-root.tsx | 16 +- .../issue-layouts/kanban/headers/label.tsx | 8 +- .../issue-layouts/kanban/headers/priority.tsx | 9 +- .../kanban/headers/state-group.tsx | 8 +- .../issue-layouts/kanban/headers/state.tsx | 8 +- .../kanban/headers/sub-group-by-card.tsx | 19 +- .../kanban/headers/sub-group-by-root.tsx | 16 +- .../issues/issue-layouts/kanban/root.tsx | 8 + .../issues/issue-layouts/kanban/swimlanes.tsx | 58 ++++- .../issues/issue-layouts/list/cycle-root.tsx | 6 +- .../projects/[projectId]/cycles/[cycleId].tsx | 8 +- web/store/cycle_filters.ts | 233 ------------------ web/store/cycle_issue.ts | 10 +- web/store/cycle_issue_filters.ts | 156 ++++++++++++ ...ban_view.ts => cycle_issue_kanban_view.ts} | 26 +- web/store/cycles.ts | 25 +- web/store/root.ts | 12 +- 24 files changed, 420 insertions(+), 385 deletions(-) delete mode 100644 web/store/cycle_filters.ts create mode 100644 web/store/cycle_issue_filters.ts rename web/store/{cycle_kanban_view.ts => cycle_issue_kanban_view.ts} (95%) diff --git a/web/components/cycles/sidebar.tsx b/web/components/cycles/sidebar.tsx index dafb1b6c265..e2ca98d3f86 100644 --- a/web/components/cycles/sidebar.tsx +++ b/web/components/cycles/sidebar.tsx @@ -32,11 +32,7 @@ import { import { ExclamationIcon } from "components/icons"; // helpers import { capitalizeFirstLetter, copyTextToClipboard } from "helpers/string.helper"; -import { - isDateGreaterThanToday, - renderDateFormat, - renderShortDateWithYearFormat, -} from "helpers/date-time.helper"; +import { isDateGreaterThanToday, renderDateFormat, renderShortDateWithYearFormat } from "helpers/date-time.helper"; // types import { ICurrentUserResponse, ICycle } from "types"; // fetch-keys @@ -50,13 +46,7 @@ type Props = { user: ICurrentUserResponse | undefined; }; -export const CycleDetailsSidebar: React.FC = ({ - cycle, - isOpen, - cycleStatus, - isCompleted, - user, -}) => { +export const CycleDetailsSidebar: React.FC = ({ cycle, isOpen, cycleStatus, isCompleted, user }) => { const [cycleDeleteModal, setCycleDeleteModal] = useState(false); const router = useRouter(); @@ -76,11 +66,7 @@ export const CycleDetailsSidebar: React.FC = ({ const submitChanges = (data: Partial) => { if (!workspaceSlug || !projectId || !cycleId) return; - mutate( - CYCLE_DETAILS(cycleId as string), - (prevData) => ({ ...(prevData as ICycle), ...data }), - false - ); + mutate(CYCLE_DETAILS(cycleId as string), (prevData) => ({ ...(prevData as ICycle), ...data }), false); cyclesService .patchCycle(workspaceSlug as string, projectId as string, cycleId as string, data, user) @@ -89,8 +75,7 @@ export const CycleDetailsSidebar: React.FC = ({ }; const handleCopyText = () => { - const originURL = - typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; + const originURL = typeof window !== "undefined" && window.location.origin ? window.location.origin : ""; copyTextToClipboard(`${originURL}/${workspaceSlug}/projects/${projectId}/cycles/${cycle?.id}`) .then(() => { @@ -116,11 +101,7 @@ export const CycleDetailsSidebar: React.FC = ({ const dateChecker = async (payload: any) => { try { - const res = await cyclesService.cycleDateCheck( - workspaceSlug as string, - projectId as string, - payload - ); + const res = await cyclesService.cycleDateCheck(workspaceSlug as string, projectId as string, payload); return res.status; } catch (err) { return false; @@ -277,20 +258,13 @@ export const CycleDetailsSidebar: React.FC = ({ const isStartValid = new Date(`${cycle?.start_date}`) <= new Date(); const isEndValid = new Date(`${cycle?.end_date}`) >= new Date(`${cycle?.start_date}`); - const progressPercentage = cycle - ? Math.round((cycle.completed_issues / cycle.total_issues) * 100) - : null; + const progressPercentage = cycle ? Math.round((cycle.completed_issues / cycle.total_issues) * 100) : null; return ( <> - +
@@ -316,9 +290,7 @@ export const CycleDetailsSidebar: React.FC = ({ {renderShortDateWithYearFormat( - new Date( - `${watch("start_date") ? watch("start_date") : cycle?.start_date}` - ), + new Date(`${watch("start_date") ? watch("start_date") : cycle?.start_date}`), "Start date" )} @@ -367,9 +339,7 @@ export const CycleDetailsSidebar: React.FC = ({ {renderShortDateWithYearFormat( - new Date( - `${watch("end_date") ? watch("end_date") : cycle?.end_date}` - ), + new Date(`${watch("end_date") ? watch("end_date") : cycle?.end_date}`), "End date" )} @@ -409,9 +379,7 @@ export const CycleDetailsSidebar: React.FC = ({
-

- {cycle.name} -

+

{cycle.name}

{!isCompleted && ( @@ -480,9 +448,7 @@ export const CycleDetailsSidebar: React.FC = ({
{({ open }) => ( -
+
Progress @@ -503,11 +469,7 @@ export const CycleDetailsSidebar: React.FC = ({ ) : (
- + {cycleStatus === "upcoming" ? "Cycle is yet to start." @@ -527,8 +489,7 @@ export const CycleDetailsSidebar: React.FC = ({ Pending Issues -{" "} - {cycle.total_issues - - (cycle.completed_issues + cycle.cancelled_issues)} + {cycle.total_issues - (cycle.completed_issues + cycle.cancelled_issues)}
@@ -564,9 +525,7 @@ export const CycleDetailsSidebar: React.FC = ({
{({ open }) => ( -
+
Other Information @@ -581,11 +540,7 @@ export const CycleDetailsSidebar: React.FC = ({ ) : (
- + No issues found. Please add issue. diff --git a/web/components/issues/issue-layouts/cycle-layout-root.tsx b/web/components/issues/issue-layouts/cycle-layout-root.tsx index fff1868b3af..17f31aa37b3 100644 --- a/web/components/issues/issue-layouts/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/cycle-layout-root.tsx @@ -21,28 +21,32 @@ export const CycleLayoutRoot: React.FC = observer(() => { const { project: projectStore, - cycle: cycleStore, - cycleFilter: cycleFilterStore, + issueFilter: issueFilterStore, cycleIssue: cycleIssueStore, + cycleIssueFilter: cycleIssueFilterStore, } = useMobxStore(); - useSWR(workspaceSlug && projectId ? `MODULE_ISSUES` : null, async () => { + useSWR(workspaceSlug && projectId && cycleId ? `CYCLE_ISSUES` : null, async () => { if (workspaceSlug && projectId && cycleId) { - await cycleFilterStore.fetchUserProjectFilters(workspaceSlug, projectId); - await cycleStore.fetchCycleWithId(workspaceSlug, projectId, cycleId); + // fetching the project display filters and display properties + await issueFilterStore.fetchUserProjectFilters(workspaceSlug, projectId); + // fetching the cycle filters + await cycleIssueFilterStore.fetchCycleFilters(workspaceSlug, projectId, cycleId); + // fetching the project state, labels and members await projectStore.fetchProjectStates(workspaceSlug, projectId); await projectStore.fetchProjectLabels(workspaceSlug, projectId); await projectStore.fetchProjectMembers(workspaceSlug, projectId); + // fetching the cycle issues await cycleIssueStore.fetchIssues(workspaceSlug, projectId, cycleId); } }); - const activeLayout = cycleFilterStore.userDisplayFilters.layout; + const activeLayout = issueFilterStore.userDisplayFilters.layout; return ( -
+
{activeLayout === "list" ? : activeLayout === "kanban" ? : null}
); diff --git a/web/components/issues/issue-layouts/kanban/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/cycle-root.tsx index dd44ec8afb1..728d4f02f05 100644 --- a/web/components/issues/issue-layouts/kanban/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/cycle-root.tsx @@ -14,12 +14,12 @@ export interface ICycleKanBanLayout {} export const CycleKanBanLayout: React.FC = observer(() => { const { - issue: issueStore, + cycleIssue: cycleIssueStore, issueFilter: issueFilterStore, - issueKanBanView: issueKanBanViewStore, + cycleIssueKanBanView: cycleIssueKanBanViewStore, }: RootStore = useMobxStore(); - const issues = issueStore?.getIssues; + const issues = cycleIssueStore?.getIssues; const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; @@ -43,12 +43,16 @@ export const CycleKanBanLayout: React.FC = observer(() => { return; currentKanBanView === "default" - ? issueKanBanViewStore?.handleDragDrop(result.source, result.destination) - : issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); + ? cycleIssueKanBanViewStore?.handleDragDrop(result.source, result.destination) + : cycleIssueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); }; const updateIssue = (sub_group_by: string | null, group_by: string | null, issue: any) => { - issueStore.updateIssueStructure(group_by, sub_group_by, issue); + cycleIssueStore.updateIssueStructure(group_by, sub_group_by, issue); + }; + + const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { + cycleIssueKanBanViewStore.handleKanBanToggle(toggle, value); }; return ( @@ -61,6 +65,8 @@ export const CycleKanBanLayout: React.FC = observer(() => { group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} + kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( { group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} + kanBanToggle={cycleIssueKanBanViewStore?.kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 395e93ea8a9..a38c51627e8 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -22,6 +22,8 @@ export interface IGroupByKanBan { isDragDisabled: boolean; handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; display_properties: any; + kanBanToggle: any; + handleKanBanToggle: any; } const GroupByKanBan: React.FC = observer( @@ -35,11 +37,11 @@ const GroupByKanBan: React.FC = observer( isDragDisabled, handleIssues, display_properties, + kanBanToggle, + handleKanBanToggle, }) => { - const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - const verticalAlignPosition = (_list: any) => - issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string); + kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string); return (
@@ -54,6 +56,8 @@ const GroupByKanBan: React.FC = observer( sub_group_by={sub_group_by} group_by={group_by} issues_count={issues?.[getValueFromObject(_list, listKey) as string]?.length || 0} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} />
)} @@ -106,10 +110,21 @@ export interface IKanBan { handleDragDrop?: (result: any) => void | undefined; handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; display_properties: any; + kanBanToggle: any; + handleKanBanToggle: any; } export const KanBan: React.FC = observer( - ({ issues, sub_group_by, group_by, sub_group_id = "null", handleIssues, display_properties }) => { + ({ + issues, + sub_group_by, + group_by, + sub_group_id = "null", + handleIssues, + display_properties, + kanBanToggle, + handleKanBanToggle, + }) => { const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); return ( @@ -125,6 +140,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -139,6 +156,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -153,6 +172,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -167,6 +188,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -181,6 +204,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -195,6 +220,8 @@ export const KanBan: React.FC = observer( isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )}
diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index b913bc85f16..f29bf661139 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -14,12 +14,14 @@ export interface IAssigneesHeader { group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const Icon = ({ user }: any) => ; export const AssigneesHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const { project: projectStore }: RootStore = useMobxStore(); const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; @@ -33,6 +35,8 @@ export const AssigneesHeader: React.FC = observer( icon={} title={assignee?.member?.display_name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={assignee?.member?.display_name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx index d7c45e6427a..678837d1b33 100644 --- a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -14,10 +14,12 @@ export interface ICreatedByHeader { group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const CreatedByHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const { project: projectStore }: RootStore = useMobxStore(); const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; @@ -31,6 +33,8 @@ export const CreatedByHeader: React.FC = observer( icon={} title={createdBy?.member?.display_name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={createdBy?.member?.display_name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index c59d6023f72..74cfdc9ba34 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -3,9 +3,6 @@ import React from "react"; import { Plus, Minimize2, Maximize2, Circle } from "lucide-react"; // mobx import { observer } from "mobx-react-lite"; -// store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; interface IHeaderGroupByCard { sub_group_by: string | null; @@ -14,13 +11,13 @@ interface IHeaderGroupByCard { icon?: React.ReactNode; title: string; count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const HeaderGroupByCard = observer( - ({ sub_group_by, group_by, column_id, icon, title, count }: IHeaderGroupByCard) => { - const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - - const verticalAlignPosition = issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(column_id); + ({ sub_group_by, group_by, column_id, icon, title, count, kanBanToggle, handleKanBanToggle }: IHeaderGroupByCard) => { + const verticalAlignPosition = kanBanToggle?.groupByHeaderMinMax.includes(column_id); return (
issueKanBanViewStore?.handleKanBanToggle("groupByHeaderMinMax", column_id)} + onClick={() => handleKanBanToggle("groupByHeaderMinMax", column_id)} > {verticalAlignPosition ? ( diff --git a/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx index bdee36e429d..3d070bd15a8 100644 --- a/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-root.tsx @@ -16,10 +16,12 @@ export interface IKanBanGroupByHeaderRoot { sub_group_by: string | null; group_by: string | null; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const KanBanGroupByHeaderRoot: React.FC = observer( - ({ column_id, sub_group_by, group_by, issues_count }) => ( + ({ column_id, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle }) => ( <> {group_by && group_by === "state" && ( = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {group_by && group_by === "state_detail.group" && ( @@ -37,6 +41,8 @@ export const KanBanGroupByHeaderRoot: React.FC = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {group_by && group_by === "priority" && ( @@ -46,6 +52,8 @@ export const KanBanGroupByHeaderRoot: React.FC = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {group_by && group_by === "labels" && ( @@ -55,6 +63,8 @@ export const KanBanGroupByHeaderRoot: React.FC = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {group_by && group_by === "assignees" && ( @@ -64,6 +74,8 @@ export const KanBanGroupByHeaderRoot: React.FC = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {group_by && group_by === "created_by" && ( @@ -73,6 +85,8 @@ export const KanBanGroupByHeaderRoot: React.FC = obser group_by={group_by} header_type={`group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index 135a1d1391f..21dbf66ded2 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -13,6 +13,8 @@ export interface ILabelHeader { group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } const Icon = ({ color }: any) => ( @@ -20,7 +22,7 @@ const Icon = ({ color }: any) => ( ); export const LabelHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const { project: projectStore }: RootStore = useMobxStore(); const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null; @@ -34,6 +36,8 @@ export const LabelHeader: React.FC = observer( icon={} title={label?.name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={label?.name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx index 91820530b01..d77b936321f 100644 --- a/web/components/issues/issue-layouts/kanban/headers/priority.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -8,13 +8,14 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card"; // constants import { issuePriorityByKey } from "constants/issue"; - export interface IPriorityHeader { column_id: string; sub_group_by: string | null; group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } const Icon = ({ priority }: any) => ( @@ -44,7 +45,7 @@ const Icon = ({ priority }: any) => ( ); export const PriorityHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const priority = column_id && issuePriorityByKey(column_id); return ( @@ -56,6 +57,8 @@ export const PriorityHeader: React.FC = observer( icon={} title={priority?.key || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={priority?.key || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx index 4baa64dd08c..d4efc909a0b 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -13,6 +13,8 @@ export interface IStateGroupHeader { group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => ( @@ -22,7 +24,7 @@ export const Icon = ({ stateGroup, color }: { stateGroup: any; color?: any }) => ); export const StateGroupHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const stateGroup = column_id && issueStateGroupByKey(column_id); console.log("stateGroup", stateGroup); @@ -36,6 +38,8 @@ export const StateGroupHeader: React.FC = observer( icon={} title={stateGroup?.key || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={stateGroup?.key || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx index 8f1b35f993a..f95ad3592b1 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -14,10 +14,12 @@ export interface IStateHeader { group_by: string | null; header_type: "group_by" | "sub_group_by"; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const StateHeader: React.FC = observer( - ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { + ({ column_id, sub_group_by, group_by, header_type, issues_count, kanBanToggle, handleKanBanToggle }) => { const { project: projectStore }: RootStore = useMobxStore(); const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null; @@ -31,6 +33,8 @@ export const StateHeader: React.FC = observer( icon={} title={state?.name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( = observer( icon={} title={state?.name || ""} count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ))} diff --git a/web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx index 17545c29b95..e9b0775f30f 100644 --- a/web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx @@ -3,27 +3,24 @@ import React from "react"; import { Circle, ChevronDown, ChevronUp } from "lucide-react"; // mobx import { observer } from "mobx-react-lite"; -// store -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; interface IHeaderSubGroupByCard { icon?: React.ReactNode; title: string; count: number; column_id: string; + kanBanToggle: any; + handleKanBanToggle: any; } -export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }: IHeaderSubGroupByCard) => { - const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - - return ( +export const HeaderSubGroupByCard = observer( + ({ icon, title, count, column_id, kanBanToggle, handleKanBanToggle }: IHeaderSubGroupByCard) => (
issueKanBanViewStore?.handleKanBanToggle("subgroupByIssuesVisibility", column_id)} + onClick={() => handleKanBanToggle("subgroupByIssuesVisibility", column_id)} > - {issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? ( + {kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? ( ) : ( @@ -39,5 +36,5 @@ export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }:
{count || 0}
- ); -}); + ) +); diff --git a/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx index 9f86393bec1..1ae724a2711 100644 --- a/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-root.tsx @@ -13,10 +13,12 @@ export interface IKanBanSubGroupByHeaderRoot { sub_group_by: string | null; group_by: string | null; issues_count: number; + kanBanToggle: any; + handleKanBanToggle: any; } export const KanBanSubGroupByHeaderRoot: React.FC = observer( - ({ column_id, sub_group_by, group_by, issues_count }) => ( + ({ column_id, sub_group_by, group_by, issues_count, kanBanToggle, handleKanBanToggle }) => ( <> {sub_group_by && sub_group_by === "state" && ( = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {sub_group_by && sub_group_by === "state_detail.group" && ( @@ -34,6 +38,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {sub_group_by && sub_group_by === "priority" && ( @@ -43,6 +49,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {sub_group_by && sub_group_by === "labels" && ( @@ -52,6 +60,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {sub_group_by && sub_group_by === "assignees" && ( @@ -61,6 +71,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} {sub_group_by && sub_group_by === "created_by" && ( @@ -70,6 +82,8 @@ export const KanBanSubGroupByHeaderRoot: React.FC = group_by={group_by} header_type={`sub_group_by`} issues_count={issues_count} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx index 4d72936c078..6e93e0b23d7 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -51,6 +51,10 @@ export const KanBanLayout: React.FC = observer(() => { issueStore.updateIssueStructure(group_by, sub_group_by, issue); }; + const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { + issueKanBanViewStore.handleKanBanToggle(toggle, value); + }; + return (
@@ -61,6 +65,8 @@ export const KanBanLayout: React.FC = observer(() => { group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} + kanBanToggle={issueKanBanViewStore?.kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> ) : ( { group_by={group_by} handleIssues={updateIssue} display_properties={display_properties} + kanBanToggle={issueKanBanViewStore?.kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 6f0dee695f3..baf8b36b2a7 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -17,6 +17,8 @@ interface ISubGroupSwimlaneHeader { group_by: string | null; list: any; listKey: string; + kanBanToggle: any; + handleKanBanToggle: any; } const SubGroupSwimlaneHeader: React.FC = ({ issues, @@ -24,6 +26,8 @@ const SubGroupSwimlaneHeader: React.FC = ({ group_by, list, listKey, + kanBanToggle, + handleKanBanToggle, }) => { const calculateIssueCount = (column_id: string) => { let issueCount = 0; @@ -45,6 +49,8 @@ const SubGroupSwimlaneHeader: React.FC = ({ sub_group_by={sub_group_by} group_by={group_by} issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} />
))} @@ -56,11 +62,21 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { issues: any; handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; display_properties: any; + kanBanToggle: any; + handleKanBanToggle: any; } const SubGroupSwimlane: React.FC = observer( - ({ issues, sub_group_by, group_by, list, listKey, handleIssues, display_properties }) => { - const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - + ({ + issues, + sub_group_by, + group_by, + list, + listKey, + handleIssues, + display_properties, + kanBanToggle, + handleKanBanToggle, + }) => { const calculateIssueCount = (column_id: string) => { let issueCount = 0; issues?.[column_id] && @@ -83,13 +99,13 @@ const SubGroupSwimlane: React.FC = observer( sub_group_by={sub_group_by} group_by={group_by} issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} />
- {!issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes( - getValueFromObject(_list, listKey) as string - ) && ( + {!kanBanToggle?.subgroupByIssuesVisibility.includes(getValueFromObject(_list, listKey) as string) && (
= observer( sub_group_id={getValueFromObject(_list, listKey) as string} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} />
)} @@ -114,10 +132,12 @@ export interface IKanBanSwimLanes { group_by: string | null; handleIssues?: (sub_group_by: string | null, group_by: string | null, issue: any) => void; display_properties: any; + kanBanToggle: any; + handleKanBanToggle: any; } export const KanBanSwimLanes: React.FC = observer( - ({ issues, sub_group_by, group_by, handleIssues, display_properties }) => { + ({ issues, sub_group_by, group_by, handleIssues, display_properties, kanBanToggle, handleKanBanToggle }) => { const { project: projectStore }: RootStore = useMobxStore(); return ( @@ -130,6 +150,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={projectStore?.projectStates} listKey={`id`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -140,6 +162,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={ISSUE_STATE_GROUPS} listKey={`key`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -150,6 +174,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={ISSUE_PRIORITIES} listKey={`key`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -160,6 +186,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={projectStore?.projectLabels} listKey={`id`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -170,6 +198,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={projectStore?.projectMembers} listKey={`member.id`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -180,6 +210,8 @@ export const KanBanSwimLanes: React.FC = observer( group_by={group_by} list={projectStore?.projectMembers} listKey={`member.id`} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )}
@@ -193,6 +225,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`id`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -205,6 +239,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`key`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -217,6 +253,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`key`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -229,6 +267,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`id`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -241,6 +281,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`member.id`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )} @@ -253,6 +295,8 @@ export const KanBanSwimLanes: React.FC = observer( listKey={`member.id`} handleIssues={handleIssues} display_properties={display_properties} + kanBanToggle={kanBanToggle} + handleKanBanToggle={handleKanBanToggle} /> )}
diff --git a/web/components/issues/issue-layouts/list/cycle-root.tsx b/web/components/issues/issue-layouts/list/cycle-root.tsx index 8f630d692ac..82755893631 100644 --- a/web/components/issues/issue-layouts/list/cycle-root.tsx +++ b/web/components/issues/issue-layouts/list/cycle-root.tsx @@ -10,16 +10,16 @@ import { RootStore } from "store/root"; export interface ICycleListLayout {} export const CycleListLayout: React.FC = observer(() => { - const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + const { issueFilter: issueFilterStore, cycleIssue: cycleIssueStore }: RootStore = useMobxStore(); - const issues = issueStore?.getIssues; + const issues = cycleIssueStore?.getIssues; const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; const display_properties = issueFilterStore?.userDisplayProperties || null; const updateIssue = (group_by: string | null, issue: any) => { - issueStore.updateIssueStructure(group_by, null, issue); + cycleIssueStore.updateIssueStructure(group_by, null, issue); }; return ( diff --git a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx index d924398e27a..5a615d935b1 100644 --- a/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx +++ b/web/pages/[workspaceSlug]/projects/[projectId]/cycles/[cycleId].tsx @@ -164,17 +164,17 @@ const SingleCycle: React.FC = () => { ) : ( <> setTransferIssuesModal(false)} isOpen={transferIssuesModal} /> + setAnalyticsModal(false)} /> +
{cycleStatus === "completed" && setTransferIssuesModal(true)} />} -
- -
+
Promise; - updateUserFilters: ( - workspaceSlug: string, - projectId: string, - filterToUpdate: Partial - ) => Promise; - updateDisplayProperties: ( - workspaceSlug: string, - projectId: string, - properties: Partial - ) => Promise; - - // computed - appliedFilters: TIssueParams[] | null; -} - -class CycleFilterStore implements ICycleFilterStore { - loader: boolean = false; - error: any | null = null; - - // observables - userDisplayProperties: any = {}; - userDisplayFilters: IIssueDisplayFilterOptions = {}; - userFilters: IIssueFilterOptions = {}; - defaultDisplayFilters: IIssueDisplayFilterOptions = {}; - defaultFilters: IIssueFilterOptions = {}; - defaultDisplayProperties: IIssueDisplayProperties = { - assignee: true, - start_date: true, - due_date: true, - labels: true, - key: true, - priority: true, - state: true, - sub_issue_count: true, - link: true, - attachment_count: true, - estimate: true, - created_on: true, - updated_on: true, - }; - - // root store - rootStore; - - // services - projectService; - issueService; - - constructor(_rootStore: RootStore) { - makeObservable(this, { - loader: observable.ref, - error: observable.ref, - - // observables - defaultDisplayFilters: observable.ref, - defaultFilters: observable.ref, - userDisplayProperties: observable.ref, - userDisplayFilters: observable.ref, - userFilters: observable.ref, - - // actions - fetchUserProjectFilters: action, - updateUserFilters: action, - updateDisplayProperties: action, - - // computed - appliedFilters: computed, - }); - - this.rootStore = _rootStore; - - this.projectService = new ProjectService(); - this.issueService = new IssueService(); - } - - computedFilter = (filters: any, filteredParams: any) => { - const computedFilters: any = {}; - Object.keys(filters).map((key) => { - if (filters[key] != undefined && filteredParams.includes(key)) - computedFilters[key] = - typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); - }); - - return computedFilters; - }; - - get appliedFilters(): TIssueParams[] | null { - if (!this.userFilters || !this.userDisplayFilters) return null; - - let filteredRouteParams: any = { - priority: this.userFilters?.priority || undefined, - state_group: this.userFilters?.state_group || undefined, - state: this.userFilters?.state || undefined, - assignees: this.userFilters?.assignees || undefined, - created_by: this.userFilters?.created_by || undefined, - labels: this.userFilters?.labels || undefined, - start_date: this.userFilters?.start_date || undefined, - target_date: this.userFilters?.target_date || undefined, - group_by: this.userDisplayFilters?.group_by || "state", - order_by: this.userDisplayFilters?.order_by || "-created_at", - sub_group_by: this.userDisplayFilters?.sub_group_by || undefined, - type: this.userDisplayFilters?.type || undefined, - sub_issue: this.userDisplayFilters?.sub_issue || true, - show_empty_groups: this.userDisplayFilters?.show_empty_groups || true, - start_target_date: this.userDisplayFilters?.start_target_date || true, - }; - - const filteredParams = handleIssueQueryParamsByLayout(this.userDisplayFilters.layout, "issues"); - if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); - - if (this.userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date"; - if (this.userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true; - - return filteredRouteParams; - } - - fetchUserProjectFilters = async (workspaceSlug: string, projectId: string) => { - try { - const memberResponse = await this.projectService.projectMemberMe(workspaceSlug, projectId); - const issueProperties = await this.issueService.getIssueProperties(workspaceSlug, projectId); - - runInAction(() => { - this.userFilters = memberResponse?.view_props?.filters; - this.userDisplayFilters = memberResponse?.view_props?.display_filters ?? {}; - this.userDisplayProperties = issueProperties?.properties || this.defaultDisplayProperties; - // default props from api - this.defaultFilters = memberResponse.default_props.filters; - this.defaultDisplayFilters = memberResponse.default_props.display_filters ?? {}; - }); - } catch (error) { - runInAction(() => { - this.error = error; - }); - - console.log("Failed to fetch user filters in issue filter store", error); - } - }; - - updateUserFilters = async (workspaceSlug: string, projectId: string, filterToUpdate: Partial) => { - const newViewProps = { - display_filters: { - ...this.userDisplayFilters, - ...filterToUpdate.display_filters, - }, - filters: { - ...this.userFilters, - ...filterToUpdate.filters, - }, - }; - - // set sub_group_by to null if group_by is set to null - if (newViewProps.display_filters.group_by === null) newViewProps.display_filters.sub_group_by = null; - - // set group_by to state if layout is switched to kanban and group_by is null - if (newViewProps.display_filters.layout === "kanban" && newViewProps.display_filters.group_by === null) - newViewProps.display_filters.group_by = "state"; - - try { - runInAction(() => { - this.userFilters = newViewProps.filters; - this.userDisplayFilters = newViewProps.display_filters; - }); - - this.projectService.setProjectView(workspaceSlug, projectId, { - view_props: newViewProps, - }); - } catch (error) { - this.fetchUserProjectFilters(workspaceSlug, projectId); - - runInAction(() => { - this.error = error; - }); - - console.log("Failed to update user filters in issue filter store", error); - } - }; - - updateDisplayProperties = async ( - workspaceSlug: string, - projectId: string, - properties: Partial - ) => { - const newProperties = { - ...this.userDisplayProperties, - ...properties, - }; - - try { - runInAction(() => { - this.userDisplayProperties = newProperties; - }); - - // await this.issueService.patchIssueProperties(workspaceSlug, projectId, newProperties); - } catch (error) { - this.fetchUserProjectFilters(workspaceSlug, projectId); - - runInAction(() => { - this.error = error; - }); - - console.log("Failed to update user filters in issue filter store", error); - } - }; -} - -export default CycleFilterStore; diff --git a/web/store/cycle_issue.ts b/web/store/cycle_issue.ts index 62f7f6f02de..2933e718ab9 100644 --- a/web/store/cycle_issue.ts +++ b/web/store/cycle_issue.ts @@ -75,8 +75,9 @@ class CycleIssueStore implements ICycleIssueStore { const groupedLayouts = ["kanban", "list", "calendar"]; const ungroupedLayouts = ["spreadsheet", "gantt_chart"]; - const issueLayout = this.rootStore?.cycleFilter?.userDisplayFilters?.layout || null; - const issueSubGroup = this.rootStore?.cycleFilter?.userDisplayFilters?.sub_group_by || null; + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null; + if (!issueLayout) return null; const _issueState = groupedLayouts.includes(issueLayout) @@ -145,10 +146,13 @@ class CycleIssueStore implements ICycleIssueStore { this.rootStore.project.setProjectId(projectId); this.rootStore.cycle.setCycleId(cycleId); - const params = this.rootStore?.cycleFilter?.appliedFilters; + const params = this.rootStore?.cycleIssueFilter?.appliedFilters; console.log("params", params); + console.log("coming here..."); const issueResponse = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); + console.log("coming here also..."); + console.log("issueResponse", issueResponse); const issueType = this.getIssueType; if (issueType != null) { diff --git a/web/store/cycle_issue_filters.ts b/web/store/cycle_issue_filters.ts new file mode 100644 index 00000000000..e1950776cb8 --- /dev/null +++ b/web/store/cycle_issue_filters.ts @@ -0,0 +1,156 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// services +import { CycleService } from "services/cycles.service"; +// helpers +import { handleIssueQueryParamsByLayout } from "helpers/issue.helper"; +// types +import { RootStore } from "./root"; +import { IIssueFilterOptions, TIssueParams } from "types"; + +export interface ICycleIssueFilterStore { + loader: boolean; + error: any | null; + cycleFilters: IIssueFilterOptions; + + // action + fetchCycleFilters: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + updateCycleFilters: ( + workspaceSlug: string, + projectId: string, + cycleId: string, + filterToUpdate: Partial + ) => Promise; + + // computed + appliedFilters: TIssueParams[] | null; +} + +class CycleIssueFilterStore implements ICycleIssueFilterStore { + // observables + loader: boolean = false; + error: any | null = null; + cycleFilters: IIssueFilterOptions = {}; + // root store + rootStore; + // services + cycleService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observables + loader: observable.ref, + error: observable.ref, + cycleFilters: observable.ref, + // computed + appliedFilters: computed, + // actions + fetchCycleFilters: action, + updateCycleFilters: action, + }); + + this.rootStore = _rootStore; + this.cycleService = new CycleService(); + } + + computedFilter = (filters: any, filteredParams: any) => { + const computedFilters: any = {}; + Object.keys(filters).map((key) => { + if (filters[key] != undefined && filteredParams.includes(key)) + computedFilters[key] = + typeof filters[key] === "string" || typeof filters[key] === "boolean" ? filters[key] : filters[key].join(","); + }); + + return computedFilters; + }; + + get appliedFilters(): TIssueParams[] | null { + const userDisplayFilters = this.rootStore?.issueFilter?.userDisplayFilters; + + console.log("userDisplayFilters", userDisplayFilters); + console.log("this.cycleFilters", this.cycleFilters); + + if (!this.cycleFilters || !userDisplayFilters) return null; + + let filteredRouteParams: any = { + priority: this.cycleFilters?.priority || undefined, + state_group: this.cycleFilters?.state_group || undefined, + state: this.cycleFilters?.state || undefined, + assignees: this.cycleFilters?.assignees || undefined, + created_by: this.cycleFilters?.created_by || undefined, + labels: this.cycleFilters?.labels || undefined, + start_date: this.cycleFilters?.start_date || undefined, + target_date: this.cycleFilters?.target_date || undefined, + group_by: userDisplayFilters?.group_by || "state", + order_by: userDisplayFilters?.order_by || "-created_at", + sub_group_by: userDisplayFilters?.sub_group_by || undefined, + type: userDisplayFilters?.type || undefined, + sub_issue: userDisplayFilters?.sub_issue || true, + show_empty_groups: userDisplayFilters?.show_empty_groups || true, + start_target_date: userDisplayFilters?.start_target_date || true, + }; + + console.log("----"); + console.log("filteredRouteParams", filteredRouteParams); + const filteredParams = handleIssueQueryParamsByLayout(userDisplayFilters.layout, "issues"); + console.log("filteredParams", filteredParams); + console.log("----"); + + if (filteredParams) filteredRouteParams = this.computedFilter(filteredRouteParams, filteredParams); + + if (userDisplayFilters.layout === "calendar") filteredRouteParams.group_by = "target_date"; + if (userDisplayFilters.layout === "gantt_chart") filteredRouteParams.start_target_date = true; + + return filteredRouteParams; + } + + fetchCycleFilters = async (workspaceSlug: string, projectId: string, cycleId: string) => { + try { + const cycleResponse = await this.cycleService.getCycleDetails(workspaceSlug, projectId, cycleId); + runInAction(() => { + this.cycleFilters = cycleResponse?.view_props?.filters || {}; + }); + } catch (error) { + runInAction(() => { + this.error = error; + }); + + console.log("Failed to fetch user filters in issue filter store", error); + } + }; + + updateCycleFilters = async ( + workspaceSlug: string, + projectId: string, + cycleId: string, + properties: Partial + ) => { + const newProperties = { + ...this.cycleFilters, + ...properties, + }; + + try { + runInAction(() => { + this.cycleFilters = newProperties; + }); + + const payload = { + view_props: { + filters: newProperties, + }, + }; + + const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, payload, undefined); + } catch (error) { + this.fetchCycleFilters(workspaceSlug, projectId, cycleId); + + runInAction(() => { + this.error = error; + }); + + console.log("Failed to update user filters in issue filter store", error); + } + }; +} + +export default CycleIssueFilterStore; diff --git a/web/store/cycle_kanban_view.ts b/web/store/cycle_issue_kanban_view.ts similarity index 95% rename from web/store/cycle_kanban_view.ts rename to web/store/cycle_issue_kanban_view.ts index d97b997f1ed..cccb375ec7d 100644 --- a/web/store/cycle_kanban_view.ts +++ b/web/store/cycle_issue_kanban_view.ts @@ -3,7 +3,7 @@ import { action, computed, makeObservable, observable, runInAction } from "mobx" import { RootStore } from "./root"; import { IIssueType } from "./issue"; -export interface ICycleKanBanViewStore { +export interface ICycleIssueKanBanViewStore { kanBanToggle: { groupByHeaderMinMax: string[]; subgroupByIssuesVisibility: string[]; @@ -18,7 +18,7 @@ export interface ICycleKanBanViewStore { handleDragDrop: (source: any, destination: any) => void; } -class CycleKanBanViewStore implements ICycleKanBanViewStore { +class CycleIssueKanBanViewStore implements ICycleIssueKanBanViewStore { kanBanToggle: { groupByHeaderMinMax: string[]; subgroupByIssuesVisibility: string[]; @@ -82,7 +82,7 @@ class CycleKanBanViewStore implements ICycleKanBanViewStore { const projectId = this.rootStore?.project?.projectId; const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; - const currentIssues: any = this.rootStore.issue.getIssues; + const currentIssues: any = this.rootStore.cycleIssue?.getIssues; const sortOrderDefaultValue = 65535; @@ -235,18 +235,18 @@ class CycleKanBanViewStore implements ICycleKanBanViewStore { } const reorderedIssues = { - ...this.rootStore?.issue.issues, + ...this.rootStore?.cycleIssue.issues, [projectId]: { - ...this.rootStore?.issue.issues?.[projectId], + ...this.rootStore?.cycleIssue.issues?.[projectId], [issueType]: { - ...this.rootStore?.issue.issues?.[projectId]?.[issueType], + ...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType], [issueType]: currentIssues, }, }, }; runInAction(() => { - this.rootStore.issue.issues = { ...reorderedIssues }; + this.rootStore.cycleIssue.issues = { ...reorderedIssues }; }); // console.log("updateIssue", updateIssue); @@ -266,7 +266,7 @@ class CycleKanBanViewStore implements ICycleKanBanViewStore { const projectId = this.rootStore?.project?.projectId; const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; - const currentIssues: any = this.rootStore.issue.getIssues; + const currentIssues: any = this.rootStore.cycleIssue?.getIssues; const sortOrderDefaultValue = 65535; @@ -376,18 +376,18 @@ class CycleKanBanViewStore implements ICycleKanBanViewStore { } const reorderedIssues = { - ...this.rootStore?.issue.issues, + ...this.rootStore?.cycleIssue.issues, [projectId]: { - ...this.rootStore?.issue.issues?.[projectId], + ...this.rootStore?.cycleIssue.issues?.[projectId], [issueType]: { - ...this.rootStore?.issue.issues?.[projectId]?.[issueType], + ...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType], [issueType]: currentIssues, }, }, }; runInAction(() => { - this.rootStore.issue.issues = { ...reorderedIssues }; + this.rootStore.cycleIssue.issues = { ...reorderedIssues }; }); this.rootStore.issueDetail?.updateIssue( @@ -401,4 +401,4 @@ class CycleKanBanViewStore implements ICycleKanBanViewStore { }; } -export default CycleKanBanViewStore; +export default CycleIssueKanBanViewStore; diff --git a/web/store/cycles.ts b/web/store/cycles.ts index 3e55ff944c6..d94c2fbddb8 100644 --- a/web/store/cycles.ts +++ b/web/store/cycles.ts @@ -30,7 +30,7 @@ export interface ICycleStore { projectId: string, params: "all" | "current" | "upcoming" | "draft" | "completed" | "incomplete" ) => Promise; - fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; + fetchCycleWithId: (workspaceSlug: string, projectId: string, cycleId: string) => Promise; createCycle: (workspaceSlug: string, projectId: string, data: any) => Promise; updateCycle: (workspaceSlug: string, projectId: string, cycleId: string, data: any) => Promise; @@ -137,7 +137,7 @@ class CycleStore implements ICycleStore { [response?.id]: response, }; }); - + return response; } catch (error) { console.log("Failed to fetch cycle detail from cycle store"); throw error; @@ -171,16 +171,21 @@ class CycleStore implements ICycleStore { try { const response = await this.cycleService.updateCycle(workspaceSlug, projectId, cycleId, data, undefined); - console.log("response", response); + const _cycles = { + ...this.cycles, + [projectId]: this.cycles[projectId].map((cycle) => { + if (cycle.id === cycleId) return { ...cycle, ...response }; + return cycle; + }), + }; + const _cycleDetails = { + ...this.cycle_details, + [cycleId]: { ...this.cycle_details[cycleId], ...response }, + }; runInAction(() => { - this.cycles = { - ...this.cycles, - [projectId]: this.cycles[projectId].map((cycle) => { - if (cycle.id === cycleId) return { ...cycle, ...response }; - return cycle; - }), - }; + this.cycles = _cycles; + this.cycle_details = _cycleDetails; }); return response; diff --git a/web/store/root.ts b/web/store/root.ts index 69723082b23..89d07e38dd9 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -11,8 +11,8 @@ import ProjectStore, { IProjectStore } from "./project"; import ModuleStore, { IModuleStore } from "./modules"; import CycleStore, { ICycleStore } from "./cycles"; import CycleIssueStore, { ICycleIssueStore } from "./cycle_issue"; -import CycleFilterStore, { ICycleFilterStore } from "./cycle_filters"; -import CycleKanBanViewStore, { ICycleKanBanViewStore } from "./cycle_kanban_view"; +import CycleIssueFilterStore, { ICycleIssueFilterStore } from "./cycle_issue_filters"; +import CycleIssueKanBanViewStore, { ICycleIssueKanBanViewStore } from "./cycle_issue_kanban_view"; import ViewStore, { IViewStore } from "./views"; import IssueFilterStore, { IIssueFilterStore } from "./issue_filters"; import IssueViewDetailStore from "./issue_detail"; @@ -35,8 +35,8 @@ export class RootStore { cycle: ICycleStore; cycleIssue: ICycleIssueStore; - cycleFilter: ICycleFilterStore; - cycleKanBanView: ICycleKanBanViewStore; + cycleIssueFilter: ICycleIssueFilterStore; + cycleIssueKanBanView: ICycleIssueKanBanViewStore; view: IViewStore; issueFilter: IIssueFilterStore; @@ -55,8 +55,8 @@ export class RootStore { this.cycle = new CycleStore(this); this.cycleIssue = new CycleIssueStore(this); - this.cycleFilter = new CycleFilterStore(this); - this.cycleKanBanView = new CycleKanBanViewStore(this); + this.cycleIssueFilter = new CycleIssueFilterStore(this); + this.cycleIssueKanBanView = new CycleIssueKanBanViewStore(this); this.view = new ViewStore(this); this.issue = new IssueStore(this); From cb69d01e4c67702fad886a3f62354e72938567ed Mon Sep 17 00:00:00 2001 From: gurusainath Date: Mon, 9 Oct 2023 14:26:23 +0530 Subject: [PATCH 3/3] chore: cycles, modules kanban and list, kanban view store --- .../issues/issue-layouts/kanban/default.tsx | 68 +-- web/constants/kanban-helpers.ts | 19 + web/store/cycle_issue.ts | 37 +- web/store/cycle_issue_kanban_view.ts | 2 +- web/store/issue.ts | 15 +- web/store/kanban_view.ts | 103 +++-- web/store/module_issue.ts | 197 +++++++++ web/store/module_issue_kanban_view.ts | 404 ++++++++++++++++++ web/store/root.ts | 10 +- 9 files changed, 777 insertions(+), 78 deletions(-) create mode 100644 web/constants/kanban-helpers.ts create mode 100644 web/store/module_issue.ts create mode 100644 web/store/module_issue_kanban_view.ts diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index a38c51627e8..e1400ab7eb6 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -62,39 +62,41 @@ const GroupByKanBan: React.FC = observer(
)} - {!verticalAlignPosition(_list) && ( -
- - {(provided: any, snapshot: any) => ( -
- {issues ? ( - - ) : ( - isDragDisabled && ( -
- {/*
Drop here
*/} -
- ) - )} - {provided.placeholder} -
- )} -
-
- )} +
+ + {(provided: any, snapshot: any) => ( +
+ {issues ? ( + + ) : ( + isDragDisabled && ( +
+ {/*
Drop here
*/} +
+ ) + )} + {provided.placeholder} +
+ )} +
+
))}
diff --git a/web/constants/kanban-helpers.ts b/web/constants/kanban-helpers.ts new file mode 100644 index 00000000000..b6c4927e1fe --- /dev/null +++ b/web/constants/kanban-helpers.ts @@ -0,0 +1,19 @@ +export const sortArrayByDate = (_array: any[], _key: string): any[] => { + console.log("date sorting"); + // return _array.sort((a, b) => { + // const x = new Date(a[_key]); + // const y = new Date(b[_key]); + // return x > y ? -1 : x < y ? 1 : 0; + // }); + return _array; +}; + +export const sortArrayByPriority = (_array: any[], _key: string): any[] => { + console.log("priority sorting"); + // return _array.sort((a, b) => { + // const x = new Date(a[_key]); + // const y = new Date(b[_key]); + // return x > y ? -1 : x < y ? 1 : 0; + // }); + return _array; +}; diff --git a/web/store/cycle_issue.ts b/web/store/cycle_issue.ts index 2933e718ab9..b8bf0e2245d 100644 --- a/web/store/cycle_issue.ts +++ b/web/store/cycle_issue.ts @@ -5,6 +5,8 @@ import { RootStore } from "./root"; import { IIssue } from "types"; // services import { CycleService } from "services/cycles.service"; +// constants +import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers"; export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped"; export type IIssueGroupedStructure = { [group_id: string]: IIssue[] }; @@ -92,17 +94,17 @@ class CycleIssueStore implements ICycleIssueStore { } get getIssues() { - const projectId: string | null = this.rootStore?.project?.projectId; + const cycleId: string | null = this.rootStore?.cycle?.cycleId; const issueType = this.getIssueType; - if (!projectId || !issueType) return null; + if (!cycleId || !issueType) return null; - return this.issues?.[projectId]?.[issueType] || null; + return this.issues?.[cycleId]?.[issueType] || null; } updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => { - const projectId: string | null = issue?.project; + const cycleId: string | null = this.rootStore?.cycle?.cycleId || null; const issueType = this.getIssueType; - if (!projectId || !issueType) return null; + if (!cycleId || !issueType) return null; let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null = this.getIssues; @@ -130,10 +132,22 @@ class CycleIssueStore implements ICycleIssueStore { issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)); } - // reorder issues based on the issue update + const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || ""; + if (orderBy === "-created_at") { + issues = sortArrayByDate(issues as any, "created_at"); + } + if (orderBy === "-updated_at") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "start_date") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "priority") { + issues = sortArrayByPriority(issues as any, "priority"); + } runInAction(() => { - this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } }; + this.issues = { ...this.issues, [cycleId]: { ...this.issues[cycleId], [issueType]: issues } }; }); }; @@ -147,19 +161,14 @@ class CycleIssueStore implements ICycleIssueStore { this.rootStore.cycle.setCycleId(cycleId); const params = this.rootStore?.cycleIssueFilter?.appliedFilters; - console.log("params", params); - - console.log("coming here..."); const issueResponse = await this.cycleService.getCycleIssuesWithParams(workspaceSlug, projectId, cycleId, params); - console.log("coming here also..."); - console.log("issueResponse", issueResponse); const issueType = this.getIssueType; if (issueType != null) { const _issues = { ...this.issues, - [projectId]: { - ...this.issues[projectId], + [cycleId]: { + ...this.issues[cycleId], [issueType]: issueResponse, }, }; diff --git a/web/store/cycle_issue_kanban_view.ts b/web/store/cycle_issue_kanban_view.ts index cccb375ec7d..2a4bce8a732 100644 --- a/web/store/cycle_issue_kanban_view.ts +++ b/web/store/cycle_issue_kanban_view.ts @@ -50,7 +50,7 @@ class CycleIssueKanBanViewStore implements ICycleIssueKanBanViewStore { this.rootStore?.issueFilter?.userDisplayFilters?.group_by && ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) ) { - if (this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by === null) return true; + if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true; if ( this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by && ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) diff --git a/web/store/issue.ts b/web/store/issue.ts index 7e578651915..06a4dd16a78 100644 --- a/web/store/issue.ts +++ b/web/store/issue.ts @@ -5,6 +5,7 @@ import { RootStore } from "./root"; import { IIssue } from "types"; // services import { IssueService } from "services/issue.service"; +import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers"; export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped"; export type IIssueGroupedStructure = { [group_id: string]: IIssue[] }; @@ -129,7 +130,19 @@ class IssueStore implements IIssueStore { issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)); } - // reorder issues based on the issue update + const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || ""; + if (orderBy === "-created_at") { + issues = sortArrayByDate(issues as any, "created_at"); + } + if (orderBy === "-updated_at") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "start_date") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "priority") { + issues = sortArrayByPriority(issues as any, "priority"); + } runInAction(() => { this.issues = { ...this.issues, [projectId]: { ...this.issues[projectId], [issueType]: issues } }; diff --git a/web/store/kanban_view.ts b/web/store/kanban_view.ts index 946a749a770..f818c6036e5 100644 --- a/web/store/kanban_view.ts +++ b/web/store/kanban_view.ts @@ -50,7 +50,7 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { this.rootStore?.issueFilter?.userDisplayFilters?.group_by && ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) ) { - if (this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by === null) return true; + if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true; if ( this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by && ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) @@ -166,20 +166,30 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { }; } + let issueStatePriority = {}; + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") { + updateIssue = { ...updateIssue, state: destination_group_id }; + issueStatePriority = { ...issueStatePriority, state: destination_group_id }; + } + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") { + updateIssue = { ...updateIssue, priority: destination_group_id }; + issueStatePriority = { ...issueStatePriority, priority: destination_group_id }; + } + const [removed] = _sourceIssues.splice(source.index, 1); if (_destinationIssues && _destinationIssues.length > 0) _destinationIssues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order, + ...issueStatePriority, }); - else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + else + _destinationIssues = [ + ..._destinationIssues, + { ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority }, + ]; updateIssue = { ...updateIssue, issueId: removed?.id }; - // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") - // updateIssue = { ...updateIssue, state: destination_group_id }; - // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") - // updateIssue = { ...updateIssue, priority: destination_group_id }; - currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; } @@ -216,22 +226,51 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { }; } + let issueStatePriority = {}; + if (source_group_id === destination_group_id) { + if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") { + updateIssue = { ...updateIssue, state: destination_sub_group_id }; + issueStatePriority = { ...issueStatePriority, state: destination_sub_group_id }; + } + if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") { + updateIssue = { ...updateIssue, priority: destination_sub_group_id }; + issueStatePriority = { ...issueStatePriority, priority: destination_sub_group_id }; + } + } else { + if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "state") { + updateIssue = { ...updateIssue, state: destination_sub_group_id, priority: destination_group_id }; + issueStatePriority = { + ...issueStatePriority, + state: destination_sub_group_id, + priority: destination_group_id, + }; + } + if (this.rootStore.issueFilter?.userDisplayFilters?.sub_group_by === "priority") { + updateIssue = { ...updateIssue, state: destination_group_id, priority: destination_sub_group_id }; + issueStatePriority = { + ...issueStatePriority, + state: destination_group_id, + priority: destination_sub_group_id, + }; + } + } + const [removed] = _sourceIssues.splice(source.index, 1); if (_destinationIssues && _destinationIssues.length > 0) _destinationIssues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order, + ...issueStatePriority, }); - else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + else + _destinationIssues = [ + ..._destinationIssues, + { ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority }, + ]; updateIssue = { ...updateIssue, issueId: removed?.id }; currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; - - // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") - // updateIssue = { ...updateIssue, state: destination_group_id }; - // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") - // updateIssue = { ...updateIssue, priority: destination_group_id }; } const reorderedIssues = { @@ -249,15 +288,13 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { this.rootStore.issue.issues = { ...reorderedIssues }; }); - // console.log("updateIssue", updateIssue); - - // this.rootStore.issueDetail?.updateIssue( - // updateIssue.workspaceSlug, - // updateIssue.projectId, - // updateIssue.issueId, - // updateIssue, - // undefined - // ); + this.rootStore.issueDetail?.updateIssue( + updateIssue.workspaceSlug, + updateIssue.projectId, + updateIssue.issueId, + updateIssue, + undefined + ); } }; @@ -348,23 +385,33 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { }; } + let issueStatePriority = {}; + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") { + updateIssue = { ...updateIssue, state: destination_group_id }; + issueStatePriority = { ...issueStatePriority, state: destination_group_id }; + } + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") { + updateIssue = { ...updateIssue, priority: destination_group_id }; + issueStatePriority = { ...issueStatePriority, priority: destination_group_id }; + } + const [removed] = _sourceIssues.splice(source.index, 1); if (_destinationIssues && _destinationIssues.length > 0) _destinationIssues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order, + ...issueStatePriority, }); - else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + else + _destinationIssues = [ + ..._destinationIssues, + { ...removed, sort_order: updateIssue.sort_order, ...issueStatePriority }, + ]; updateIssue = { ...updateIssue, issueId: removed?.id }; currentIssues[source_group_id] = _sourceIssues; currentIssues[destination_group_id] = _destinationIssues; } - - if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") - updateIssue = { ...updateIssue, state: destination_group_id }; - if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") - updateIssue = { ...updateIssue, priority: destination_group_id }; } // user can drag the issues only vertically diff --git a/web/store/module_issue.ts b/web/store/module_issue.ts new file mode 100644 index 00000000000..2da27badc65 --- /dev/null +++ b/web/store/module_issue.ts @@ -0,0 +1,197 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// store +import { RootStore } from "./root"; +// types +import { IIssue } from "types"; +// services +import { ModuleService } from "services/modules.service"; +// helpers +import { sortArrayByDate, sortArrayByPriority } from "constants/kanban-helpers"; + +export type IIssueType = "grouped" | "groupWithSubGroups" | "ungrouped"; +export type IIssueGroupedStructure = { [group_id: string]: IIssue[] }; +export type IIssueGroupWithSubGroupsStructure = { + [group_id: string]: { + [sub_group_id: string]: IIssue[]; + }; +}; +export type IIssueUnGroupedStructure = IIssue[]; + +export interface IModuleIssueStore { + loader: boolean; + error: any | null; + // issues + issues: { + [module_id: string]: { + grouped: IIssueGroupedStructure; + groupWithSubGroups: IIssueGroupWithSubGroupsStructure; + ungrouped: IIssueUnGroupedStructure; + }; + }; + // computed + getIssueType: IIssueType | null; + getIssues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null; + // action + fetchIssues: (workspaceSlug: string, projectId: string, moduleId: string) => Promise; + updateIssueStructure: (group_id: string | null, sub_group_id: string | null, issue: IIssue) => void; +} + +class ModuleIssueStore implements IModuleIssueStore { + loader: boolean = false; + error: any | null = null; + issues: { + [module_id: string]: { + grouped: { + [group_id: string]: IIssue[]; + }; + groupWithSubGroups: { + [group_id: string]: { + [sub_group_id: string]: IIssue[]; + }; + }; + ungrouped: IIssue[]; + }; + } = {}; + // service + moduleService; + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + loader: observable.ref, + error: observable.ref, + issues: observable.ref, + // computed + getIssueType: computed, + getIssues: computed, + // actions + fetchIssues: action, + updateIssueStructure: action, + }); + this.rootStore = _rootStore; + this.moduleService = new ModuleService(); + } + + get getIssueType() { + const groupedLayouts = ["kanban", "list", "calendar"]; + const ungroupedLayouts = ["spreadsheet", "gantt_chart"]; + + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const issueSubGroup = this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by || null; + + if (!issueLayout) return null; + + const _issueState = groupedLayouts.includes(issueLayout) + ? issueSubGroup + ? "groupWithSubGroups" + : "grouped" + : ungroupedLayouts.includes(issueLayout) + ? "ungrouped" + : null; + + return _issueState || null; + } + + get getIssues() { + const moduleId: string | null = this.rootStore?.module?.moduleId; + const issueType = this.getIssueType; + if (!moduleId || !issueType) return null; + + return this.issues?.[moduleId]?.[issueType] || null; + } + + updateIssueStructure = async (group_id: string | null, sub_group_id: string | null, issue: IIssue) => { + const moduleId: string | null = this.rootStore?.module?.moduleId; + const issueType = this.getIssueType; + if (!moduleId || !issueType) return null; + + let issues: IIssueGroupedStructure | IIssueGroupWithSubGroupsStructure | IIssueUnGroupedStructure | null = + this.getIssues; + if (!issues) return null; + + if (issueType === "grouped" && group_id) { + issues = issues as IIssueGroupedStructure; + issues = { + ...issues, + [group_id]: issues[group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)), + }; + } + if (issueType === "groupWithSubGroups" && group_id && sub_group_id) { + issues = issues as IIssueGroupWithSubGroupsStructure; + issues = { + ...issues, + [sub_group_id]: { + ...issues[sub_group_id], + [group_id]: issues[sub_group_id][group_id].map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)), + }, + }; + } + if (issueType === "ungrouped") { + issues = issues as IIssueUnGroupedStructure; + issues = issues.map((i: IIssue) => (i?.id === issue?.id ? { ...i, ...issue } : i)); + } + + const orderBy = this.rootStore?.issueFilter?.userDisplayFilters?.order_by || ""; + if (orderBy === "-created_at") { + issues = sortArrayByDate(issues as any, "created_at"); + } + if (orderBy === "-updated_at") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "start_date") { + issues = sortArrayByDate(issues as any, "updated_at"); + } + if (orderBy === "priority") { + issues = sortArrayByPriority(issues as any, "priority"); + } + + runInAction(() => { + this.issues = { ...this.issues, [moduleId]: { ...this.issues[moduleId], [issueType]: issues } }; + }); + }; + + fetchIssues = async (workspaceSlug: string, projectId: string, moduleId: string) => { + try { + this.loader = true; + this.error = null; + + this.rootStore.workspace.setWorkspaceSlug(workspaceSlug); + this.rootStore.project.setProjectId(projectId); + this.rootStore.module.setModuleId(moduleId); + + const params = this.rootStore?.cycleIssueFilter?.appliedFilters; + const issueResponse = await this.moduleService.getModuleIssuesWithParams( + workspaceSlug, + projectId, + moduleId, + params + ); + + const issueType = this.getIssueType; + if (issueType != null) { + const _issues = { + ...this.issues, + [moduleId]: { + ...this.issues[moduleId], + [issueType]: issueResponse, + }, + }; + runInAction(() => { + this.issues = _issues; + this.loader = false; + this.error = null; + }); + } + + return issueResponse; + } catch (error) { + console.error("Error: Fetching error in issues", error); + this.loader = false; + this.error = error; + return error; + } + }; +} + +export default ModuleIssueStore; diff --git a/web/store/module_issue_kanban_view.ts b/web/store/module_issue_kanban_view.ts new file mode 100644 index 00000000000..c34be7bffd8 --- /dev/null +++ b/web/store/module_issue_kanban_view.ts @@ -0,0 +1,404 @@ +import { action, computed, makeObservable, observable, runInAction } from "mobx"; +// types +import { RootStore } from "./root"; +import { IIssueType } from "./issue"; + +export interface IModuleIssueKanBanViewStore { + kanBanToggle: { + groupByHeaderMinMax: string[]; + subgroupByIssuesVisibility: string[]; + }; + // computed + canUserDragDrop: boolean; + canUserDragDropVertically: boolean; + canUserDragDropHorizontally: boolean; + // actions + handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void; + handleSwimlaneDragDrop: (source: any, destination: any) => void; + handleDragDrop: (source: any, destination: any) => void; +} + +class ModuleIssueKanBanViewStore implements IModuleIssueKanBanViewStore { + kanBanToggle: { + groupByHeaderMinMax: string[]; + subgroupByIssuesVisibility: string[]; + } = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] }; + // root store + rootStore; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + kanBanToggle: observable, + // computed + canUserDragDrop: computed, + canUserDragDropVertically: computed, + canUserDragDropHorizontally: computed, + + // actions + handleKanBanToggle: action, + handleSwimlaneDragDrop: action, + handleDragDrop: action, + }); + + this.rootStore = _rootStore; + } + + get canUserDragDrop() { + if ( + this.rootStore?.issueFilter?.userDisplayFilters?.order_by && + this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" && + this.rootStore?.issueFilter?.userDisplayFilters?.group_by && + ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) + ) { + if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) return true; + if ( + this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by && + ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) + ) + return true; + } + return false; + } + + get canUserDragDropVertically() { + return false; + } + + get canUserDragDropHorizontally() { + return false; + } + + handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { + this.kanBanToggle = { + ...this.kanBanToggle, + [toggle]: this.kanBanToggle[toggle].includes(value) + ? this.kanBanToggle[toggle].filter((v) => v !== value) + : [...this.kanBanToggle[toggle], value], + }; + }; + + handleSwimlaneDragDrop = async (source: any, destination: any) => { + const workspaceSlug = this.rootStore?.workspace?.workspaceSlug; + const projectId = this.rootStore?.project?.projectId; + const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const currentIssues: any = this.rootStore.moduleIssue?.getIssues; + + const sortOrderDefaultValue = 65535; + + if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) { + // update issue payload + let updateIssue: any = { + workspaceSlug: workspaceSlug, + projectId: projectId, + }; + + // source, destination group and sub group id + let droppableSourceColumnId = source.droppableId; + droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null; + let droppableDestinationColumnId = destination.droppableId; + droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null; + if (!droppableSourceColumnId || !droppableDestinationColumnId) return null; + + const source_group_id: string = droppableSourceColumnId[0]; + const source_sub_group_id: string = droppableSourceColumnId[1] === "null" ? null : droppableSourceColumnId[1]; + + const destination_group_id: string = droppableDestinationColumnId[0]; + const destination_sub_group_id: string = + droppableDestinationColumnId[1] === "null" ? null : droppableDestinationColumnId[1]; + + if (source_sub_group_id === destination_sub_group_id) { + if (source_group_id === destination_group_id) { + const _issues = currentIssues[source_sub_group_id][source_group_id]; + + // update the sort order + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _issues.length - 1) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2, + }; + } + + const [removed] = _issues.splice(source.index, 1); + _issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order }); + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_sub_group_id][source_group_id] = _issues; + } + + if (source_group_id != destination_group_id) { + const _sourceIssues = currentIssues[source_sub_group_id][source_group_id]; + let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + updateIssue = { ...updateIssue, issueId: removed?.id }; + + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + // updateIssue = { ...updateIssue, state: destination_group_id }; + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + // updateIssue = { ...updateIssue, priority: destination_group_id }; + + currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; + currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; + } + } + + if (source_sub_group_id != destination_sub_group_id) { + const _sourceIssues = currentIssues[source_sub_group_id][source_group_id]; + let _destinationIssues = currentIssues[destination_sub_group_id][destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_sub_group_id][source_group_id] = _sourceIssues; + currentIssues[destination_sub_group_id][destination_group_id] = _destinationIssues; + + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + // updateIssue = { ...updateIssue, state: destination_group_id }; + // if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + // updateIssue = { ...updateIssue, priority: destination_group_id }; + } + + const reorderedIssues = { + ...this.rootStore?.cycleIssue.issues, + [projectId]: { + ...this.rootStore?.cycleIssue.issues?.[projectId], + [issueType]: { + ...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType], + [issueType]: currentIssues, + }, + }, + }; + + runInAction(() => { + this.rootStore.moduleIssue.issues = { ...reorderedIssues }; + }); + + // console.log("updateIssue", updateIssue); + + // this.rootStore.issueDetail?.updateIssue( + // updateIssue.workspaceSlug, + // updateIssue.projectId, + // updateIssue.issueId, + // updateIssue, + // undefined + // ); + } + }; + + handleDragDrop = async (source: any, destination: any) => { + const workspaceSlug = this.rootStore?.workspace?.workspaceSlug; + const projectId = this.rootStore?.project?.projectId; + const issueType: IIssueType | null = this.rootStore?.issue?.getIssueType; + const issueLayout = this.rootStore?.issueFilter?.userDisplayFilters?.layout || null; + const currentIssues: any = this.rootStore.moduleIssue?.getIssues; + + const sortOrderDefaultValue = 65535; + + if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && currentIssues) { + // update issue payload + let updateIssue: any = { + workspaceSlug: workspaceSlug, + projectId: projectId, + }; + + // source, destination group and sub group id + let droppableSourceColumnId = source.droppableId; + droppableSourceColumnId = droppableSourceColumnId ? droppableSourceColumnId.split("__") : null; + let droppableDestinationColumnId = destination.droppableId; + droppableDestinationColumnId = droppableDestinationColumnId ? droppableDestinationColumnId.split("__") : null; + if (!droppableSourceColumnId || !droppableDestinationColumnId) return null; + + const source_group_id: string = droppableSourceColumnId[0]; + const destination_group_id: string = droppableDestinationColumnId[0]; + + if (this.canUserDragDrop) { + // vertical + if (source_group_id === destination_group_id) { + const _issues = currentIssues[source_group_id]; + + // update the sort order + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _issues.length - 1) { + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: (_issues[destination.index - 1].sort_order + _issues[destination.index].sort_order) / 2, + }; + } + + const [removed] = _issues.splice(source.index, 1); + _issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order }); + updateIssue = { ...updateIssue, issueId: removed?.id }; + currentIssues[source_group_id] = _issues; + } + + // horizontal + if (source_group_id != destination_group_id) { + const _sourceIssues = currentIssues[source_group_id]; + let _destinationIssues = currentIssues[destination_group_id] || []; + + if (_destinationIssues && _destinationIssues.length > 0) { + if (destination.index === 0) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, + }; + } else if (destination.index === _destinationIssues.length) { + updateIssue = { + ...updateIssue, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, + }; + } else { + updateIssue = { + ...updateIssue, + sort_order: + (_destinationIssues[destination.index - 1].sort_order + + _destinationIssues[destination.index].sort_order) / + 2, + }; + } + } else { + updateIssue = { + ...updateIssue, + sort_order: sortOrderDefaultValue, + }; + } + + const [removed] = _sourceIssues.splice(source.index, 1); + if (_destinationIssues && _destinationIssues.length > 0) + _destinationIssues.splice(destination.index, 0, { + ...removed, + sort_order: updateIssue.sort_order, + }); + else _destinationIssues = [..._destinationIssues, { ...removed, sort_order: updateIssue.sort_order }]; + updateIssue = { ...updateIssue, issueId: removed?.id }; + + currentIssues[source_group_id] = _sourceIssues; + currentIssues[destination_group_id] = _destinationIssues; + } + + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "state") + updateIssue = { ...updateIssue, state: destination_group_id }; + if (this.rootStore.issueFilter?.userDisplayFilters?.group_by === "priority") + updateIssue = { ...updateIssue, priority: destination_group_id }; + } + + // user can drag the issues only vertically + if (this.canUserDragDropVertically && destination_group_id === destination_group_id) { + } + + // user can drag the issues only horizontally + if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) { + } + + const reorderedIssues = { + ...this.rootStore?.cycleIssue.issues, + [projectId]: { + ...this.rootStore?.cycleIssue.issues?.[projectId], + [issueType]: { + ...this.rootStore?.cycleIssue.issues?.[projectId]?.[issueType], + [issueType]: currentIssues, + }, + }, + }; + + runInAction(() => { + this.rootStore.moduleIssue.issues = { ...reorderedIssues }; + }); + + this.rootStore.issueDetail?.updateIssue( + updateIssue.workspaceSlug, + updateIssue.projectId, + updateIssue.issueId, + updateIssue, + undefined + ); + } + }; +} + +export default ModuleIssueKanBanViewStore; diff --git a/web/store/root.ts b/web/store/root.ts index 89d07e38dd9..69e67b36748 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -9,6 +9,9 @@ import DraftIssuesStore from "./issue_draft"; import WorkspaceStore, { IWorkspaceStore } from "./workspace"; import ProjectStore, { IProjectStore } from "./project"; import ModuleStore, { IModuleStore } from "./modules"; +import ModuleIssueStore, { IModuleIssueStore } from "./module_issue"; +import ModuleFilterStore, { IModuleFilterStore } from "./module_filters"; +import ModuleIssueKanBanViewStore, { IModuleIssueKanBanViewStore } from "./module_issue_kanban_view"; import CycleStore, { ICycleStore } from "./cycles"; import CycleIssueStore, { ICycleIssueStore } from "./cycle_issue"; import CycleIssueFilterStore, { ICycleIssueFilterStore } from "./cycle_issue_filters"; @@ -18,7 +21,6 @@ import IssueFilterStore, { IIssueFilterStore } from "./issue_filters"; import IssueViewDetailStore from "./issue_detail"; import IssueKanBanViewStore from "./kanban_view"; import CalendarStore, { ICalendarStore } from "./calendar"; -import ModuleFilterStore, { IModuleFilterStore } from "./module_filters"; enableStaticRendering(typeof window === "undefined"); @@ -30,8 +32,11 @@ export class RootStore { workspace: IWorkspaceStore; project: IProjectStore; issue: IIssueStore; + module: IModuleStore; + moduleIssue: IModuleIssueStore; moduleFilter: IModuleFilterStore; + moduleIssueKanBanView: IModuleIssueKanBanViewStore; cycle: ICycleStore; cycleIssue: ICycleIssueStore; @@ -50,8 +55,11 @@ export class RootStore { this.workspace = new WorkspaceStore(this); this.project = new ProjectStore(this); this.projectPublish = new ProjectPublishStore(this); + this.module = new ModuleStore(this); + this.moduleIssue = new ModuleIssueStore(this); this.moduleFilter = new ModuleFilterStore(this); + this.moduleIssueKanBanView = new ModuleIssueKanBanViewStore(this); this.cycle = new CycleStore(this); this.cycleIssue = new CycleIssueStore(this);