feat: Mobx integration, List and Kanban boards implementation in plane space (#1844)

* feat: init mobx and issue filter

* feat: Implemented list and kanban views in plane space and integrated mobx.

* feat: updated store type check
This commit is contained in:
guru_sainath 2023-08-11 17:18:33 +05:30 committed by GitHub
parent ad4cdcc512
commit cd5e5b96da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
63 changed files with 2123 additions and 7 deletions

View file

@ -0,0 +1,32 @@
"use client";
// helpers
import { renderDateFormat } from "constants/helpers";
export const findHowManyDaysLeft = (date: string | Date) => {
const today = new Date();
const eventDate = new Date(date);
const timeDiff = Math.abs(eventDate.getTime() - today.getTime());
return Math.ceil(timeDiff / (1000 * 3600 * 24));
};
const validDate = (date: any, state: string): string => {
if (date === null || ["backlog", "unstarted", "cancelled"].includes(state))
return `bg-gray-500/10 text-gray-500 border-gray-500/50`;
else {
const today = new Date();
const dueDate = new Date(date);
if (dueDate < today) return `bg-red-500/10 text-red-500 border-red-500/50`;
else return `bg-green-500/10 text-green-500 border-green-500/50`;
}
};
export const IssueBlockDueDate = ({ due_date, state }: any) => (
<div
className={`h-[24px] rounded-sm flex px-2 items-center border border-gray-300 gap-1 text-gray-700 text-xs font-medium
${validDate(due_date, state)}`}
>
{renderDateFormat(due_date)}
</div>
);

View file

@ -0,0 +1,17 @@
"use client";
export const IssueBlockLabels = ({ labels }: any) => (
<div className="relative flex items-center flex-wrap gap-1">
{labels &&
labels.length > 0 &&
labels.map((_label: any) => (
<div
className={`h-[24px] rounded-sm flex px-1 items-center border gap-1 !bg-transparent !text-gray-700`}
style={{ backgroundColor: `${_label?.color}10`, borderColor: `${_label?.color}50` }}
>
<div className="w-[10px] h-[10px] rounded-full" style={{ backgroundColor: `${_label?.color}` }} />
<div className="text-sm">{_label?.name}</div>
</div>
))}
</div>
);

View file

@ -0,0 +1,17 @@
"use client";
// types
import { TIssuePriorityKey } from "store/types/issue";
// constants
import { issuePriorityFilter } from "constants/data";
export const IssueBlockPriority = ({ priority }: { priority: TIssuePriorityKey | null }) => {
const priority_detail = priority != null ? issuePriorityFilter(priority) : null;
if (priority_detail === null) return <></>;
return (
<div className={`w-[24px] h-[24px] rounded-sm flex justify-center items-center ${priority_detail?.className}`}>
<span className="material-symbols-rounded text-[16px]">{priority_detail?.icon}</span>
</div>
);
};

View file

@ -0,0 +1,18 @@
"use client";
// constants
import { issueGroupFilter } from "constants/data";
export const IssueBlockState = ({ state }: any) => {
const stateGroup = issueGroupFilter(state.group);
if (stateGroup === null) return <></>;
return (
<div
className={`h-[24px] rounded-sm flex px-1 items-center border ${stateGroup?.className} gap-1 !bg-transparent !text-gray-700`}
>
<stateGroup.icon />
<div className="text-sm">{state?.name}</div>
</div>
);
};

View file

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

View file

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

View file

@ -0,0 +1,57 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// components
import { IssueBlockPriority } from "components/issues/board-views/block-priority";
import { IssueBlockState } from "components/issues/board-views/block-state";
import { IssueBlockLabels } from "components/issues/board-views/block-labels";
import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
// interfaces
import { IIssue } from "store/types/issue";
import { RootStore } from "store/root";
export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
const store: RootStore = useMobxStore();
return (
<div className="p-2 px-3 bg-white space-y-2 rounded-sm shadow">
{/* id */}
<div className="flex-shrink-0 text-sm text-gray-600 w-[60px]">
{store?.project?.project?.identifier}-{issue?.sequence_id}
</div>
{/* name */}
<div className="font-medium text-gray-800 h-full line-clamp-2">{issue.name}</div>
{/* priority */}
<div className="relative flex items-center gap-3 w-full">
{issue?.priority && (
<div className="flex-shrink-0">
<IssueBlockPriority priority={issue?.priority} />
</div>
)}
{/* state */}
{issue?.state_detail && (
<div className="flex-shrink-0">
<IssueBlockState state={issue?.state_detail} />
</div>
)}
{/* labels */}
{issue?.label_details && issue?.label_details.length > 0 && (
<div className="flex-shrink-0">
<IssueBlockLabels labels={issue?.label_details} />
</div>
)}
{/* due date */}
{issue?.target_date && (
<div className="flex-shrink-0">
<IssueBlockDueDate due_date={issue?.target_date} group={issue?.state_detail?.group} />
</div>
)}
</div>
</div>
);
};

View file

