From 6da484d9ec73c9c4cc6adfa550edd9c2491ca98c Mon Sep 17 00:00:00 2001 From: gurusainath Date: Wed, 27 Sep 2023 11:44:51 +0530 Subject: [PATCH 1/5] chore: updated the all the group_by and sub_group_by UI and functionality render in kanban --- .../issues/issue-layouts/kanban/default.tsx | 195 +++++++++--------- .../issue-layouts/kanban/headers/assignee.tsx | 22 +- .../kanban/headers/created_by.tsx | 22 +- .../headers/{card.tsx => group-by-card.tsx} | 4 +- .../kanban/headers/group-by-root.tsx | 12 +- .../issue-layouts/kanban/headers/label.tsx | 20 +- .../issue-layouts/kanban/headers/priority.tsx | 22 +- .../kanban/headers/state-group.tsx | 23 ++- .../issue-layouts/kanban/headers/state.tsx | 20 +- .../kanban/headers/sub-group-by-card.tsx | 31 +++ .../kanban/headers/sub-group-by-root.tsx | 14 +- .../issues/issue-layouts/kanban/root.tsx | 3 +- .../issues/issue-layouts/kanban/swimlanes.tsx | 68 +++--- web/constants/issue.ts | 10 + web/store/issue.ts | 3 +- web/store/project.ts | 9 + 16 files changed, 304 insertions(+), 174 deletions(-) rename web/components/issues/issue-layouts/kanban/headers/{card.tsx => group-by-card.tsx} (93%) create mode 100644 web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 321105635ee..11eef3832c3 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -5,13 +5,62 @@ import { Droppable } from "@hello-pangea/dnd"; import { KanBanGroupByHeaderRoot } from "./headers/group-by-root"; import { IssueBlock } from "./block"; // constants -import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES, getValueFromObject } from "constants/issue"; // mobx import { observer } from "mobx-react-lite"; // mobx import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; +export interface IGroupByKanBan { + list: any; + listKey: string; + sub_group_by: string | null; + sub_group_id: string; + issues?: any; + handleIssues?: () => void; + handleDragDrop?: (result: any) => void | undefined; +} + +const GroupByKanBan: React.FC = ({ list, listKey, sub_group_by, sub_group_id = "null", issues }) => ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} + +
+ + {(provided: any, snapshot: any) => ( +
+ {issues && ( + + )} + {provided.placeholder} +
+ )} +
+
+
+ ))} +
+); + export interface IKanBan { issues?: any; handleIssues?: () => void; @@ -28,123 +77,63 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul return (
{group_by && group_by === "state" && ( -
- {projectStore?.projectStates && - projectStore?.projectStates.length > 0 && - projectStore?.projectStates.map((state) => ( -
- {sub_group_by === null && ( -
- -
- )} - -
- - {(provided: any, snapshot: any) => ( -
- {issues && ( - - )} - {provided.placeholder} -
- )} -
-
-
- ))} -
+ )} {group_by && group_by === "state_detail.group" && ( -
- {ISSUE_STATE_GROUPS && - ISSUE_STATE_GROUPS.length > 0 && - ISSUE_STATE_GROUPS.map((stateGroup) => ( -
- {sub_group_by === null && ( -
- -
- )} -
content
-
- ))} -
+ )} {group_by && group_by === "priority" && ( -
- {ISSUE_PRIORITIES && - ISSUE_PRIORITIES.length > 0 && - ISSUE_PRIORITIES.map((priority) => ( -
- {sub_group_by === null && ( -
- -
- )} -
content
-
- ))} -
+ )} {group_by && group_by === "labels" && ( -
- {projectStore?.projectLabels && - projectStore?.projectLabels.length > 0 && - projectStore?.projectLabels.map((label) => ( -
- {sub_group_by === null && ( -
- -
- )} -
content
-
- ))} -
+ )} {group_by && group_by === "assignees" && ( -
- {projectStore?.projectMembers && - projectStore?.projectMembers.length > 0 && - projectStore?.projectMembers.map((member) => ( -
- {sub_group_by === null && ( -
- -
- )} -
content
-
- ))} -
+ )} {group_by && group_by === "created_by" && ( -
- {projectStore?.projectMembers && - projectStore?.projectMembers.length > 0 && - projectStore?.projectMembers.map((member) => ( -
- {sub_group_by === null && ( -
- -
- )} -
content
-
- ))} -
+ )}
); diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index 6824f18533c..875c9719ef5 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -1,5 +1,6 @@ // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // mobx import { observer } from "mobx-react-lite"; // store @@ -8,12 +9,23 @@ import { RootStore } from "store/root"; export interface IAssigneesHeader { column_id: string; + type?: "group_by" | "sub_group_by"; } -export const AssigneesHeader: React.FC = observer(({ column_id }) => { - const { project: projectStore }: RootStore = useMobxStore(); +export const AssigneesHeader: React.FC = observer(({ column_id, type }) => { + const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); - const assignee = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null; + const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{assignee && }; + return ( + <> + {assignee && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); 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 3b71bbf6794..3f672acdaca 100644 --- a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -1,5 +1,6 @@ // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // mobx import { observer } from "mobx-react-lite"; // store @@ -8,12 +9,23 @@ import { RootStore } from "store/root"; export interface ICreatedByHeader { column_id: string; + type?: "group_by" | "sub_group_by"; } -export const CreatedByHeader: React.FC = observer(({ column_id }) => { - const { project: projectStore }: RootStore = useMobxStore(); +export const CreatedByHeader: React.FC = observer(({ column_id, type }) => { + const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); - const createdBy = (column_id && projectStore?.getProjectMemberById(column_id)) ?? null; + const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{createdBy && }; + return ( + <> + {createdBy && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/card.tsx b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx similarity index 93% rename from web/components/issues/issue-layouts/kanban/headers/card.tsx rename to web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx index 8c35aa99d89..14918989b7b 100644 --- a/web/components/issues/issue-layouts/kanban/headers/card.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/group-by-card.tsx @@ -2,12 +2,12 @@ import React from "react"; // lucide icons import { Plus, Minimize2, Maximize2, Circle } from "lucide-react"; -interface IHeaderCard { +interface IHeaderGroupByCard { icon?: React.ReactNode; title: string; } -export const HeaderCard = ({ icon, title }: IHeaderCard) => { +export const HeaderGroupByCard = ({ icon, title }: IHeaderGroupByCard) => { const position = false; return ( 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 9412d04f306..00f30b764b1 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 @@ -21,12 +21,12 @@ export const KanBanGroupByHeaderRoot: React.FC = obser return ( <> - {group_by && group_by === "state" && } - {group_by && group_by === "state_detail.group" && } - {group_by && group_by === "priority" && } - {group_by && group_by === "labels" && } - {group_by && group_by === "assignees" && } - {group_by && group_by === "created_by" && } + {group_by && group_by === "state" && } + {group_by && group_by === "state_detail.group" && } + {group_by && group_by === "priority" && } + {group_by && group_by === "labels" && } + {group_by && group_by === "assignees" && } + {group_by && group_by === "created_by" && } ); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index 00223e515bf..ec54c32bb98 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -1,5 +1,6 @@ // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // mobx import { observer } from "mobx-react-lite"; // store @@ -8,12 +9,23 @@ import { RootStore } from "store/root"; export interface ILabelHeader { column_id: string; + type?: "group_by" | "sub_group_by"; } -export const LabelHeader: React.FC = observer(({ column_id }) => { - const { project: projectStore }: RootStore = useMobxStore(); +export const LabelHeader: React.FC = observer(({ column_id, type }) => { + const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null; + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{label && }; + return ( + <> + {label && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx index 5493f1a360a..b6e5fa10c05 100644 --- a/web/components/issues/issue-layouts/kanban/headers/priority.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -1,6 +1,7 @@ import React from "react"; // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // constants import { issuePriorityByKey } from "constants/issue"; // mobx @@ -11,12 +12,23 @@ import { RootStore } from "store/root"; export interface IPriorityHeader { column_id: string; + type?: "group_by" | "sub_group_by"; } -export const PriorityHeader: React.FC = observer(({ column_id }) => { - const {}: RootStore = useMobxStore(); +export const PriorityHeader: React.FC = observer(({ column_id, type }) => { + const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); - const stateGroup = column_id && issuePriorityByKey(column_id); + const priority = column_id && issuePriorityByKey(column_id); + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{stateGroup && }; + return ( + <> + {priority && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); 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 2a406ac946c..f8e61f52adc 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -1,6 +1,7 @@ import React from "react"; // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // constants import { issueStateGroupByKey } from "constants/issue"; // mobx @@ -11,13 +12,25 @@ import { RootStore } from "store/root"; export interface IStateGroupHeader { column_id: string; - swimlanes?: boolean; + type?: "group_by" | "sub_group_by"; } -export const StateGroupHeader: React.FC = observer(({ column_id }) => { - const {}: RootStore = useMobxStore(); +export const StateIcon = () => {}; + +export const StateGroupHeader: React.FC = observer(({ column_id, type }) => { + const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); const stateGroup = column_id && issueStateGroupByKey(column_id); + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{stateGroup && }; + return ( + <> + {stateGroup && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx index 4ed95a07ca4..998531b57a5 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -1,5 +1,6 @@ // components -import { HeaderCard } from "./card"; +import { HeaderGroupByCard } from "./group-by-card"; +import { HeaderSubGroupByCard } from "./sub-group-by-card"; // mobx import { observer } from "mobx-react-lite"; // store @@ -8,12 +9,23 @@ import { RootStore } from "store/root"; export interface IStateHeader { column_id: string; + type?: "group_by" | "sub_group_by"; } -export const StateHeader: React.FC = observer(({ column_id }) => { - const { project: projectStore }: RootStore = useMobxStore(); +export const StateHeader: React.FC = observer(({ column_id, type }) => { + const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null; + const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; - return <>{state && }; + return ( + <> + {state && + (sub_group_by && type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); }); 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 new file mode 100644 index 00000000000..0b10af3077d --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/headers/sub-group-by-card.tsx @@ -0,0 +1,31 @@ +import React from "react"; +// lucide icons +import { Plus, Minimize2, Maximize2, Circle } from "lucide-react"; + +interface IHeaderSubGroupByCard { + icon?: React.ReactNode; + title: string; +} + +export const HeaderSubGroupByCard = ({ icon, title }: IHeaderSubGroupByCard) => { + const position = false; + + return ( +
+
+ {icon ? icon : } +
+ +
+
{title}
+
(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 f78970fd702..eef0cd1a669 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 @@ -21,12 +21,14 @@ export const KanBanSubGroupByHeaderRoot: React.FC = return ( <> - {sub_group_by && sub_group_by === "state" && } - {sub_group_by && sub_group_by === "state_detail.group" && } - {sub_group_by && sub_group_by === "priority" && } - {sub_group_by && sub_group_by === "labels" && } - {sub_group_by && sub_group_by === "assignees" && } - {sub_group_by && sub_group_by === "created_by" && } + {sub_group_by && sub_group_by === "state" && } + {sub_group_by && sub_group_by === "state_detail.group" && ( + + )} + {sub_group_by && sub_group_by === "priority" && } + {sub_group_by && sub_group_by === "labels" && } + {sub_group_by && sub_group_by === "assignees" && } + {sub_group_by && sub_group_by === "created_by" && } ); }); diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx index 4b3bb5de98c..0939423a18d 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -39,7 +39,8 @@ export const KanBanLayout: React.FC = observer(({}) => { }; return ( -
+
+ {/*
*/} {currentKanBanView === "default" ? : } diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 7ac4a47439f..68b2e98300a 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -4,99 +4,115 @@ import { KanBanGroupByHeaderRoot } from "./headers/group-by-root"; import { KanBanSubGroupByHeaderRoot } from "./headers/sub-group-by-root"; import { KanBan } from "./default"; // constants -import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue"; +import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES, getValueFromObject } from "constants/issue"; // mobx import { observer } from "mobx-react-lite"; // mobx import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; -export interface IKanBanSwimLanes { - issues?: any; - handleIssues?: () => void; - handleDragDrop?: () => void; +interface ISubGroupSwimlaneHeader { + list: any; + listKey: string; } -const SubGroupSwimlaneHeader = ({ list, _key }: any) => ( +const SubGroupSwimlaneHeader: React.FC = ({ list, listKey }) => (
{list && list.length > 0 && list.map((_list: any) => (
- +
))}
); -const SubGroupSwimlane = ({ issues, list, _key }: any) => ( +interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { + issues?: any; +} + +const SubGroupSwimlane: React.FC = ({ issues, list, listKey }) => (
{list && list.length > 0 && list.map((_list: any) => (
-
+
- +
- +
))}
); +export interface IKanBanSwimLanes { + issues?: any; + handleIssues?: () => void; + handleDragDrop?: () => void; +} + export const KanBanSwimLanes: React.FC = observer(({ issues }) => { const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; - console.log("sub_group_by", sub_group_by); - return (
-
- {group_by && group_by === "state" && } +
+ {group_by && group_by === "state" && ( + + )} + {group_by && group_by === "state_detail.group" && ( - + )} - {group_by && group_by === "priority" && } - {group_by && group_by === "labels" && } + + {group_by && group_by === "priority" && } + + {group_by && group_by === "labels" && ( + + )} + {group_by && group_by === "assignees" && ( - + )} + {group_by && group_by === "created_by" && ( - + )}
{sub_group_by && sub_group_by === "state" && ( - + )} {sub_group_by && sub_group_by === "state_detail.group" && ( - + )} {sub_group_by && sub_group_by === "priority" && ( - + )} {sub_group_by && sub_group_by === "labels" && ( - + )} {sub_group_by && sub_group_by === "assignees" && ( - + )} {sub_group_by && sub_group_by === "created_by" && ( - + )}
); diff --git a/web/constants/issue.ts b/web/constants/issue.ts index eaec04999f2..1d3440c6573 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -315,3 +315,13 @@ export const ISSUE_DISPLAY_FILTERS_BY_LAYOUT: { }, }, }; + +export const getValueFromObject = (object: Object, key: string): string | number | boolean | null => { + const keys = key ? key.split(".") : []; + + let value: any = object; + if (!value || keys.length === 0) return null; + + for (const _key of keys) value = value[_key]; + return value; +}; diff --git a/web/store/issue.ts b/web/store/issue.ts index 00c3fa46abb..1c666f2e3d4 100644 --- a/web/store/issue.ts +++ b/web/store/issue.ts @@ -142,8 +142,7 @@ class IssueStore implements IIssueStore { this.rootStore.workspace.setWorkspaceSlug(workspaceSlug); this.rootStore.project.setProjectId(projectId); - // TODO: replace this once the issue filter is completed - const params = { group_by: "state", order_by: "-created_at" }; + const params = this.rootStore?.issueFilter?.appliedFilters; const issueResponse = await this.issueService.getIssuesWithParams(workspaceSlug, projectId, params); const issueType = this.getIssueType; diff --git a/web/store/project.ts b/web/store/project.ts index 1f5d8fdec88..2fd490dc188 100644 --- a/web/store/project.ts +++ b/web/store/project.ts @@ -48,6 +48,7 @@ export interface IProjectStore { getProjectStateById: (stateId: string) => IState | null; getProjectLabelById: (labelId: string) => IIssueLabels | null; getProjectMemberById: (memberId: string) => IProjectMember | null; + getProjectMemberByUserId: (memberId: string) => IProjectMember | null; fetchProjects: (workspaceSlug: string) => Promise; fetchProjectStates: (workspaceSlug: string, projectSlug: string) => Promise; @@ -254,6 +255,14 @@ class ProjectStore implements IProjectStore { return memberInfo; }; + getProjectMemberByUserId = (memberId: string) => { + if (!this.projectId) return null; + const members = this.projectMembers; + if (!members) return null; + const memberInfo: IProjectMember | null = members.find((member) => member.member.id === memberId) || null; + return memberInfo; + }; + fetchProjectStates = async (workspaceSlug: string, projectSlug: string) => { try { this.loader = true; From 247ebcfe603b776cb8343e7b1e000df89714feb0 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Wed, 27 Sep 2023 20:28:47 +0530 Subject: [PATCH 2/5] chore: kanban sorting in mobx and ui updates --- web/components/gantt-chart/contexts/index.tsx | 9 +- .../issues/issue-layouts/kanban/block.tsx | 78 ++--- .../issues/issue-layouts/kanban/default.tsx | 33 +- .../kanban/headers/sub-group-by-card.tsx | 35 +- .../issues/issue-layouts/kanban/root.tsx | 33 +- .../issues/issue-layouts/kanban/swimlanes.tsx | 5 +- web/store/issue_detail.ts | 8 +- web/store/kanban_view.ts | 305 +++++++++++++++--- 8 files changed, 375 insertions(+), 131 deletions(-) diff --git a/web/components/gantt-chart/contexts/index.tsx b/web/components/gantt-chart/contexts/index.tsx index 05dfbe67879..137cc2607c2 100644 --- a/web/components/gantt-chart/contexts/index.tsx +++ b/web/components/gantt-chart/contexts/index.tsx @@ -6,10 +6,7 @@ import { allViewsWithData, currentViewDataWithView } from "../data"; export const ChartContext = createContext(undefined); -const chartReducer = ( - state: ChartContextData, - action: ChartContextActionPayload -): ChartContextData => { +const chartReducer = (state: ChartContextData, action: ChartContextActionPayload): ChartContextData => { switch (action.type) { case "CURRENT_VIEW": return { ...state, currentView: action.payload }; @@ -50,9 +47,7 @@ export const ChartContextProvider: React.FC<{ children: React.ReactNode }> = ({ }; return ( - + {children} ); diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 6900e9782ed..23ddd678920 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -5,46 +5,48 @@ interface IssueBlockProps { sub_group_id: string; columnId: string; issues: any; + isDragDisabled: boolean; } -export const IssueBlock = ({ sub_group_id, columnId, issues }: IssueBlockProps) => { - console.log(); - - return ( - <> - {issues && issues.length > 0 ? ( - <> - {issues.map((issue: any, index: any) => ( - - {(provided: any, snapshot: any) => ( +export const IssueBlock = ({ sub_group_id, columnId, issues, isDragDisabled }: IssueBlockProps) => ( + <> + {issues && issues.length > 0 ? ( + <> + {issues.map((issue: any, index: any) => ( + + {(provided: any, snapshot: any) => ( +
-
-
ONE-{issue.sequence_id}
-
{issue.name}
-
Footer
-
+
ONE-{issue.sequence_id}
+
{issue.name}
+
Footer
- )} - - ))} - - ) : ( -
No issues are available.
- )} - - ); -}; +
+ )} +
+ ))} + + ) : ( + !isDragDisabled && ( +
+
Drop here
+
+ ) + )} + +); diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 11eef3832c3..d9f7f9aed2d 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -20,9 +20,17 @@ export interface IGroupByKanBan { issues?: any; handleIssues?: () => void; handleDragDrop?: (result: any) => void | undefined; + isDragDisabled: boolean; } -const GroupByKanBan: React.FC = ({ list, listKey, sub_group_by, sub_group_id = "null", issues }) => ( +const GroupByKanBan: React.FC = ({ + list, + listKey, + sub_group_by, + sub_group_id = "null", + issues, + isDragDisabled, +}) => (
{list && list.length > 0 && @@ -35,7 +43,7 @@ const GroupByKanBan: React.FC = ({ list, listKey, sub_group_by, )}
- + {(provided: any, snapshot: any) => (
= ({ list, listKey, sub_group_by, {...provided.droppableProps} ref={provided.innerRef} > - {issues && ( + {issues ? ( + ) : ( + isDragDisabled && ( +
+
Drop here
+
+ ) )} {provided.placeholder}
@@ -69,7 +84,11 @@ export interface IKanBan { } export const KanBan: React.FC = observer(({ issues, sub_group_id = "null" }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + const { + project: projectStore, + issueFilter: issueFilterStore, + issueKanBanView: issueKanBanViewStore, + }: RootStore = useMobxStore(); const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; @@ -83,6 +102,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )} @@ -93,6 +113,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )} @@ -103,6 +124,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )} @@ -113,6 +135,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )} @@ -123,6 +146,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )} @@ -133,6 +157,7 @@ export const KanBan: React.FC = observer(({ issues, sub_group_id = "nul sub_group_by={sub_group_by} sub_group_id={sub_group_id} issues={issues} + isDragDisabled={!issueKanBanViewStore?.canUserDragDrop} /> )}
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 0b10af3077d..91bd65b80be 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 @@ -1,31 +1,26 @@ import React from "react"; // lucide icons -import { Plus, Minimize2, Maximize2, Circle } from "lucide-react"; +import { Circle, ChevronDown, ChevronUp } from "lucide-react"; interface IHeaderSubGroupByCard { icon?: React.ReactNode; title: string; } -export const HeaderSubGroupByCard = ({ icon, title }: IHeaderSubGroupByCard) => { - const position = false; +export const HeaderSubGroupByCard = ({ icon, title }: IHeaderSubGroupByCard) => ( +
+
+ + {/* */} +
- return ( -
-
- {icon ? icon : } -
+
+ {icon ? icon : } +
-
-
{title}
-
(0)
-
+
+
{title}
+
(0)
- ); -}; +
+); diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx index 0939423a18d..f919cfd75d1 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -15,8 +15,30 @@ export interface IKanBanLayout { handleDragDrop?: (result: any) => void; } +interface IIssueVisibility { + kanban: string[]; + swimlanes: string[]; +} + export const KanBanLayout: React.FC = observer(({}) => { - const { issue: issueStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); + const [issueVisiility, setIssueVisibility] = React.useState({ + kanban: [], + swimlanes: [], + }); + const handleIssueVisibility = (key: "kanban" | "swimlanes", value: string) => { + setIssueVisibility((prevState: IIssueVisibility) => ({ + ...prevState, + [key]: prevState[key].includes(value) + ? prevState[key].filter((item) => item !== value) + : [...prevState[key], value], + })); + }; + + const { + issue: issueStore, + issueFilter: issueFilterStore, + issueKanBanView: issueKanBanViewStore, + }: RootStore = useMobxStore(); const currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by ? "swimlanes" : "default"; @@ -34,13 +56,14 @@ export const KanBanLayout: React.FC = observer(({}) => { ) return; - console.log("result", result); - // issueKanBanViewStore?.handleDragDrop(result.source, result.destination); + currentKanBanView === "default" + ? issueKanBanViewStore?.handleDragDrop(result.source, result.destination) + : issueKanBanViewStore?.handleSwimlaneDragDrop(result.source, result.destination); }; return ( -
- {/*
*/} + //
+
{currentKanBanView === "default" ? : } diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 68b2e98300a..ed5d6769e0f 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -46,7 +46,10 @@ const SubGroupSwimlane: React.FC = ({ issues, list, listKey }
- +
))} diff --git a/web/store/issue_detail.ts b/web/store/issue_detail.ts index 31a91d51f33..4095099a096 100644 --- a/web/store/issue_detail.ts +++ b/web/store/issue_detail.ts @@ -115,8 +115,6 @@ class IssueDetailStore implements IIssueDetailStore { const response = await this.issueService.createIssues(workspaceId, projectId, data, user); - if (response) this.rootStore.issue.addIssueToIssuesStore(projectId, response); - runInAction(() => { this.loader = false; this.error = null; @@ -137,7 +135,7 @@ class IssueDetailStore implements IIssueDetailStore { projectId: string, issueId: string, data: Partial, - user: ICurrentUserResponse + user: ICurrentUserResponse | undefined ) => { const newIssues = { ...this.issues }; newIssues[issueId] = { @@ -154,8 +152,6 @@ class IssueDetailStore implements IIssueDetailStore { const response = await this.issueService.patchIssue(workspaceId, projectId, issueId, data, user); - if (response) this.rootStore.issue.updateIssueInIssuesStore(projectId, response); - runInAction(() => { this.loader = false; this.error = null; @@ -192,8 +188,6 @@ class IssueDetailStore implements IIssueDetailStore { await this.issueService.deleteIssue(workspaceId, projectId, issueId, user); - this.rootStore.issue.deleteIssueFromIssuesStore(projectId, issueId); - runInAction(() => { this.loader = false; this.error = null; diff --git a/web/store/kanban_view.ts b/web/store/kanban_view.ts index c0d15227aa5..4166354e65e 100644 --- a/web/store/kanban_view.ts +++ b/web/store/kanban_view.ts @@ -1,4 +1,4 @@ -import { action, computed, makeObservable } from "mobx"; +import { action, computed, makeObservable, runInAction } from "mobx"; // types import { RootStore } from "./root"; import { IIssueType } from "./issue"; @@ -27,50 +27,74 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { get canUserDragDrop() { if ( - this.rootStore?.issueFilter?.userDisplayFilters?.group_by && this.rootStore?.issueFilter?.userDisplayFilters?.order_by && - ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) && - this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" + this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" && + this.rootStore?.issueFilter?.userDisplayFilters?.group_by && + ["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by) ) { - return true; + 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 true; + return false; } + get canUserDragDropHorizontally() { - return true; + return false; } - handleDragDrop = async (source: any, destination: any) => { + 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 = 10000; - - if (workspaceSlug && projectId && issueType && issueLayout === "kanban" && this.rootStore.issue.getIssues) { - 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, }; - // user can drag the issues from any direction - if (this.canUserDragDrop) { - // vertical - if (source.droppableId === destination.droppableId) { - const _columnId = source.droppableId; - const _issues = currentIssues[_columnId]; + // 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 }; + 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 }; + updateIssue = { + ...updateIssue, + sort_order: _issues[destination.index].sort_order + sortOrderDefaultValue, + }; } else { updateIssue = { ...updateIssue, @@ -78,34 +102,26 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { }; } - // update the mobx state array const [removed] = _issues.splice(source.index, 1); _issues.splice(destination.index, 0, { ...removed, sort_order: updateIssue.sort_order }); updateIssue = { ...updateIssue, issueId: removed?.id }; - - currentIssues[_columnId] = _issues; + currentIssues[source_sub_group_id][source_group_id] = _issues; } - // horizontal - if (source.droppableId != destination.droppableId) { - const _sourceColumnId = source.droppableId; - const _destinationColumnId = destination.droppableId; + 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] || []; - const _sourceIssues = currentIssues[_sourceColumnId]; - const _destinationIssues = currentIssues[_destinationColumnId]; - - if (_destinationIssues.length > 0) { + if (_destinationIssues && _destinationIssues.length > 0) { if (destination.index === 0) { updateIssue = { ...updateIssue, sort_order: _destinationIssues[destination.index].sort_order - sortOrderDefaultValue, - state: destination?.droppableId, }; - } else if (destination.index === _destinationIssues.length - 1) { + } else if (destination.index === _destinationIssues.length) { updateIssue = { ...updateIssue, - sort_order: _destinationIssues[destination.index].sort_order + sortOrderDefaultValue, - state: destination?.droppableId, + sort_order: _destinationIssues[destination.index - 1].sort_order + sortOrderDefaultValue, }; } else { updateIssue = { @@ -114,39 +130,225 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { (_destinationIssues[destination.index - 1].sort_order + _destinationIssues[destination.index].sort_order) / 2, - state: destination?.droppableId, }; } } else { updateIssue = { ...updateIssue, sort_order: sortOrderDefaultValue, - state: destination?.droppableId, }; } 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, - state: destination?.droppableId, 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[_sourceColumnId] = _sourceIssues; - currentIssues[_destinationColumnId] = _destinationIssues; + 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 && source.droppableId === destination.droppableId) { + if (this.canUserDragDropVertically && destination_group_id === destination_group_id) { } // user can drag the issues only horizontally - if (this.canUserDragDropHorizontally && source.droppableId != destination.droppableId) { + if (this.canUserDragDropHorizontally && destination_group_id != destination_group_id) { } - this.rootStore.issue.issues = { + const reorderedIssues = { ...this.rootStore?.issue.issues, [projectId]: { ...this.rootStore?.issue.issues?.[projectId], @@ -157,12 +359,17 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { }, }; - // this.rootStore.issueDetail?.updateIssueAsync( - // updateIssue.workspaceSlug, - // updateIssue.projectId, - // updateIssue.issueId, - // updateIssue - // ); + runInAction(() => { + this.rootStore.issue.issues = { ...reorderedIssues }; + }); + + this.rootStore.issueDetail?.updateIssue( + updateIssue.workspaceSlug, + updateIssue.projectId, + updateIssue.issueId, + updateIssue, + undefined + ); } }; } From ecb9da143d289f63271f44c6701cfa9484f52677 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 28 Sep 2023 15:03:17 +0530 Subject: [PATCH 3/5] chore: ui changes and drag and drop functionality changes in kanban --- web/components/core/views/all-views.tsx | 3 +- .../issues/issue-layouts/kanban/block.tsx | 10 +- .../issues/issue-layouts/kanban/default.tsx | 169 +++++++++-------- .../issue-layouts/kanban/headers/assignee.tsx | 41 +++-- .../kanban/headers/created_by.tsx | 41 +++-- .../kanban/headers/group-by-card.tsx | 70 ++++--- .../kanban/headers/group-by-root.tsx | 57 ++++-- .../issue-layouts/kanban/headers/label.tsx | 21 ++- .../issue-layouts/kanban/headers/priority.tsx | 44 +++-- .../kanban/headers/state-group.tsx | 44 +++-- .../issue-layouts/kanban/headers/state.tsx | 21 ++- .../kanban/headers/sub-group-by-card.tsx | 45 +++-- .../kanban/headers/sub-group-by-root.tsx | 72 ++++++-- .../issue-layouts/kanban/properties.tsx | 91 ++++++++++ .../issues/issue-layouts/kanban/root.tsx | 47 ++--- .../issues/issue-layouts/kanban/swimlanes.tsx | 171 +++++++++++++----- web/store/kanban_view.ts | 29 ++- 17 files changed, 661 insertions(+), 315 deletions(-) create mode 100644 web/components/issues/issue-layouts/kanban/properties.tsx diff --git a/web/components/core/views/all-views.tsx b/web/components/core/views/all-views.tsx index a75ac67d4c4..1ebbf7de861 100644 --- a/web/components/core/views/all-views.tsx +++ b/web/components/core/views/all-views.tsx @@ -233,7 +233,8 @@ export const AllViews: React.FC = ({ //
// )} // -
+ +
); diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 23ddd678920..e9c0619cb79 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -1,5 +1,7 @@ // react beautiful dnd import { Draggable } from "@hello-pangea/dnd"; +// components +import { KanBanProperties } from "./properties"; interface IssueBlockProps { sub_group_id: string; @@ -22,19 +24,21 @@ export const IssueBlock = ({ sub_group_id, columnId, issues, isDragDisabled }: I {(provided: any, snapshot: any) => (
ONE-{issue.sequence_id}
{issue.name}
-
Footer
+
+ +
)} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index d9f7f9aed2d..6b9d5200b11 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -13,150 +13,159 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; export interface IGroupByKanBan { - list: any; - listKey: string; + issues: any; sub_group_by: string | null; + group_by: string | null; sub_group_id: string; - issues?: any; + list: any; + listKey: string; handleIssues?: () => void; - handleDragDrop?: (result: any) => void | undefined; isDragDisabled: boolean; } -const GroupByKanBan: React.FC = ({ - list, - listKey, - sub_group_by, - sub_group_id = "null", - issues, - isDragDisabled, -}) => ( -
- {list && - list.length > 0 && - list.map((_list: any) => ( -
- {sub_group_by === null && ( -
- -
- )} +const GroupByKanBan: React.FC = observer( + ({ issues, sub_group_by, group_by, sub_group_id = "null", list, listKey, isDragDisabled }) => { + const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); + + const verticalAlignPosition = (_list: any) => + issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(getValueFromObject(_list, listKey) as string); -
- - {(provided: any, snapshot: any) => ( -
- {issues ? ( - - ) : ( - isDragDisabled && ( -
-
Drop here
+ return ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+ {sub_group_by === null && ( +
+ +
+ )} + + {!verticalAlignPosition(_list) && ( +
+ + {(provided: any, snapshot: any) => ( +
+ {issues ? ( + + ) : ( + isDragDisabled && ( +
+
Drop here
+
+ ) + )} + {provided.placeholder}
- ) - )} - {provided.placeholder} + )} +
)} - -
-
- ))} -
+
+ ))} +
+ ); + } ); export interface IKanBan { - issues?: any; + issues: any; + sub_group_by: string | null; + group_by: string | null; + sub_group_id?: string; handleIssues?: () => void; handleDragDrop?: (result: any) => void | undefined; - sub_group_id?: string; } -export const KanBan: React.FC = observer(({ issues, sub_group_id = "null" }) => { - const { - project: projectStore, - issueFilter: issueFilterStore, - issueKanBanView: issueKanBanViewStore, - }: RootStore = useMobxStore(); - - const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; - const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; +export const KanBan: React.FC = observer(({ issues, sub_group_by, group_by, sub_group_id = "null" }) => { + const { project: projectStore, issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); return (
{group_by && group_by === "state" && ( )} {group_by && group_by === "state_detail.group" && ( )} {group_by && group_by === "priority" && ( )} {group_by && group_by === "labels" && ( )} {group_by && group_by === "assignees" && ( )} {group_by && group_by === "created_by" && ( )} diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index 875c9719ef5..e1687c83f74 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -9,23 +9,32 @@ import { RootStore } from "store/root"; export interface IAssigneesHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } -export const AssigneesHeader: React.FC = observer(({ column_id, type }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); +export const AssigneesHeader: React.FC = observer( + ({ column_id, sub_group_by, group_by, header_type }) => { + const { project: projectStore }: RootStore = useMobxStore(); - const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; + const assignee = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; - return ( - <> - {assignee && - (sub_group_by && type === "sub_group_by" ? ( - - ) : ( - - ))} - - ); -}); + return ( + <> + {assignee && + (sub_group_by && header_type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); + } +); 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 3f672acdaca..ba22a8efdc5 100644 --- a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -9,23 +9,32 @@ import { RootStore } from "store/root"; export interface ICreatedByHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } -export const CreatedByHeader: React.FC = observer(({ column_id, type }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); +export const CreatedByHeader: React.FC = observer( + ({ column_id, sub_group_by, group_by, header_type }) => { + const { project: projectStore }: RootStore = useMobxStore(); - const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; + const createdBy = (column_id && projectStore?.getProjectMemberByUserId(column_id)) ?? null; - return ( - <> - {createdBy && - (sub_group_by && type === "sub_group_by" ? ( - - ) : ( - - ))} - - ); -}); + return ( + <> + {createdBy && + (sub_group_by && header_type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); + } +); 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 14918989b7b..4fdbab390da 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 @@ -1,39 +1,59 @@ import React from "react"; // lucide icons 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; + group_by: string | null; + column_id: string; icon?: React.ReactNode; title: string; + count: number; } -export const HeaderGroupByCard = ({ icon, title }: IHeaderGroupByCard) => { - const position = false; +export const HeaderGroupByCard = observer( + ({ sub_group_by, group_by, column_id, icon, title, count }: IHeaderGroupByCard) => { + const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); - return ( -
-
- {icon ? icon : } -
+ const verticalAlignPosition = issueKanBanViewStore.kanBanToggle?.groupByHeaderMinMax.includes(column_id); -
-
{title}
-
(0)
-
+ return ( +
+
+ {icon ? icon : } +
-
- {position ? : } -
+
+
{title}
+
{count || 0}
+
+ + {sub_group_by === null && ( +
issueKanBanViewStore?.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 00f30b764b1..96b9269c7a4 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 @@ -13,20 +13,51 @@ import { RootStore } from "store/root"; export interface IKanBanGroupByHeaderRoot { column_id: string; + sub_group_by: string | null; + group_by: string | null; } -export const KanBanGroupByHeaderRoot: React.FC = observer(({ column_id }) => { - const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); - const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; - - return ( +export const KanBanGroupByHeaderRoot: React.FC = observer( + ({ column_id, sub_group_by, group_by }) => ( <> - {group_by && group_by === "state" && } - {group_by && group_by === "state_detail.group" && } - {group_by && group_by === "priority" && } - {group_by && group_by === "labels" && } - {group_by && group_by === "assignees" && } - {group_by && group_by === "created_by" && } + {group_by && group_by === "state" && ( + + )} + {group_by && group_by === "state_detail.group" && ( + + )} + {group_by && group_by === "priority" && ( + + )} + {group_by && group_by === "labels" && ( + + )} + {group_by && group_by === "assignees" && ( + + )} + {group_by && group_by === "created_by" && ( + + )} - ); -}); + ) +); diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index ec54c32bb98..a36f4680999 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -9,22 +9,29 @@ import { RootStore } from "store/root"; export interface ILabelHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } -export const LabelHeader: React.FC = observer(({ column_id, type }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); +export const LabelHeader: React.FC = observer(({ column_id, sub_group_by, group_by, header_type }) => { + const { project: projectStore }: RootStore = useMobxStore(); const label = (column_id && projectStore?.getProjectLabelById(column_id)) ?? null; - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; return ( <> {label && - (sub_group_by && type === "sub_group_by" ? ( - + (sub_group_by && header_type === "sub_group_by" ? ( + ) : ( - + ))} ); diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx index b6e5fa10c05..f1596f9259a 100644 --- a/web/components/issues/issue-layouts/kanban/headers/priority.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -6,29 +6,33 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { issuePriorityByKey } from "constants/issue"; // mobx import { observer } from "mobx-react-lite"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; export interface IPriorityHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } -export const PriorityHeader: React.FC = observer(({ column_id, type }) => { - const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); - - const priority = column_id && issuePriorityByKey(column_id); - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; +export const PriorityHeader: React.FC = observer( + ({ column_id, sub_group_by, group_by, header_type }) => { + const priority = column_id && issuePriorityByKey(column_id); - return ( - <> - {priority && - (sub_group_by && type === "sub_group_by" ? ( - - ) : ( - - ))} - - ); -}); + return ( + <> + {priority && + (sub_group_by && header_type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); + } +); 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 f8e61f52adc..180209a07ea 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -6,31 +6,35 @@ import { HeaderSubGroupByCard } from "./sub-group-by-card"; import { issueStateGroupByKey } from "constants/issue"; // mobx import { observer } from "mobx-react-lite"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; export interface IStateGroupHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } export const StateIcon = () => {}; -export const StateGroupHeader: React.FC = observer(({ column_id, type }) => { - const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); - - const stateGroup = column_id && issueStateGroupByKey(column_id); - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; +export const StateGroupHeader: React.FC = observer( + ({ column_id, sub_group_by, group_by, header_type }) => { + const stateGroup = column_id && issueStateGroupByKey(column_id); - return ( - <> - {stateGroup && - (sub_group_by && type === "sub_group_by" ? ( - - ) : ( - - ))} - - ); -}); + return ( + <> + {stateGroup && + (sub_group_by && header_type === "sub_group_by" ? ( + + ) : ( + + ))} + + ); + } +); diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx index 998531b57a5..c757c921ac2 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -9,22 +9,29 @@ import { RootStore } from "store/root"; export interface IStateHeader { column_id: string; - type?: "group_by" | "sub_group_by"; + sub_group_by: string | null; + group_by: string | null; + header_type: "group_by" | "sub_group_by"; } -export const StateHeader: React.FC = observer(({ column_id, type }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); +export const StateHeader: React.FC = observer(({ column_id, sub_group_by, group_by, header_type }) => { + const { project: projectStore }: RootStore = useMobxStore(); const state = (column_id && projectStore?.getProjectStateById(column_id)) ?? null; - const sub_group_by = issueFilterStore?.userDisplayFilters?.sub_group_by ?? null; return ( <> {state && - (sub_group_by && type === "sub_group_by" ? ( - + (sub_group_by && header_type === "sub_group_by" ? ( + ) : ( - + ))} ); 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 91bd65b80be..2229818131a 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 @@ -1,26 +1,43 @@ import React from "react"; // lucide icons 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; } -export const HeaderSubGroupByCard = ({ icon, title }: IHeaderSubGroupByCard) => ( -
-
- - {/* */} -
+export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }: IHeaderSubGroupByCard) => { + const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); -
- {icon ? icon : } -
+ return ( +
+
issueKanBanViewStore?.handleKanBanToggle("subgroupByIssuesVisibility", column_id)} + > + {issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? ( + + ) : ( + + )} +
+ +
+ {icon ? icon : } +
-
-
{title}
-
(0)
+
+
{title}
+
{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 eef0cd1a669..df80eaa1e76 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 @@ -1,3 +1,5 @@ +// mobx +import { observer } from "mobx-react-lite"; // components import { StateHeader } from "./state"; import { StateGroupHeader } from "./state-group"; @@ -5,30 +7,64 @@ import { AssigneesHeader } from "./assignee"; import { PriorityHeader } from "./priority"; import { LabelHeader } from "./label"; import { CreatedByHeader } from "./created_by"; -// mobx -import { observer } from "mobx-react-lite"; -// mobx -import { useMobxStore } from "lib/mobx/store-provider"; -import { RootStore } from "store/root"; export interface IKanBanSubGroupByHeaderRoot { column_id: string; + sub_group_by: string | null; + group_by: string | null; } -export const KanBanSubGroupByHeaderRoot: React.FC = observer(({ column_id }) => { - const { issueFilter: issueFilterStore }: RootStore = useMobxStore(); - const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; - - return ( +export const KanBanSubGroupByHeaderRoot: React.FC = observer( + ({ column_id, sub_group_by, group_by }) => ( <> - {sub_group_by && sub_group_by === "state" && } + {sub_group_by && sub_group_by === "state" && ( + + )} {sub_group_by && sub_group_by === "state_detail.group" && ( - + + )} + {sub_group_by && sub_group_by === "priority" && ( + + )} + {sub_group_by && sub_group_by === "labels" && ( + + )} + {sub_group_by && sub_group_by === "assignees" && ( + + )} + {sub_group_by && sub_group_by === "created_by" && ( + )} - {sub_group_by && sub_group_by === "priority" && } - {sub_group_by && sub_group_by === "labels" && } - {sub_group_by && sub_group_by === "assignees" && } - {sub_group_by && sub_group_by === "created_by" && } - ); -}); + ) +); diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx new file mode 100644 index 00000000000..b2a8ce8ff89 --- /dev/null +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -0,0 +1,91 @@ +// lucide icons +import { Circle } from "lucide-react"; + +export const KanBanProperties = () => { + console.log("properties"); + return ( +
+ {/* basic properties */} + {/* state */} +
+
+ +
+
state
+
+ + {/* priority */} +
+
+ +
+
priority
+
+ + {/* label */} +
+
+ +
+
label
+
+ + {/* assignee */} +
+
+ +
+
assignee
+
+ + {/* start date */} +
+
+ +
+
start date
+
+ + {/* target/due date */} +
+
+ +
+
target/due date
+
+ + {/* extra render properties */} + {/* estimate */} +
+
+ +
+
0
+
+ + {/* sub-issues */} +
+
+ +
+
0
+
+ + {/* attachments */} +
+
+ +
+
0
+
+ + {/* link */} +
+
+ +
+
0
+
+
+ ); +}; diff --git a/web/components/issues/issue-layouts/kanban/root.tsx b/web/components/issues/issue-layouts/kanban/root.tsx index f919cfd75d1..5701a297d49 100644 --- a/web/components/issues/issue-layouts/kanban/root.tsx +++ b/web/components/issues/issue-layouts/kanban/root.tsx @@ -3,48 +3,32 @@ import React from "react"; 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"; -import { KanBanSwimLanes } from "./swimlanes"; -import { KanBan } from "./default"; -export interface IKanBanLayout { - issues?: any; - handleIssues?: () => void; - handleDragDrop?: (result: any) => void; -} - -interface IIssueVisibility { - kanban: string[]; - swimlanes: string[]; -} - -export const KanBanLayout: React.FC = observer(({}) => { - const [issueVisiility, setIssueVisibility] = React.useState({ - kanban: [], - swimlanes: [], - }); - const handleIssueVisibility = (key: "kanban" | "swimlanes", value: string) => { - setIssueVisibility((prevState: IIssueVisibility) => ({ - ...prevState, - [key]: prevState[key].includes(value) - ? prevState[key].filter((item) => item !== value) - : [...prevState[key], value], - })); - }; +export interface IKanBanLayout {} +export const KanBanLayout: 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 currentKanBanView: "swimlanes" | "default" = issueFilterStore?.userDisplayFilters?.sub_group_by ? "swimlanes" : "default"; - const issues = issueStore?.getIssues; - const onDragEnd = (result: any) => { if (!result) return; @@ -62,10 +46,13 @@ export const KanBanLayout: React.FC = observer(({}) => { }; return ( - //
- {currentKanBanView === "default" ? : } + {currentKanBanView === "default" ? ( + + ) : ( + + )}
); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index ed5d6769e0f..cfe42ae6748 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -12,110 +12,193 @@ import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; interface ISubGroupSwimlaneHeader { + sub_group_by: string | null; + group_by: string | null; list: any; listKey: string; } - -const SubGroupSwimlaneHeader: React.FC = ({ list, listKey }) => ( +const SubGroupSwimlaneHeader: React.FC = ({ sub_group_by, group_by, list, listKey }) => (
{list && list.length > 0 && list.map((_list: any) => (
- +
))}
); interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { - issues?: any; + issues: any; } +const SubGroupSwimlane: React.FC = observer(({ issues, sub_group_by, group_by, list, listKey }) => { + const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); -const SubGroupSwimlane: React.FC = ({ issues, list, listKey }) => ( -
- {list && - list.length > 0 && - list.map((_list: any) => ( -
-
-
- + return ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+
+
+ +
+
-
-
- -
- + {!issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes( + getValueFromObject(_list, listKey) as string + ) && ( +
+ +
+ )}
-
- ))} -
-); + ))} +
+ ); +}); export interface IKanBanSwimLanes { - issues?: any; + issues: any; + sub_group_by: string | null; + group_by: string | null; handleIssues?: () => void; - handleDragDrop?: () => void; } -export const KanBanSwimLanes: React.FC = observer(({ issues }) => { - const { project: projectStore, issueFilter: issueFilterStore }: RootStore = useMobxStore(); - - const group_by: string | null = issueFilterStore?.userDisplayFilters?.group_by || null; - const sub_group_by: string | null = issueFilterStore?.userDisplayFilters?.sub_group_by || null; +export const KanBanSwimLanes: React.FC = observer(({ issues, sub_group_by, group_by }) => { + const { project: projectStore }: RootStore = useMobxStore(); return (
{group_by && group_by === "state" && ( - + )} {group_by && group_by === "state_detail.group" && ( - + )} - {group_by && group_by === "priority" && } + {group_by && group_by === "priority" && ( + + )} {group_by && group_by === "labels" && ( - + )} {group_by && group_by === "assignees" && ( - + )} {group_by && group_by === "created_by" && ( - + )}
{sub_group_by && sub_group_by === "state" && ( - + )} {sub_group_by && sub_group_by === "state_detail.group" && ( - + )} {sub_group_by && sub_group_by === "priority" && ( - + )} {sub_group_by && sub_group_by === "labels" && ( - + )} {sub_group_by && sub_group_by === "assignees" && ( - + )} {sub_group_by && sub_group_by === "created_by" && ( - + )}
); diff --git a/web/store/kanban_view.ts b/web/store/kanban_view.ts index 4166354e65e..946a749a770 100644 --- a/web/store/kanban_view.ts +++ b/web/store/kanban_view.ts @@ -1,24 +1,42 @@ -import { action, computed, makeObservable, runInAction } from "mobx"; +import { action, computed, makeObservable, observable, runInAction } from "mobx"; // types import { RootStore } from "./root"; import { IIssueType } from "./issue"; export interface IIssueKanBanViewStore { + 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 IssueKanBanViewStore implements IIssueKanBanViewStore { + 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, }); @@ -50,6 +68,15 @@ class IssueKanBanViewStore implements IIssueKanBanViewStore { 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; From 7c861afee408ee4b00be764454eecc8f8caece9f Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 28 Sep 2023 15:39:48 +0530 Subject: [PATCH 4/5] chore: issues count render in kanban default and swimlanes --- .../issues/issue-layouts/kanban/default.tsx | 2 +- .../issue-layouts/kanban/headers/assignee.tsx | 6 +- .../kanban/headers/created_by.tsx | 6 +- .../kanban/headers/group-by-card.tsx | 10 +++- .../issue-layouts/kanban/headers/label.tsx | 2 +- .../issue-layouts/kanban/headers/priority.tsx | 2 +- .../kanban/headers/state-group.tsx | 2 +- .../issue-layouts/kanban/headers/state.tsx | 2 +- .../kanban/headers/sub-group-by-card.tsx | 4 +- .../issues/issue-layouts/kanban/swimlanes.tsx | 55 +++++++++++++------ 10 files changed, 62 insertions(+), 29 deletions(-) diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 6f7afc5e546..4666ddbb286 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -42,7 +42,7 @@ const GroupByKanBan: React.FC = observer( column_id={getValueFromObject(_list, listKey) as string} sub_group_by={sub_group_by} group_by={group_by} - issues_count={issues[getValueFromObject(_list, listKey) as string].length || 0} + issues_count={issues?.[getValueFromObject(_list, listKey) as string].length || 0} />
)} diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index 7ada66e4a81..811067b5f0c 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -25,7 +25,11 @@ export const AssigneesHeader: React.FC = observer( <> {assignee && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : ( = observer( <> {createdBy && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : (
-
{title}
-
{count || 0}
+
+ {title} +
+
+ {count || 0} +
{sub_group_by === null && ( diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index 67a89473895..d83834aab60 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -25,7 +25,7 @@ export const LabelHeader: React.FC = observer( <> {label && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : ( = observer( <> {priority && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : ( = observer( <> {stateGroup && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : ( = observer( <> {state && (sub_group_by && header_type === "sub_group_by" ? ( - + ) : (
-
{title}
-
{count || 0}
+
{title}
+
{count || 0}
); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 94b0c68e44b..5579b33cd1c 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -24,23 +24,33 @@ const SubGroupSwimlaneHeader: React.FC = ({ group_by, list, listKey, -}) => ( -
- {console.log("issues", issues)} - {list && - list.length > 0 && - list.map((_list: any) => ( -
- -
- ))} -
-); +}) => { + const calculateIssueCount = (column_id: string) => { + let issueCount = 0; + issues && + Object.keys(issues)?.forEach((_issueKey: any) => { + issueCount += issues?.[_issueKey]?.[column_id]?.length || 0; + }); + return issueCount; + }; + + return ( +
+ {list && + list.length > 0 && + list.map((_list: any) => ( +
+ +
+ ))} +
+ ); +}; interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { issues: any; @@ -48,6 +58,15 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { const SubGroupSwimlane: React.FC = observer(({ issues, sub_group_by, group_by, list, listKey }) => { const { issueKanBanView: issueKanBanViewStore }: RootStore = useMobxStore(); + const calculateIssueCount = (column_id: string) => { + let issueCount = 0; + issues?.[column_id] && + Object.keys(issues?.[column_id])?.forEach((_list: any) => { + issueCount += issues?.[column_id]?.[_list]?.length || 0; + }); + return issueCount; + }; + return (
{list && @@ -60,7 +79,7 @@ const SubGroupSwimlane: React.FC = observer(({ issues, sub_gr column_id={getValueFromObject(_list, listKey) as string} sub_group_by={sub_group_by} group_by={group_by} - issues_count={0} + issues_count={calculateIssueCount(getValueFromObject(_list, listKey) as string)} />
From f0c8f8cf37bfc3de0b0abd4266ac24c4143a0986 Mon Sep 17 00:00:00 2001 From: gurusainath Date: Thu, 28 Sep 2023 16:50:54 +0530 Subject: [PATCH 5/5] chore: Added icons to the group_by and sub_group_by in kanban and swimlanes --- .../issues/issue-layouts/kanban/block.tsx | 2 +- .../issues/issue-layouts/kanban/default.tsx | 2 +- .../issue-layouts/kanban/headers/assignee.tsx | 9 +++- .../kanban/headers/created_by.tsx | 9 ++-- .../kanban/headers/group-by-card.tsx | 6 +-- .../issue-layouts/kanban/headers/label.tsx | 16 ++++++-- .../issue-layouts/kanban/headers/priority.tsx | 41 +++++++++++++++++-- .../kanban/headers/state-group.tsx | 22 +++++++--- .../issue-layouts/kanban/headers/state.tsx | 13 ++++-- .../kanban/headers/sub-group-by-card.tsx | 4 +- 10 files changed, 97 insertions(+), 27 deletions(-) diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index e9c0619cb79..ad424df258f 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -48,7 +48,7 @@ export const IssueBlock = ({ sub_group_id, columnId, issues, isDragDisabled }: I ) : ( !isDragDisabled && (
-
Drop here
+ {/*
Drop here
*/}
) )} diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx index 4666ddbb286..842550e4009 100644 --- a/web/components/issues/issue-layouts/kanban/default.tsx +++ b/web/components/issues/issue-layouts/kanban/default.tsx @@ -68,7 +68,7 @@ const GroupByKanBan: React.FC = observer( ) : ( isDragDisabled && (
-
Drop here
+ {/*
Drop here
*/}
) )} diff --git a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx index 811067b5f0c..b913bc85f16 100644 --- a/web/components/issues/issue-layouts/kanban/headers/assignee.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/assignee.tsx @@ -1,8 +1,9 @@ +// mobx +import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; -// mobx -import { observer } from "mobx-react-lite"; +import { Avatar } from "components/ui"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -15,6 +16,8 @@ export interface IAssigneesHeader { issues_count: number; } +export const Icon = ({ user }: any) => ; + export const AssigneesHeader: React.FC = observer( ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { const { project: projectStore }: RootStore = useMobxStore(); @@ -27,6 +30,7 @@ export const AssigneesHeader: React.FC = observer( (sub_group_by && header_type === "sub_group_by" ? ( } title={assignee?.member?.display_name || ""} count={issues_count} /> @@ -35,6 +39,7 @@ export const AssigneesHeader: React.FC = observer( sub_group_by={sub_group_by} group_by={group_by} column_id={column_id} + icon={} title={assignee?.member?.display_name || ""} count={issues_count} /> 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 42f93c1fb55..d7c45e6427a 100644 --- a/web/components/issues/issue-layouts/kanban/headers/created_by.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/created_by.tsx @@ -1,8 +1,9 @@ +// mobx +import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; -// mobx -import { observer } from "mobx-react-lite"; +import { Icon } from "./assignee"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -26,8 +27,9 @@ export const CreatedByHeader: React.FC = observer( {createdBy && (sub_group_by && header_type === "sub_group_by" ? ( } + title={createdBy?.member?.display_name || ""} count={issues_count} /> ) : ( @@ -35,6 +37,7 @@ export const CreatedByHeader: React.FC = observer( sub_group_by={sub_group_by} group_by={group_by} column_id={column_id} + icon={} title={createdBy?.member?.display_name || ""} count={issues_count} /> 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 5dc00e52844..c59d6023f72 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 @@ -28,7 +28,7 @@ export const HeaderGroupByCard = observer( verticalAlignPosition ? `flex-col items-center w-[44px]` : `flex-row items-center w-full` }`} > -
+
{icon ? icon : }
@@ -45,7 +45,7 @@ export const HeaderGroupByCard = observer( {sub_group_by === null && (
issueKanBanViewStore?.handleKanBanToggle("groupByHeaderMinMax", column_id)} > {verticalAlignPosition ? ( @@ -56,7 +56,7 @@ export const HeaderGroupByCard = observer(
)} - {/*
+ {/*
*/}
diff --git a/web/components/issues/issue-layouts/kanban/headers/label.tsx b/web/components/issues/issue-layouts/kanban/headers/label.tsx index d83834aab60..135a1d1391f 100644 --- a/web/components/issues/issue-layouts/kanban/headers/label.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/label.tsx @@ -1,8 +1,8 @@ +// mobx +import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; -// mobx -import { observer } from "mobx-react-lite"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -15,6 +15,10 @@ export interface ILabelHeader { issues_count: number; } +const Icon = ({ color }: any) => ( +
+); + export const LabelHeader: React.FC = observer( ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { const { project: projectStore }: RootStore = useMobxStore(); @@ -25,12 +29,18 @@ export const LabelHeader: React.FC = observer( <> {label && (sub_group_by && header_type === "sub_group_by" ? ( - + } + title={label?.name || ""} + count={issues_count} + /> ) : ( } title={label?.name || ""} count={issues_count} /> diff --git a/web/components/issues/issue-layouts/kanban/headers/priority.tsx b/web/components/issues/issue-layouts/kanban/headers/priority.tsx index 97665b54761..38e4afbc4e5 100644 --- a/web/components/issues/issue-layouts/kanban/headers/priority.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/priority.tsx @@ -1,11 +1,12 @@ -import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; +// lucide icons +import { AlertCircle, SignalHigh, SignalMedium, SignalLow, Ban } from "lucide-react"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; // constants import { issuePriorityByKey } from "constants/issue"; -// mobx -import { observer } from "mobx-react-lite"; export interface IPriorityHeader { column_id: string; @@ -15,6 +16,32 @@ export interface IPriorityHeader { issues_count: number; } +const Icon = ({ priority }: any) => ( +
+ {priority === "urgent" ? ( +
+ +
+ ) : priority === "high" ? ( +
+ +
+ ) : priority === "medium" ? ( +
+ +
+ ) : priority === "low" ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+); + export const PriorityHeader: React.FC = observer( ({ column_id, sub_group_by, group_by, header_type, issues_count }) => { const priority = column_id && issuePriorityByKey(column_id); @@ -23,12 +50,18 @@ export const PriorityHeader: React.FC = observer( <> {priority && (sub_group_by && header_type === "sub_group_by" ? ( - + } + title={priority?.key || ""} + count={issues_count} + /> ) : ( } title={priority?.key || ""} count={issues_count} /> 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 fd583e544e2..4baa64dd08c 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state-group.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state-group.tsx @@ -1,11 +1,11 @@ -import React from "react"; +// mobx +import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; +import { StateGroupIcon } from "components/icons"; // constants import { issueStateGroupByKey } from "constants/issue"; -// mobx -import { observer } from "mobx-react-lite"; export interface IStateGroupHeader { column_id: string; @@ -15,22 +15,34 @@ export interface IStateGroupHeader { issues_count: number; } -export const StateIcon = () => {}; +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 }) => { const stateGroup = column_id && issueStateGroupByKey(column_id); + console.log("stateGroup", stateGroup); + return ( <> {stateGroup && (sub_group_by && header_type === "sub_group_by" ? ( - + } + title={stateGroup?.key || ""} + count={issues_count} + /> ) : ( } title={stateGroup?.key || ""} count={issues_count} /> diff --git a/web/components/issues/issue-layouts/kanban/headers/state.tsx b/web/components/issues/issue-layouts/kanban/headers/state.tsx index 4d355d88dba..8f1b35f993a 100644 --- a/web/components/issues/issue-layouts/kanban/headers/state.tsx +++ b/web/components/issues/issue-layouts/kanban/headers/state.tsx @@ -1,8 +1,9 @@ +// mobx +import { observer } from "mobx-react-lite"; // components import { HeaderGroupByCard } from "./group-by-card"; import { HeaderSubGroupByCard } from "./sub-group-by-card"; -// mobx -import { observer } from "mobx-react-lite"; +import { Icon } from "./state-group"; // store import { useMobxStore } from "lib/mobx/store-provider"; import { RootStore } from "store/root"; @@ -25,12 +26,18 @@ export const StateHeader: React.FC = observer( <> {state && (sub_group_by && header_type === "sub_group_by" ? ( - + } + title={state?.name || ""} + count={issues_count} + /> ) : ( } title={state?.name || ""} count={issues_count} /> 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 ced90e0b0bd..17545c29b95 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 @@ -20,7 +20,7 @@ export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }: return (
issueKanBanViewStore?.handleKanBanToggle("subgroupByIssuesVisibility", column_id)} > {issueKanBanViewStore.kanBanToggle?.subgroupByIssuesVisibility.includes(column_id) ? ( @@ -30,7 +30,7 @@ export const HeaderSubGroupByCard = observer(({ icon, title, count, column_id }: )}
-
+
{icon ? icon : }