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:
parent
ad4cdcc512
commit
cd5e5b96da
63 changed files with 2123 additions and 7 deletions
32
apps/space/components/issues/board-views/block-due-date.tsx
Normal file
32
apps/space/components/issues/board-views/block-due-date.tsx
Normal 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>
|
||||
);
|
||||
17
apps/space/components/issues/board-views/block-labels.tsx
Normal file
17
apps/space/components/issues/board-views/block-labels.tsx
Normal 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>
|
||||
);
|
||||
17
apps/space/components/issues/board-views/block-priority.tsx
Normal file
17
apps/space/components/issues/board-views/block-priority.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
18
apps/space/components/issues/board-views/block-state.tsx
Normal file
18
apps/space/components/issues/board-views/block-state.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const IssueCalendarView = () => <div> </div>;
|
||||
1
apps/space/components/issues/board-views/gantt/index.tsx
Normal file
1
apps/space/components/issues/board-views/gantt/index.tsx
Normal file
|
|
@ -0,0 +1 @@
|
|||
export const IssueGanttView = () => <div> </div>;
|
||||
57
apps/space/components/issues/board-views/kanban/block.tsx
Normal file
57
apps/space/components/issues/board-views/kanban/block.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
apps/space/components/issues/board-views/kanban/header.tsx
Normal file
31
apps/space/components/issues/board-views/kanban/header.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
44
apps/space/components/issues/board-views/kanban/index.tsx
Normal file
44
apps/space/components/issues/board-views/kanban/index.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
59
apps/space/components/issues/board-views/list/block.tsx
Normal file
59
apps/space/components/issues/board-views/list/block.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
31
apps/space/components/issues/board-views/list/header.tsx
Normal file
31
apps/space/components/issues/board-views/list/header.tsx
Normal 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>
|
||||
);
|
||||
});
|
||||
38
apps/space/components/issues/board-views/list/index.tsx
Normal file
38
apps/space/components/issues/board-views/list/index.tsx
Normal 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>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
export const IssueSpreadsheetView = () => <div> </div>;
|
||||
Loading…
Add table
Add a link
Reference in a new issue