@ -0,0 +1,31 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// interfaces
import { IIssueState } from "store/types/issue";
// constants
import { issueGroupFilter } from "constants/data";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
const store: RootStore = useMobxStore();
const stateGroup = issueGroupFilter(state.group);
if (stateGroup === null) return <></>;
return (
<div className="py-2 flex items-center gap-2">
<div className="w-[28px] h-[28px] flex justify-center items-center">
<stateGroup.icon />
</div>
<div className="font-medium capitalize">{state?.name}</div>
<div className="bg-gray-200/50 text-gray-700 font-medium text-xs w-full max-w-[26px] h-[20px] flex justify-center items-center rounded-full">
{store.issue.getCountOfIssuesByState(state.id)}
</div>
</div>
);
});

View file

@ -0,0 +1,44 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// components
import { IssueListHeader } from "components/issues/board-views/kanban/header";
import { IssueListBlock } from "components/issues/board-views/kanban/block";
// interfaces
import { IIssueState, IIssue } from "store/types/issue";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const IssueKanbanView = observer(() => {
const store: RootStore = useMobxStore();
return (
<div className="relative w-full h-full overflow-hidden overflow-x-auto flex gap-3">
{store?.issue?.states &&
store?.issue?.states.length > 0 &&
store?.issue?.states.map((_state: IIssueState) => (
<div className="flex-shrink-0 relative w-[340px] h-full flex flex-col">
<div className="flex-shrink-0">
<IssueListHeader state={_state} />
</div>
<div className="w-full h-full overflow-hidden overflow-y-auto">
{store.issue.getFilteredIssuesByState(_state.id) &&
store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
<div className="space-y-3 pb-2">
{store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
<IssueListBlock issue={_issue} />
))}
</div>
) : (
<div className="relative w-full h-full flex justify-center items-center p-10 text-center text-sm text-gray-600">
No Issues are available.
</div>
)}
</div>
</div>
))}
</div>
);
});

View file

@ -0,0 +1,59 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// components
import { IssueBlockPriority } from "components/issues/board-views/block-priority";
import { IssueBlockState } from "components/issues/board-views/block-state";
import { IssueBlockLabels } from "components/issues/board-views/block-labels";
import { IssueBlockDueDate } from "components/issues/board-views/block-due-date";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
// interfaces
import { IIssue } from "store/types/issue";
import { RootStore } from "store/root";
export const IssueListBlock = ({ issue }: { issue: IIssue }) => {
const store: RootStore = useMobxStore();
return (
<div className="p-2 px-3 relative flex items-center gap-3">
<div className="relative flex items-center gap-3 w-full">
{/* id */}
<div className="flex-shrink-0 text-sm text-gray-600 w-[60px]">
{store?.project?.project?.identifier}-{issue?.sequence_id}
</div>
{/* name */}
<div className="font-medium text-gray-800 h-full line-clamp-1">{issue.name}</div>
</div>
{/* priority */}
{issue?.priority && (
<div className="flex-shrink-0">
<IssueBlockPriority priority={issue?.priority} />
</div>
)}
{/* state */}
{issue?.state_detail && (
<div className="flex-shrink-0">
<IssueBlockState state={issue?.state_detail} />
</div>
)}
{/* labels */}
{issue?.label_details && issue?.label_details.length > 0 && (
<div className="flex-shrink-0">
<IssueBlockLabels labels={issue?.label_details} />
</div>
)}
{/* due date */}
{issue?.target_date && (
<div className="flex-shrink-0">
<IssueBlockDueDate due_date={issue?.target_date} group={issue?.state_detail?.group} />
</div>
)}
</div>
);
};

View file

@ -0,0 +1,31 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// interfaces
import { IIssueState } from "store/types/issue";
// constants
import { issueGroupFilter } from "constants/data";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const IssueListHeader = observer(({ state }: { state: IIssueState }) => {
const store: RootStore = useMobxStore();
const stateGroup = issueGroupFilter(state.group);
if (stateGroup === null) return <></>;
return (
<div className="py-2 px-3 flex items-center gap-2">
<div className="w-[28px] h-[28px] flex justify-center items-center">
<stateGroup.icon />
</div>
<div className="font-medium capitalize">{state?.name}</div>
<div className="bg-gray-200/50 text-gray-700 font-medium text-xs w-full max-w-[26px] h-[20px] flex justify-center items-center rounded-full">
{store.issue.getCountOfIssuesByState(state.id)}
</div>
</div>
);
});

View file

@ -0,0 +1,38 @@
"use client";
// mobx react lite
import { observer } from "mobx-react-lite";
// components
import { IssueListHeader } from "components/issues/board-views/list/header";
import { IssueListBlock } from "components/issues/board-views/list/block";
// interfaces
import { IIssueState, IIssue } from "store/types/issue";
// mobx hook
import { useMobxStore } from "lib/mobx/store-provider";
import { RootStore } from "store/root";
export const IssueListView = observer(() => {
const store: RootStore = useMobxStore();
return (
<>
{store?.issue?.states &&
store?.issue?.states.length > 0 &&
store?.issue?.states.map((_state: IIssueState) => (
<div className="relative w-full">
<IssueListHeader state={_state} />
{store.issue.getFilteredIssuesByState(_state.id) &&
store.issue.getFilteredIssuesByState(_state.id).length > 0 ? (
<div className="bg-white divide-y">
{store.issue.getFilteredIssuesByState(_state.id).map((_issue: IIssue) => (
<IssueListBlock issue={_issue} />
))}
</div>
) : (
<div className="bg-white p-5 text-sm text-gray-600">No Issues are available.</div>
)}
</div>
))}
</>
);
});

View file

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