chore: Implemented new kanaban board UX and implemented draggable using react beautiful dnd

This commit is contained in:
gurusainath 2023-09-05 23:37:52 +05:30
parent 731309a932
commit ef630ef663
12 changed files with 465 additions and 95 deletions

View File

@ -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) => (
<Draggable
draggableId={issue.id}
index={index}
key={`issue-blocks-${columnId}-${issue.id}`}
>
{(provided: any, snapshot: any) => (
<div
key={issue.id}
className="p-2"
{...provided.draggableProps}
{...provided.dragHandleProps}
ref={provided.innerRef}
>
<div
className={`text-sm rounded p-2 px-3 bg-white shadow space-y-1 border border-transparent`}
>
<div className="text-xs line-clamp-1 text-gray-500 font-medium">
ONE-{issue.sequence_id}-{issue.sort_order}
</div>
<div className="line-clamp-2 h-[42px]">
{issue.name} {issue.name} {issue.name} {issue.name} {issue.name} {issue.name}{" "}
{issue.name} {issue.name}
</div>
<div>Footer</div>
</div>
</div>
)}
</Draggable>
))}
</>
) : (
<div>No issues are available.</div>
)}
</>
);
};

View File

@ -0,0 +1 @@
export const IssueFooter = () => <div>Footer</div>;

View File

@ -0,0 +1 @@
export const IssueHeader = () => <div>Header</div>;

View File

@ -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 (
<div className="relative w-screen h-screen">
{issueViewStore.loader && issueViewStore?.issues === null ? (
<div>Loading...</div>
) : (
<div className="relative w-full h-full p-2">
{issueViewStore?.getIssues && Object.keys(issueViewStore?.getIssues).length > 0 ? (
<div className="relative overflow-hidden overflow-x-auto w-full h-full flex">
<DragDropContext onDragEnd={onDragEnd}>
{Object.keys(issueViewStore?.getIssues).map((_issueStateKey: any) => (
<div
key={`${_issueStateKey}`}
className="flex-shrink-0 w-[380px] h-full relative flex flex-col"
>
<div className="flex-shrink-0 w-full p-2 py-1">
<IssueHeader />
</div>
<div className="w-full h-full overflow-auto">
<Droppable droppableId={`${_issueStateKey}`}>
{(provided: any, snapshot: any) => (
<>
<div {...provided.droppableProps} ref={provided.innerRef}>
<IssueContent
columnId={_issueStateKey}
issues={issueViewStore?.getIssues[_issueStateKey]}
/>
</div>
{provided.placeholder}
</>
)}
</Droppable>
</div>
<div className="flex-shrink-0 w-full p-2 py-1">
<IssueFooter />
</div>
</div>
))}
</DragDropContext>
</div>
) : (
<div>No Issues are available</div>
)}
</div>
)}
</div>
);
});

View File

@ -1,11 +0,0 @@
import React from "react";
export const KanbanInitLayout = () => {
console.log("");
return (
<div>
<div>Hello</div>
</div>
);
};

29
web/pages/kanban.tsx Normal file
View File

@ -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 (
<div>
<IssueRoot />
</div>
);
};
export default KanbanViewRoot;

View File

@ -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");
}

View File

@ -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" },
];

View File

@ -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<any>;
getProjectIssueDisplayProperties: () => any | Promise<any>;
getProjectMembers: () => any | Promise<any>;
getProjectStates: () => any | Promise<any>;
getProjectLabels: () => any | Promise<any>;
}
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;

View File

@ -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<any>;
}
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;

View File

@ -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;

View File

@ -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);
}
}