From ef630ef66383a16802a55bf9a5c41e634dd59d4f Mon Sep 17 00:00:00 2001 From: gurusainath Date: Tue, 5 Sep 2023 23:37:52 +0530 Subject: [PATCH] chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd --- .../issue-layouts/kanban/content.tsx | 52 ++++++ .../issue-layouts/kanban/footer.tsx | 1 + .../issue-layouts/kanban/header.tsx | 1 + web/components/issue-layouts/kanban/index.tsx | 70 ++++++++ web/components/kanban-layout/index.tsx | 11 -- web/pages/kanban.tsx | 29 ++++ web/services/issues.service.ts | 2 +- web/store/issue-views/data.ts | 60 +++++++ web/store/issue-views/filters.ts | 154 ++++++++++++++++++ web/store/issue-views/kanban.ts | 92 +++++++++++ web/store/kanban.ts | 82 ---------- web/store/root.ts | 6 +- 12 files changed, 465 insertions(+), 95 deletions(-) create mode 100644 web/components/issue-layouts/kanban/content.tsx create mode 100644 web/components/issue-layouts/kanban/footer.tsx create mode 100644 web/components/issue-layouts/kanban/header.tsx create mode 100644 web/components/issue-layouts/kanban/index.tsx delete mode 100644 web/components/kanban-layout/index.tsx create mode 100644 web/pages/kanban.tsx create mode 100644 web/store/issue-views/data.ts create mode 100644 web/store/issue-views/filters.ts create mode 100644 web/store/issue-views/kanban.ts delete mode 100644 web/store/kanban.ts diff --git a/web/components/issue-layouts/kanban/content.tsx b/web/components/issue-layouts/kanban/content.tsx new file mode 100644 index 000000000..15e34becd --- /dev/null +++ b/web/components/issue-layouts/kanban/content.tsx @@ -0,0 +1,52 @@ +// react beautiful dnd +import { Draggable } from "react-beautiful-dnd"; + +interface IssueContentProps { + columnId: string; + issues: any; +} + +export const IssueContent = ({ columnId, issues }: IssueContentProps) => { + console.log(); + + return ( + <> + {issues && issues.length > 0 ? ( + <> + {issues.map((issue: any, index: any) => ( + + {(provided: any, snapshot: any) => ( +
+
+
+ ONE-{issue.sequence_id}-{issue.sort_order} +
+
+ {issue.name} {issue.name} {issue.name} {issue.name} {issue.name} {issue.name}{" "} + {issue.name} {issue.name} +
+
Footer
+
+
+ )} +
+ ))} + + ) : ( +
No issues are available.
+ )} + + ); +}; diff --git a/web/components/issue-layouts/kanban/footer.tsx b/web/components/issue-layouts/kanban/footer.tsx new file mode 100644 index 000000000..21b09c11f --- /dev/null +++ b/web/components/issue-layouts/kanban/footer.tsx @@ -0,0 +1 @@ +export const IssueFooter = () =>
Footer
; diff --git a/web/components/issue-layouts/kanban/header.tsx b/web/components/issue-layouts/kanban/header.tsx new file mode 100644 index 000000000..4852fabd9 --- /dev/null +++ b/web/components/issue-layouts/kanban/header.tsx @@ -0,0 +1 @@ +export const IssueHeader = () =>
Header
; diff --git a/web/components/issue-layouts/kanban/index.tsx b/web/components/issue-layouts/kanban/index.tsx new file mode 100644 index 000000000..eaa6e4468 --- /dev/null +++ b/web/components/issue-layouts/kanban/index.tsx @@ -0,0 +1,70 @@ +import React from "react"; +// react beautiful dnd +import { DragDropContext, Droppable } from "react-beautiful-dnd"; +// components +import { IssueHeader } from "./header"; +import { IssueContent } from "./content"; +import { IssueFooter } from "./footer"; +// mobx +import { observer } from "mobx-react-lite"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +export const IssueRoot = observer(() => { + const store: RootStore = useMobxStore(); + const { kanban: issueViewStore } = store; + + const onDragEnd = (result: any) => { + console.log("result", result); + }; + + return ( +
+ {issueViewStore.loader && issueViewStore?.issues === null ? ( +
Loading...
+ ) : ( +
+ {issueViewStore?.getIssues && Object.keys(issueViewStore?.getIssues).length > 0 ? ( +
+ + {Object.keys(issueViewStore?.getIssues).map((_issueStateKey: any) => ( +
+
+ +
+ +
+ + {(provided: any, snapshot: any) => ( + <> +
+ +
+ {provided.placeholder} + + )} +
+
+ +
+ +
+
+ ))} +
+
+ ) : ( +
No Issues are available
+ )} +
+ )} +
+ ); +}); diff --git a/web/components/kanban-layout/index.tsx b/web/components/kanban-layout/index.tsx deleted file mode 100644 index cd910124d..000000000 --- a/web/components/kanban-layout/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from "react"; - -export const KanbanInitLayout = () => { - console.log(""); - - return ( -
-
Hello
-
- ); -}; diff --git a/web/pages/kanban.tsx b/web/pages/kanban.tsx new file mode 100644 index 000000000..a25d75824 --- /dev/null +++ b/web/pages/kanban.tsx @@ -0,0 +1,29 @@ +import React from "react"; +// swr +import useSWR from "swr"; +// components +import { IssueRoot } from "components/issue-layouts/kanban"; +// mobx store +import { useMobxStore } from "lib/mobx/store-provider"; +import { RootStore } from "store/root"; + +const KanbanViewRoot = () => { + const workspaceSlug: string = "plane-demo"; + const projectSlug: string = "5b0e3f6e-c9f1-444d-be22-a8c2706bcf54"; + + const store: RootStore = useMobxStore(); + const { kanban: issueViewStore } = store; + + useSWR(`PROJECT_ISSUES_KANBAN_VIEW`, () => { + if (workspaceSlug && projectSlug) + issueViewStore.getIssuesAsync(workspaceSlug, projectSlug, "kanban"); + }); + + return ( +
+ +
+ ); +}; + +export default KanbanViewRoot; diff --git a/web/services/issues.service.ts b/web/services/issues.service.ts index b8875e6c5..c6e122a3b 100644 --- a/web/services/issues.service.ts +++ b/web/services/issues.service.ts @@ -17,7 +17,7 @@ const { NEXT_PUBLIC_API_BASE_URL } = process.env; const trackEvent = process.env.NEXT_PUBLIC_TRACK_EVENTS === "true" || process.env.NEXT_PUBLIC_TRACK_EVENTS === "1"; -class ProjectIssuesServices extends APIService { +export class ProjectIssuesServices extends APIService { constructor() { super(NEXT_PUBLIC_API_BASE_URL || "http://localhost:8000"); } diff --git a/web/store/issue-views/data.ts b/web/store/issue-views/data.ts new file mode 100644 index 000000000..559084e08 --- /dev/null +++ b/web/store/issue-views/data.ts @@ -0,0 +1,60 @@ +export const filtersPriority: { key: string; title: string }[] = [ + { key: "", title: "Urgent" }, + { key: "", title: "High" }, + { key: "", title: "Medium" }, + { key: "", title: "Low" }, + { key: "", title: "None" }, +]; + +export const filtersStartDate = [ + { key: "", title: "Last Week" }, + { key: "", title: "2 weeks from now" }, + { key: "", title: "1 month from now" }, + { key: "", title: "2 months from now" }, + { key: "", title: "Custom" }, +]; + +export const filtersDueDate = [ + { key: "", title: "Last Week" }, + { key: "", title: "2 weeks from now" }, + { key: "", title: "1 month from now" }, + { key: "", title: "2 months from now" }, + { key: "", title: "Custom" }, +]; + +export const displayPropertyGroupBy = [ + { key: "state", title: "States" }, + { key: "state_detail.group", title: "State Groups" }, + { key: "priority", title: "Priority" }, + { key: "labels", title: "Labels" }, + { key: "assignees", title: "Assignees" }, + { key: "created_by", title: "Created By" }, +]; + +export const displayPropertyOrderBy = [ + { key: "sort_order", title: "Manual" }, + { key: "created_at", title: "Last Created" }, + { key: "updated_at", title: "Last Updated" }, + { key: "start_date", title: "Start Date" }, + { key: "priority", title: "Priority" }, +]; + +export const displayPropertyIssueType = [ + { key: "all", title: "All" }, + { key: "active", title: "Active Issues" }, + { key: "backlog", title: "Backlog Issues" }, +]; + +export const displayProperties = [ + { key: "assignee", title: "Assignee" }, + { key: "start_date", title: "Start Date" }, + { key: "due_date", title: "Due Date" }, + { key: "key", title: "Id" }, + { key: "labels", title: "Labels" }, + { key: "priority", title: "Priority" }, + { key: "state", title: "State" }, + { key: "sub_issue_count", title: "Sub Issue Count" }, + { key: "attachment_count", title: "Attachment Count" }, + { key: "link", title: "Link" }, + { key: "estimate", title: "Estimate" }, +]; diff --git a/web/store/issue-views/filters.ts b/web/store/issue-views/filters.ts new file mode 100644 index 000000000..2f79b9d3d --- /dev/null +++ b/web/store/issue-views/filters.ts @@ -0,0 +1,154 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// types +import { RootStore } from "../root"; +// services +import {} from "services/issues.service"; + +export type TIssueViews = "list" | "kanban" | "calendar" | "spreadsheet" | "gantt"; + +export interface IIssueFilterStore { + loader: boolean; + error: any | null; + + // current issue view + workspaceId: string | null; + projectId: string | null; + issueView: string | null; + + // filters + priority?: null; + state?: null; + assignees?: null; + createdBy?: null; + labels?: null; + startDate?: null; + dueDate?: null; + userSelectedParams?: { + assignees: undefined | string; + created_by: undefined | string; + group_by: undefined | string; + labels: undefined | string; + order_by: undefined | string; + priority: undefined | string; + start_date: undefined | string; + state: undefined | string; + sub_issue: boolean; + target_date: undefined | string; + type: undefined | string; + }; + + // display properties + displayProperties?: { + assignee: boolean; + attachment_count: boolean; + created_on: boolean; + due_date: boolean; + estimate: boolean; + key: boolean; + labels: boolean; + link: boolean; + priority: boolean; + start_date: boolean; + state: boolean; + sub_issue_count: boolean; + updated_on: boolean; + }; + + // extra's + showEmptyGroups?: boolean; + + // actions + getProjectIssueFilterProperties: () => any | Promise; + getProjectIssueDisplayProperties: () => any | Promise; + getProjectMembers: () => any | Promise; + getProjectStates: () => any | Promise; + getProjectLabels: () => any | Promise; +} + +class IssueFilterStore implements IIssueFilterStore { + loader: boolean = false; + error: any | null = null; + + workspaceId: string | null = null; + projectId: string | null = null; + issueView: string | null = null; + + // root store + rootStore; + // service + projectPublishService = null; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + loader: observable, + error: observable, + + workspaceId: observable, + projectId: observable, + + // action + getProjectIssueFilterProperties: action, + getProjectIssueDisplayProperties: action, + getProjectMembers: action, + getProjectStates: action, + getProjectLabels: action, + // computed + }); + + this.rootStore = _rootStore; + this.projectPublishService = null; + } + + // computed functions starts + getComputedFilters = ( + _workspaceId: string, + _projectId: string, + _view: TIssueViews | null = "kanban" + ) => { + this.workspaceId = _workspaceId; + this.projectId = _projectId; + this.issueView = _view; + + let filteredRouteParams = { + assignees: undefined, // ['user_id', 'user_id'] + state: undefined, // ['state_id', 'state_id'] + priority: undefined, // ['low', 'high', 'medium', 'urgent', 'null'] + type: undefined, // 'active' (started, un_started) || 'backlog' || 'null' (all_the_issues) + labels: undefined, // ['label_id', 'label_id'] + created_by: undefined, // ['user_id', 'user_id'] + start_date: undefined, // ['yyyy-mm-dd:after/before', 'yyyy-mm-dd:after/before'] + target_date: undefined, // [yyyy-mm-dd:after, yyyy-mm-dd:before] + order_by: "-created_at", // TIssueOrderByOptions + group_by: "state", // TIssueGroupByOptions + sub_issue: true, // true for all other views except spreadsheet + }; + + if (_view === "list") filteredRouteParams = { ...filteredRouteParams }; + if (_view === "kanban") filteredRouteParams = { ...filteredRouteParams }; + if (_view === "calendar") filteredRouteParams = { ...filteredRouteParams }; + if (_view === "spreadsheet") filteredRouteParams = { ...filteredRouteParams }; + if (_view === "gantt") filteredRouteParams = { ...filteredRouteParams }; + + return filteredRouteParams; + }; + + // computed functions ends + + // fetching current user project issue filter and display settings + getProjectIssueFilterProperties = () => {}; + + // fetching display properties + getProjectIssueDisplayProperties = () => {}; + + // fetching project members + getProjectMembers = () => {}; + + // fetching project state <-> groups + getProjectStates = () => {}; + + // fetching project labels + getProjectLabels = () => {}; +} + +export default IssueFilterStore; diff --git a/web/store/issue-views/kanban.ts b/web/store/issue-views/kanban.ts new file mode 100644 index 000000000..34a9e5b14 --- /dev/null +++ b/web/store/issue-views/kanban.ts @@ -0,0 +1,92 @@ +import { observable, action, computed, makeObservable, runInAction } from "mobx"; +// types +import { RootStore } from "../root"; +// services +import { ProjectIssuesServices } from "services/issues.service"; +// types +import { TIssueViews } from "./filters"; + +export interface IKanbanStore { + loader: boolean; + error: any | null; + issues: { [key: string]: any } | null; + + getIssuesAsync: ( + workspaceId: string, + projectId: string, + view: TIssueViews | null + ) => null | Promise; +} + +class KanbanStore implements IKanbanStore { + loader: boolean = false; + error: any | null = null; + + issues: { [key: string]: any } | null = null; + + // root store + rootStore; + // service + issueService; + + constructor(_rootStore: RootStore) { + makeObservable(this, { + // observable + loader: observable, + error: observable, + + issues: observable.ref, + // action + getIssuesAsync: action, + // computed + }); + + this.rootStore = _rootStore; + this.issueService = new ProjectIssuesServices(); + } + + // computed + get getIssues() { + if (this.rootStore.issueFilters.projectId && this.issues != null) + return this.issues[this.rootStore.issueFilters.projectId]; + else return null; + } + + // fetching issues + getIssuesAsync = async (workspaceId: string, projectId: string, view: TIssueViews | null) => { + try { + this.loader = true; + this.error = null; + + const filteredParams = this.rootStore.issueFilters.getComputedFilters( + workspaceId, + projectId, + view + ); + const issuesResponse = await this.issueService.getIssuesWithParams( + workspaceId, + projectId, + filteredParams + ); + + if (issuesResponse) { + runInAction(() => { + this.issues = { ...this.issues, [projectId]: { ...issuesResponse } }; + this.loader = false; + this.error = null; + }); + } + + return issuesResponse; + } catch (error) { + console.warn("error", error); + this.loader = false; + this.error = null; + return error; + } + }; + + // handle issue drag and drop +} + +export default KanbanStore; diff --git a/web/store/kanban.ts b/web/store/kanban.ts deleted file mode 100644 index 81730032f..000000000 --- a/web/store/kanban.ts +++ /dev/null @@ -1,82 +0,0 @@ -import { observable, action, computed, makeObservable, runInAction } from "mobx"; -// types -import { RootStore } from "./root"; -// services - -export interface IKanbanStore { - loader: boolean; - error: any | null; - - // current issue view - issueView?: "kanban"; - - // filters - priority?: null; - state?: null; - assignees?: null; - createdBy?: null; - labels?: null; - startDate?: null; - dueDate?: null; - userSelectedParams?: { - assignees: undefined | string; - created_by: undefined | string; - group_by: undefined | string; - labels: undefined | string; - order_by: undefined | string; - priority: undefined | string; - start_date: undefined | string; - state: undefined | string; - sub_issue: boolean; - target_date: undefined | string; - type: undefined | string; - }; - - // display properties - displayProperties?: { - assignee: boolean; - attachment_count: boolean; - created_on: boolean; - due_date: boolean; - estimate: boolean; - key: boolean; - labels: boolean; - link: boolean; - priority: boolean; - start_date: boolean; - state: boolean; - sub_issue_count: boolean; - updated_on: boolean; - }; - - // extra's - showEmptyGroups?: boolean; - - issues?: null; -} - -class KanbanStore implements IKanbanStore { - loader: boolean = false; - error: any | null = null; - - // root store - rootStore; - // service - projectPublishService = null; - - constructor(_rootStore: RootStore) { - makeObservable(this, { - // observable - loader: observable, - error: observable, - - // action - // computed - }); - - this.rootStore = _rootStore; - this.projectPublishService = null; - } -} - -export default KanbanStore; diff --git a/web/store/root.ts b/web/store/root.ts index 2e81922d2..eba9aff95 100644 --- a/web/store/root.ts +++ b/web/store/root.ts @@ -5,8 +5,10 @@ import UserStore from "./user"; import ThemeStore from "./theme"; import ProjectStore, { IProjectStore } from "./project"; import ProjectPublishStore, { IProjectPublishStore } from "./project-publish"; -import KanbanStore from "./kanban"; import IssuesStore from "./issues"; +// issues views and filters +import IssueFilterStore from "./issue-views/filters"; +import KanbanStore from "./issue-views/kanban"; enableStaticRendering(typeof window === "undefined"); @@ -16,6 +18,7 @@ export class RootStore { project: IProjectStore; projectPublish: IProjectPublishStore; issues: IssuesStore; + issueFilters: IssueFilterStore; kanban: KanbanStore; constructor() { @@ -24,6 +27,7 @@ export class RootStore { this.project = new ProjectStore(this); this.projectPublish = new ProjectPublishStore(this); this.issues = new IssuesStore(this); + this.issueFilters = new IssueFilterStore(this); this.kanban = new KanbanStore(this); } }