Kanban new store implementation for project issues

This commit is contained in:
rahulramesha 2023-12-15 17:57:43 +05:30
parent 78bc9f615f
commit fd1f262bf7
21 changed files with 672 additions and 248 deletions

View File

@ -6,17 +6,21 @@ import { Button } from "@plane/ui";
// hooks // hooks
import useToast from "hooks/use-toast"; import useToast from "hooks/use-toast";
// types // types
import type { IIssue } from "types"; import { useIssues } from "hooks/store/use-issues";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
handleClose: () => void; handleClose: () => void;
data: IIssue; dataId: string;
onSubmit?: () => Promise<void>; onSubmit?: () => Promise<void>;
}; };
export const DeleteIssueModal: React.FC<Props> = (props) => { export const DeleteIssueModal: React.FC<Props> = (props) => {
const { data, isOpen, handleClose, onSubmit } = props; const { dataId, isOpen, handleClose, onSubmit } = props;
const { issueMap } = useIssues();
const issue = issueMap.allIssues[dataId];
const [isDeleteLoading, setIsDeleteLoading] = useState(false); const [isDeleteLoading, setIsDeleteLoading] = useState(false);
@ -93,7 +97,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
<p className="text-sm text-custom-text-200"> <p className="text-sm text-custom-text-200">
Are you sure you want to delete issue{" "} Are you sure you want to delete issue{" "}
<span className="break-words font-medium text-custom-text-100"> <span className="break-words font-medium text-custom-text-100">
{data?.project_detail?.identifier}-{data?.sequence_id} {issue?.project_detail?.identifier}-{issue?.sequence_id}
</span> </span>
{""}? All of the data related to the issue will be permanently removed. This action cannot be {""}? All of the data related to the issue will be permanently removed. This action cannot be
undone. undone.

View File

@ -12,35 +12,26 @@ import { IIssue } from "types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { import {
ICycleIssuesFilterStore, ICycleIssuesFilterStore,
ICycleIssuesStore,
IModuleIssuesFilterStore, IModuleIssuesFilterStore,
IModuleIssuesStore,
IProfileIssuesFilterStore, IProfileIssuesFilterStore,
IProfileIssuesStore,
IProjectDraftIssuesStore,
IProjectIssuesFilterStore, IProjectIssuesFilterStore,
IProjectIssuesStore,
IViewIssuesFilterStore, IViewIssuesFilterStore,
IViewIssuesStore,
} from "store_legacy/issues"; } from "store_legacy/issues";
import { IQuickActionProps } from "../list/list-view-types"; import { IQuickActionProps } from "../list/list-view-types";
import { IIssueKanBanViewStore } from "store_legacy/issue"; import { IProjectIssues } from "store/issue/project";
//components //components
import { KanBan } from "./default"; import { KanBan } from "./default";
import { KanBanSwimLanes } from "./swimlanes"; import { KanBanSwimLanes } from "./swimlanes";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
import { DeleteIssueModal, IssuePeekOverview } from "components/issues"; import { DeleteIssueModal, IssuePeekOverview } from "components/issues";
import { EUserProjectRoles } from "constants/project"; import { EUserProjectRoles } from "constants/project";
import { useIssues } from "hooks/store/use-issues";
import { handleDragDrop } from "./utils";
import { IIssueKanBanViewStore } from "store/issue/issue_kanban_view.store";
export interface IBaseKanBanLayout { export interface IBaseKanBanLayout {
issueStore: issues: IProjectIssues;
| IProjectIssuesStore issuesFilter:
| IModuleIssuesStore
| ICycleIssuesStore
| IViewIssuesStore
| IProjectDraftIssuesStore
| IProfileIssuesStore;
issuesFilterStore:
| IProjectIssuesFilterStore | IProjectIssuesFilterStore
| IModuleIssuesFilterStore | IModuleIssuesFilterStore
| ICycleIssuesFilterStore | ICycleIssuesFilterStore
@ -56,14 +47,6 @@ export interface IBaseKanBanLayout {
showLoader?: boolean; showLoader?: boolean;
viewId?: string; viewId?: string;
currentStore?: EProjectStore; currentStore?: EProjectStore;
handleDragDrop?: (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: any,
issueWithIds: any
) => Promise<IIssue | undefined>;
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>; addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
canEditPropertiesBasedOnProject?: (projectId: string) => boolean; canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
} }
@ -76,33 +59,31 @@ type KanbanDragState = {
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => { export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBaseKanBanLayout) => {
const { const {
issueStore, issues,
issuesFilterStore, issuesFilter,
kanbanViewStore, kanbanViewStore,
QuickActions, QuickActions,
issueActions, issueActions,
showLoader, showLoader,
viewId, viewId,
currentStore, currentStore,
handleDragDrop,
addIssuesToView, addIssuesToView,
canEditPropertiesBasedOnProject, canEditPropertiesBasedOnProject,
} = props; } = props;
// router // router
const router = useRouter(); const router = useRouter();
const { workspaceSlug, peekIssueId, peekProjectId } = router.query; const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query;
// store hooks // store hooks
const { const {
membership: { currentProjectRole }, membership: { currentProjectRole },
} = useUser(); } = useUser();
const { issueMap } = useIssues();
// toast alert // toast alert
const { setToastAlert } = useToast(); const { setToastAlert } = useToast();
const issues = issueStore?.getIssues || {}; const issueIds = issues?.getIssuesIds || [];
const issueIds = issueStore?.getIssuesIds || [];
const displayFilters = issuesFilterStore?.issueFilters?.displayFilters; const displayFilters = issuesFilter?.issueFilters?.displayFilters;
const displayProperties = issuesFilterStore?.issueFilters?.displayProperties || null;
const sub_group_by: string | null = displayFilters?.sub_group_by || null; const sub_group_by: string | null = displayFilters?.sub_group_by || null;
const group_by: string | null = displayFilters?.group_by || null; const group_by: string | null = displayFilters?.group_by || null;
@ -111,7 +92,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan; const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan;
const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {};
// states // states
const [isDragStarted, setIsDragStarted] = useState<boolean>(false); const [isDragStarted, setIsDragStarted] = useState<boolean>(false);
@ -161,15 +142,23 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
}); });
setDeleteIssueModal(true); setDeleteIssueModal(true);
} else { } else {
await handleDragDrop(result.source, result.destination, sub_group_by, group_by, issues, issueIds).catch( await handleDragDrop(
(err) => { result.source,
setToastAlert({ result.destination,
title: "Error", workspaceSlug.toString(),
type: "error", projectId.toString(),
message: err.detail ?? "Failed to perform this action", issues,
}); sub_group_by,
} group_by,
); issueMap,
issueIds
).catch((err) => {
setToastAlert({
title: "Error",
type: "error",
message: err.detail ?? "Failed to perform this action",
});
});
} }
} }
}; };
@ -202,12 +191,20 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
const handleDeleteIssue = async () => { const handleDeleteIssue = async () => {
if (!handleDragDrop) return; if (!handleDragDrop) return;
await handleDragDrop(dragState.source, dragState.destination, sub_group_by, group_by, issues, issueIds).finally( await handleDragDrop(
() => { dragState.source,
setDeleteIssueModal(false); dragState.destination,
setDragState({}); workspaceSlug.toString(),
} projectId.toString(),
); issues,
sub_group_by,
group_by,
issueMap,
issueIds
).finally(() => {
setDeleteIssueModal(false);
setDragState({});
});
}; };
const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
@ -217,13 +214,13 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
return ( return (
<> <>
<DeleteIssueModal <DeleteIssueModal
data={dragState.draggedIssueId ? issues[dragState.draggedIssueId] : ({} as IIssue)} dataId={dragState.draggedIssueId}
isOpen={deleteIssueModal} isOpen={deleteIssueModal}
handleClose={() => setDeleteIssueModal(false)} handleClose={() => setDeleteIssueModal(false)}
onSubmit={handleDeleteIssue} onSubmit={handleDeleteIssue}
/> />
{showLoader && issueStore?.loader === "init-loader" && ( {showLoader && issues?.loader === "init-loader" && (
<div className="fixed right-2 top-16 z-30 flex h-10 w-10 items-center justify-center rounded bg-custom-background-80 shadow-custom-shadow-sm"> <div className="fixed right-2 top-16 z-30 flex h-10 w-10 items-center justify-center rounded bg-custom-background-80 shadow-custom-shadow-sm">
<Spinner className="h-5 w-5" /> <Spinner className="h-5 w-5" />
</div> </div>
@ -254,18 +251,18 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
</div> </div>
<KanBanView <KanBanView
issues={issues} issueMap={issueMap}
issueIds={issueIds} issueIds={issueIds}
issuesFilter={issuesFilter}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={renderQuickActions} quickActions={renderQuickActions}
displayProperties={displayProperties}
kanBanToggle={kanbanViewStore?.kanBanToggle} kanBanToggle={kanbanViewStore?.kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickAdd} enableQuickIssueCreate={enableQuickAdd}
showEmptyGroup={userDisplayFilters?.show_empty_groups || true} showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
quickAddCallback={issueStore?.quickAddIssue} quickAddCallback={issues?.quickAddIssue}
viewId={viewId} viewId={viewId}
disableIssueCreation={!enableIssueCreation || !isEditingAllowed} disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}

View File

@ -1,39 +1,57 @@
import { memo } from "react"; import { memo } from "react";
import { Draggable } from "@hello-pangea/dnd"; import { Draggable } from "@hello-pangea/dnd";
import isEqual from "lodash/isEqual";
// components // components
import { KanBanProperties } from "./properties"; import { KanBanProperties } from "./properties";
// ui // ui
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
// types // types
import { IIssueDisplayProperties, IIssue } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
import { IssuesFilter } from "store/issue/base-issue-filter.store";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
interface IssueBlockProps { interface IssueBlockProps {
sub_group_id: string; sub_group_id: string;
columnId: string; columnId: string;
index: number; index: number;
issue: IIssue; issue: IIssue;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
isDragDisabled: boolean; isDragDisabled: boolean;
showEmptyGroup: boolean; showEmptyGroup: boolean;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue) => React.ReactNode; quickActions: (issue: IIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
} }
interface IssueDetailsBlockProps { interface IssueDetailsBlockProps {
issue: IIssue; issue: IIssue;
showEmptyGroup: boolean; showEmptyGroup: boolean;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue) => React.ReactNode; quickActions: (issue: IIssue) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
isReadOnly: boolean; isReadOnly: boolean;
} }
const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => { const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
const { issue, showEmptyGroup, handleIssues, quickActions, displayProperties, isReadOnly } = props; const { issue, showEmptyGroup, handleIssues, quickActions, isReadOnly, issuesFilter } = props;
const router = useRouter(); const router = useRouter();
@ -52,14 +70,17 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
return ( return (
<> <>
{displayProperties && displayProperties?.key && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.key}
>
<div className="relative"> <div className="relative">
<div className="line-clamp-1 text-xs text-custom-text-300"> <div className="line-clamp-1 text-xs text-custom-text-300">
{issue.project_detail.identifier}-{issue.sequence_id} {issue.project_detail.identifier}-{issue.sequence_id}
</div> </div>
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div> <div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
</div> </div>
)} </WithDisplayPropertiesHOC>
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}> <Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
<div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}> <div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}>
{issue.name} {issue.name}
@ -68,8 +89,8 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
<div> <div>
<KanBanProperties <KanBanProperties
issue={issue} issue={issue}
issuesFilter={issuesFilter}
handleIssues={updateIssue} handleIssues={updateIssue}
displayProperties={displayProperties}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
isReadOnly={isReadOnly} isReadOnly={isReadOnly}
/> />
@ -78,15 +99,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
); );
}; };
const validateMemo = (prevProps: IssueDetailsBlockProps, nextProps: IssueDetailsBlockProps) => { const KanbanIssueMemoBlock = memo(KanbanIssueDetailsBlock);
if (prevProps.issue !== nextProps.issue) return false;
if (!isEqual(prevProps.displayProperties, nextProps.displayProperties)) {
return false;
}
return true;
};
const KanbanIssueMemoBlock = memo(KanbanIssueDetailsBlock, validateMemo);
export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => { export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
const { const {
@ -94,11 +107,11 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
columnId, columnId,
index, index,
issue, issue,
issuesFilter,
isDragDisabled, isDragDisabled,
showEmptyGroup, showEmptyGroup,
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties,
canEditProperties, canEditProperties,
} = props; } = props;
@ -128,10 +141,10 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
> >
<KanbanIssueMemoBlock <KanbanIssueMemoBlock
issue={issue} issue={issue}
issuesFilter={issuesFilter}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
isReadOnly={!canEditIssueProperties} isReadOnly={!canEditIssueProperties}
/> />
</div> </div>

View File

@ -1,21 +1,33 @@
import { memo } from "react"; import { memo } from "react";
//types //types
import { IIssueDisplayProperties, IIssue } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { IIssueResponse } from "store_legacy/issues/types";
// components // components
import { KanbanIssueBlock } from "components/issues"; import { KanbanIssueBlock } from "components/issues";
import { IIssueStore } from "store/issue/issue.store";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
interface IssueBlocksListProps { interface IssueBlocksListProps {
sub_group_id: string; sub_group_id: string;
columnId: string; columnId: string;
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: string[]; issueIds: string[];
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
isDragDisabled: boolean; isDragDisabled: boolean;
showEmptyGroup: boolean; showEmptyGroup: boolean;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
canEditProperties: (projectId: string | undefined) => boolean; canEditProperties: (projectId: string | undefined) => boolean;
} }
@ -23,13 +35,13 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
const { const {
sub_group_id, sub_group_id,
columnId, columnId,
issues, issueMap,
issueIds, issueIds,
issuesFilter,
showEmptyGroup, showEmptyGroup,
isDragDisabled, isDragDisabled,
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties,
canEditProperties, canEditProperties,
} = props; } = props;
@ -38,19 +50,19 @@ const KanbanIssueBlocksListMemo: React.FC<IssueBlocksListProps> = (props) => {
{issueIds && issueIds.length > 0 ? ( {issueIds && issueIds.length > 0 ? (
<> <>
{issueIds.map((issueId, index) => { {issueIds.map((issueId, index) => {
if (!issues[issueId]) return null; const issue = issueMap.allIssues[issueId];
const issue = issues[issueId]; if (!issue) return null;
return ( return (
<KanbanIssueBlock <KanbanIssueBlock
key={`kanban-issue-block-${issue.id}`} key={`kanban-issue-block-${issue.id}`}
index={index} index={index}
issue={issue} issue={issue}
issuesFilter={issuesFilter}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
columnId={columnId} columnId={columnId}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}

View File

@ -5,16 +5,31 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
import { KanbanGroup } from "./kanban-group"; import { KanbanGroup } from "./kanban-group";
// types // types
import { IIssueDisplayProperties, IIssue } from "types"; import { IIssue } from "types";
// constants // constants
import { columnTypes, getKanbanColumns, IKanbanColumn } from "./utils"; import { columnTypes, getKanbanColumns, IKanbanColumn } from "./utils";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types"; import { IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
import { IIssueStore } from "store/issue/issue.store";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
import { useLabel, useProject, useProjectState } from "hooks/store";
export interface IGroupByKanBan { export interface IGroupByKanBan {
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: any; issueIds: any;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
sub_group_id: string; sub_group_id: string;
@ -22,7 +37,6 @@ export interface IGroupByKanBan {
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
showEmptyGroup: boolean; showEmptyGroup: boolean;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
enableQuickIssueCreate?: boolean; enableQuickIssueCreate?: boolean;
@ -41,8 +55,9 @@ export interface IGroupByKanBan {
const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => { const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
const { const {
issues, issueMap,
issueIds, issueIds,
issuesFilter,
sub_group_by, sub_group_by,
group_by, group_by,
sub_group_id = "null", sub_group_id = "null",
@ -50,7 +65,6 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
handleIssues, handleIssues,
showEmptyGroup, showEmptyGroup,
quickActions, quickActions,
displayProperties,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
enableQuickIssueCreate, enableQuickIssueCreate,
@ -62,9 +76,12 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
canEditProperties, canEditProperties,
} = props; } = props;
const { project, projectLabel, projectMember, projectState } = useMobxStore(); //const { projectMember } = useMobxStore();
const project = useProject();
const projectLabel = useLabel();
const projectState = useProjectState();
const list = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectMember, projectState); const list = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectState);
if (!list) return null; if (!list) return null;
@ -99,8 +116,9 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
)} )}
<KanbanGroup <KanbanGroup
groupId={_list.id} groupId={_list.id}
issues={issues} issueMap={issueMap}
issueIds={issueIds} issueIds={issueIds}
issuesFilter={issuesFilter}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
@ -108,7 +126,6 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
handleIssues={handleIssues} handleIssues={handleIssues}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
enableQuickIssueCreate={enableQuickIssueCreate} enableQuickIssueCreate={enableQuickIssueCreate}
quickAddCallback={quickAddCallback} quickAddCallback={quickAddCallback}
viewId={viewId} viewId={viewId}
@ -124,14 +141,19 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
}); });
export interface IKanBan { export interface IKanBan {
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues; issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
sub_group_id?: string; sub_group_id?: string;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
showEmptyGroup: boolean; showEmptyGroup: boolean;
@ -151,14 +173,14 @@ export interface IKanBan {
export const KanBan: React.FC<IKanBan> = observer((props) => { export const KanBan: React.FC<IKanBan> = observer((props) => {
const { const {
issues, issueMap,
issueIds, issueIds,
issuesFilter,
sub_group_by, sub_group_by,
group_by, group_by,
sub_group_id = "null", sub_group_id = "null",
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
showEmptyGroup, showEmptyGroup,
@ -176,7 +198,8 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
return ( return (
<div className="relative h-full w-full"> <div className="relative h-full w-full">
<GroupByKanBan <GroupByKanBan
issues={issues} issueMap={issueMap}
issuesFilter={issuesFilter}
issueIds={issueIds} issueIds={issueIds}
group_by={group_by} group_by={group_by}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
@ -185,7 +208,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate} enableQuickIssueCreate={enableQuickIssueCreate}

View File

@ -1,14 +1,28 @@
import { Droppable } from "@hello-pangea/dnd"; import { Droppable } from "@hello-pangea/dnd";
//types //types
import { IIssue, IIssueDisplayProperties, IIssueResponse } from "types"; import { IIssue } from "types";
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
//components //components
import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from "."; import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from ".";
import { IIssueStore } from "store/issue/issue.store";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
interface IKanbanGroup { interface IKanbanGroup {
groupId: string; groupId: string;
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: any; issueIds: any;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
sub_group_id: string; sub_group_id: string;
@ -16,7 +30,6 @@ interface IKanbanGroup {
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
showEmptyGroup: boolean; showEmptyGroup: boolean;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
enableQuickIssueCreate?: boolean; enableQuickIssueCreate?: boolean;
quickAddCallback?: ( quickAddCallback?: (
workspaceSlug: string, workspaceSlug: string,
@ -36,14 +49,14 @@ export const KanbanGroup = (props: IKanbanGroup) => {
sub_group_id, sub_group_id,
group_by, group_by,
sub_group_by, sub_group_by,
issues, issueMap,
issuesFilter,
verticalPosition, verticalPosition,
issueIds, issueIds,
isDragDisabled, isDragDisabled,
showEmptyGroup, showEmptyGroup,
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties,
canEditProperties, canEditProperties,
enableQuickIssueCreate, enableQuickIssueCreate,
disableIssueCreation, disableIssueCreation,
@ -62,17 +75,17 @@ export const KanbanGroup = (props: IKanbanGroup) => {
{...provided.droppableProps} {...provided.droppableProps}
ref={provided.innerRef} ref={provided.innerRef}
> >
{issues && !verticalPosition ? ( {!verticalPosition ? (
<KanbanIssueBlocksList <KanbanIssueBlocksList
sub_group_id={sub_group_id} sub_group_id={sub_group_id}
columnId={groupId} columnId={groupId}
issues={issues} issueMap={issueMap}
issueIds={issueIds?.[groupId] || []} issueIds={issueIds?.[groupId] || []}
issuesFilter={issuesFilter}
isDragDisabled={isDragDisabled} isDragDisabled={isDragDisabled}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
canEditProperties={canEditProperties} canEditProperties={canEditProperties}
/> />
) : null} ) : null}

View File

@ -10,18 +10,31 @@ import { IssuePropertyAssignee } from "../properties/assignee";
import { IssuePropertyEstimates } from "../properties/estimates"; import { IssuePropertyEstimates } from "../properties/estimates";
import { IssuePropertyDate } from "../properties/date"; import { IssuePropertyDate } from "../properties/date";
import { Tooltip } from "@plane/ui"; import { Tooltip } from "@plane/ui";
import { IIssue, IIssueDisplayProperties, IState, TIssuePriorities } from "types"; import { IIssue, IState, TIssuePriorities } from "types";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
import { WithDisplayPropertiesHOC } from "../properties/with-display-properties-HOC";
export interface IKanBanProperties { export interface IKanBanProperties {
issue: IIssue; issue: IIssue;
handleIssues: (issue: IIssue) => void; handleIssues: (issue: IIssue) => void;
displayProperties: IIssueDisplayProperties | null; issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
showEmptyGroup: boolean; showEmptyGroup: boolean;
isReadOnly: boolean; isReadOnly: boolean;
} }
export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => { export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) => {
const { issue, handleIssues, displayProperties, isReadOnly } = props; const { issue, handleIssues, issuesFilter, isReadOnly } = props;
const handleState = (state: IState) => { const handleState = (state: IState) => {
handleIssues({ ...issue, state: state.id }); handleIssues({ ...issue, state: state.id });
@ -55,7 +68,10 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
<div className="flex flex-wrap items-center gap-2 whitespace-nowrap"> <div className="flex flex-wrap items-center gap-2 whitespace-nowrap">
{/* basic properties */} {/* basic properties */}
{/* state */} {/* state */}
{displayProperties && displayProperties?.state && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.state}
>
<IssuePropertyState <IssuePropertyState
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.state || null} value={issue?.state || null}
@ -64,20 +80,27 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
disabled={isReadOnly} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} </WithDisplayPropertiesHOC>
{/* priority */} {/* priority */}
{displayProperties && displayProperties?.priority && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.priority}
>
<IssuePropertyPriority <IssuePropertyPriority
value={issue?.priority || null} value={issue?.priority || null}
onChange={handlePriority} onChange={handlePriority}
disabled={isReadOnly} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} </WithDisplayPropertiesHOC>
{/* label */} {/* label */}
{displayProperties && displayProperties?.labels && (
<WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.labels}
>
<IssuePropertyLabels <IssuePropertyLabels
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.labels || null} value={issue?.labels || null}
@ -86,30 +109,39 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
disabled={isReadOnly} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} </WithDisplayPropertiesHOC>
{/* start date */} {/* start date */}
{displayProperties && displayProperties?.start_date && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.start_date}
>
<IssuePropertyDate <IssuePropertyDate
value={issue?.start_date || null} value={issue?.start_date || null}
onChange={(date: string) => handleStartDate(date)} onChange={(date: string) => handleStartDate(date)}
disabled={isReadOnly} disabled={isReadOnly}
type="start_date" type="start_date"
/> />
)} </WithDisplayPropertiesHOC>
{/* target/due date */} {/* target/due date */}
{displayProperties && displayProperties?.due_date && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.due_date}
>
<IssuePropertyDate <IssuePropertyDate
value={issue?.target_date || null} value={issue?.target_date || null}
onChange={(date: string) => handleTargetDate(date)} onChange={(date: string) => handleTargetDate(date)}
disabled={isReadOnly} disabled={isReadOnly}
type="target_date" type="target_date"
/> />
)} </WithDisplayPropertiesHOC>
{/* assignee */} {/* assignee */}
{displayProperties && displayProperties?.assignee && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.assignee}
>
<IssuePropertyAssignee <IssuePropertyAssignee
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.assignees || null} value={issue?.assignees || null}
@ -119,10 +151,13 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
disabled={isReadOnly} disabled={isReadOnly}
multiple multiple
/> />
)} </WithDisplayPropertiesHOC>
{/* estimates */} {/* estimates */}
{displayProperties && displayProperties?.estimate && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.estimate}
>
<IssuePropertyEstimates <IssuePropertyEstimates
projectId={issue?.project_detail?.id || null} projectId={issue?.project_detail?.id || null}
value={issue?.estimate_point || null} value={issue?.estimate_point || null}
@ -130,38 +165,49 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
disabled={isReadOnly} disabled={isReadOnly}
hideDropdownArrow hideDropdownArrow
/> />
)} </WithDisplayPropertiesHOC>
{/* extra render properties */} {/* extra render properties */}
{/* sub-issues */} {/* sub-issues */}
{displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.sub_issue_count && !!issue?.sub_issues_count}
>
<Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}> <Tooltip tooltipHeading="Sub-issues" tooltipContent={`${issue.sub_issues_count}`}>
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1"> <div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
<Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} /> <Layers className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{issue.sub_issues_count}</div> <div className="text-xs">{issue.sub_issues_count}</div>
</div> </div>
</Tooltip> </Tooltip>
)} </WithDisplayPropertiesHOC>
{/* attachments */} {/* attachments */}
{displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) =>
displayProperties?.attachment_count && !!issue?.attachment_count
}
>
<Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}> <Tooltip tooltipHeading="Attachments" tooltipContent={`${issue.attachment_count}`}>
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1"> <div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
<Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} /> <Paperclip className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{issue.attachment_count}</div> <div className="text-xs">{issue.attachment_count}</div>
</div> </div>
</Tooltip> </Tooltip>
)} </WithDisplayPropertiesHOC>
{/* link */} {/* link */}
{displayProperties && displayProperties?.link && !!issue?.link_count && ( <WithDisplayPropertiesHOC
issuesFilter={issuesFilter}
getShouldRenderProperty={(displayProperties) => displayProperties?.link && !!issue?.link_count}
>
<Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}> <Tooltip tooltipHeading="Links" tooltipContent={`${issue.link_count}`}>
<div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1"> <div className="flex h-5 flex-shrink-0 items-center justify-center gap-2 overflow-hidden rounded border-[0.5px] border-custom-border-300 px-2.5 py-1">
<Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} /> <Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
<div className="text-xs">{issue.link_count}</div> <div className="text-xs">{issue.link_count}</div>
</div> </div>
</Tooltip> </Tooltip>
)} </WithDisplayPropertiesHOC>
</div> </div>
); );
}); });

View File

@ -11,67 +11,45 @@ import { IIssue } from "types";
// constants // constants
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
import { EIssuesStoreType } from "constants/issue";
import { useIssues } from "hooks/store/use-issues";
export interface IKanBanLayout {} export interface IKanBanLayout {}
export const KanBanLayout: React.FC = observer(() => { export const KanBanLayout: React.FC = observer(() => {
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
const { const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
projectIssues: issueStore,
projectIssuesFilter: issuesFilterStore, const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT);
issueKanBanView: issueKanBanViewStore,
kanBanHelpers: kanBanHelperStore,
} = useMobxStore();
const issueActions = useMemo( const issueActions = useMemo(
() => ({ () => ({
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueStore.updateIssue(workspaceSlug, issue.project, issue.id, issue); await issues.updateIssue(workspaceSlug, issue.project, issue.id, issue);
}, },
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug) return; if (!workspaceSlug) return;
await issueStore.removeIssue(workspaceSlug, issue.project, issue.id); await issues.removeIssue(workspaceSlug, issue.project, issue.id);
}, },
}), }),
[issueStore] [issues]
); );
const handleDragDrop = async (
source: any,
destination: any,
subGroupBy: string | null,
groupBy: string | null,
issues: IIssueResponse | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) =>
await kanBanHelperStore.handleDragDrop(
source,
destination,
workspaceSlug,
projectId,
issueStore,
subGroupBy,
groupBy,
issues,
issueWithIds
);
return ( return (
<BaseKanBanRoot <BaseKanBanRoot
issueActions={issueActions} issueActions={issueActions}
issuesFilterStore={issuesFilterStore} issues={issues}
issueStore={issueStore} issuesFilter={issuesFilter}
kanbanViewStore={issueKanBanViewStore} kanbanViewStore={issueKanBanViewStore}
showLoader={true} showLoader={true}
QuickActions={ProjectIssueQuickActions} QuickActions={ProjectIssueQuickActions}
currentStore={EProjectStore.PROJECT} currentStore={EProjectStore.PROJECT}
handleDragDrop={handleDragDrop}
/> />
); );
}); });

View File

@ -1,17 +1,24 @@
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
//mobx
import { useMobxStore } from "lib/mobx/store-provider";
// components // components
import { KanBan } from "./default"; import { KanBan } from "./default";
import { HeaderSubGroupByCard } from "./headers/sub-group-by-card"; import { HeaderSubGroupByCard } from "./headers/sub-group-by-card";
import { HeaderGroupByCard } from "./headers/group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card";
// types // types
import { IIssue, IIssueDisplayProperties } from "types"; import { IIssue } from "types";
import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types"; import { IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types";
// constants // constants
import { EIssueActions } from "../types"; import { EIssueActions } from "../types";
import { EProjectStore } from "store_legacy/command-palette.store"; import { EProjectStore } from "store_legacy/command-palette.store";
import { IKanbanColumn, columnTypes, getKanbanColumns } from "./utils"; import { IKanbanColumn, columnTypes, getKanbanColumns } from "./utils";
import { IIssueStore } from "store/issue/issue.store";
import { useLabel, useProject, useProjectState } from "hooks/store";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
interface ISubGroupSwimlaneHeader { interface ISubGroupSwimlaneHeader {
issueIds: any; issueIds: any;
@ -51,12 +58,17 @@ const SubGroupSwimlaneHeader: React.FC<ISubGroupSwimlaneHeader> = ({
); );
interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: any; issueIds: any;
showEmptyGroup: boolean; showEmptyGroup: boolean;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
isDragStarted?: boolean; isDragStarted?: boolean;
@ -75,14 +87,14 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader {
} }
const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => { const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
const { const {
issues, issueMap,
issueIds, issueIds,
sub_group_by, sub_group_by,
group_by, group_by,
list, list,
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties, issuesFilter,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
showEmptyGroup, showEmptyGroup,
@ -124,14 +136,14 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
{!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && ( {!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
<div className="relative"> <div className="relative">
<KanBan <KanBan
issues={issues} issueMap={issueMap}
issueIds={issueIds?.[_list.id]} issueIds={issueIds?.[_list.id]}
issuesFilter={issuesFilter}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
group_by={group_by} group_by={group_by}
sub_group_id={_list.id} sub_group_id={_list.id}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}
@ -150,13 +162,18 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = observer((props) => {
}); });
export interface IKanBanSwimLanes { export interface IKanBanSwimLanes {
issues: IIssueResponse; issueMap: IIssueStore;
issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues; issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
sub_group_by: string | null; sub_group_by: string | null;
group_by: string | null; group_by: string | null;
handleIssues: (issue: IIssue, action: EIssueActions) => void; handleIssues: (issue: IIssue, action: EIssueActions) => void;
quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode;
displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any; kanBanToggle: any;
handleKanBanToggle: any; handleKanBanToggle: any;
showEmptyGroup: boolean; showEmptyGroup: boolean;
@ -177,13 +194,13 @@ export interface IKanBanSwimLanes {
export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => { export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
const { const {
issues, issueMap,
issueIds, issueIds,
issuesFilter,
sub_group_by, sub_group_by,
group_by, group_by,
handleIssues, handleIssues,
quickActions, quickActions,
displayProperties,
kanBanToggle, kanBanToggle,
handleKanBanToggle, handleKanBanToggle,
showEmptyGroup, showEmptyGroup,
@ -196,16 +213,12 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
viewId, viewId,
} = props; } = props;
const { project, projectLabel, projectMember, projectState } = useMobxStore(); const project = useProject();
const projectLabel = useLabel();
const projectState = useProjectState();
const groupByList = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectMember, projectState); const groupByList = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectState);
const subGroupByList = getKanbanColumns( const subGroupByList = getKanbanColumns(sub_group_by as columnTypes, project, projectLabel, projectState);
sub_group_by as columnTypes,
project,
projectLabel,
projectMember,
projectState
);
if (!groupByList || !subGroupByList) return null; if (!groupByList || !subGroupByList) return null;
@ -224,14 +237,14 @@ export const KanBanSwimLanes: React.FC<IKanBanSwimLanes> = observer((props) => {
{sub_group_by && ( {sub_group_by && (
<SubGroupSwimlane <SubGroupSwimlane
issues={issues} issueMap={issueMap}
list={subGroupByList} list={subGroupByList}
issueIds={issueIds} issueIds={issueIds}
issuesFilter={issuesFilter}
group_by={group_by} group_by={group_by}
sub_group_by={sub_group_by} sub_group_by={sub_group_by}
handleIssues={handleIssues} handleIssues={handleIssues}
quickActions={quickActions} quickActions={quickActions}
displayProperties={displayProperties}
kanBanToggle={kanBanToggle} kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle} handleKanBanToggle={handleKanBanToggle}
showEmptyGroup={showEmptyGroup} showEmptyGroup={showEmptyGroup}

View File

@ -1,9 +1,14 @@
import { DraggableLocation } from "@hello-pangea/dnd";
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui"; import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue"; import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
import { renderEmoji } from "helpers/emoji.helper"; import { renderEmoji } from "helpers/emoji.helper";
import { ReactElement } from "react"; import { ReactElement } from "react";
import { IProjectLabelStore, IProjectMemberStore, IProjectStateStore, IProjectStore } from "store_legacy/project"; import { IIssueStore } from "store/issue/issue.store";
import { IIssue } from "types"; import { IProjectIssues } from "store/issue/project";
import { ILabelRootStore } from "store/label";
import { IProjectStore } from "store/project/project.store";
import { IStateStore } from "store/state.store";
import { IGroupedIssues, IIssue, ISubGroupedIssues, TUnGroupedIssues } from "types";
export type columnTypes = export type columnTypes =
| "project" | "project"
@ -24,9 +29,9 @@ export interface IKanbanColumn {
export const getKanbanColumns = ( export const getKanbanColumns = (
groupBy: columnTypes | null, groupBy: columnTypes | null,
project: IProjectStore, project: IProjectStore,
projectLabel: IProjectLabelStore, projectLabel: ILabelRootStore,
projectMember: IProjectMemberStore, projectState: IStateStore
projectState: IProjectStateStore //projectMember?: IProjectMemberStore,
): IKanbanColumn[] | undefined => { ): IKanbanColumn[] | undefined => {
switch (groupBy) { switch (groupBy) {
case "project": case "project":
@ -39,27 +44,32 @@ export const getKanbanColumns = (
return getPriorityColumns(); return getPriorityColumns();
case "labels": case "labels":
return getLabelsColumns(projectLabel); return getLabelsColumns(projectLabel);
case "assignees": // case "assignees":
return getAssigneeColumns(projectMember); // return getAssigneeColumns(projectMember);
case "created_by": // case "created_by":
return getCreatedByColumns(projectMember); // return getCreatedByColumns(projectMember);
} }
}; };
const getProjectColumns = (project: IProjectStore): IKanbanColumn[] | undefined => { const getProjectColumns = (project: IProjectStore): IKanbanColumn[] | undefined => {
const { workspaceProjects: projects } = project; const { workspaceProjects: projectIds, projectMap } = project;
if (!projects) return; if (!projectIds) return;
return projects.map((project) => ({ return projectIds.map((projectId) => {
id: project.id, const project = projectMap[projectId];
name: project.name,
Icon: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>, if (project)
payload: { project: project.id }, return {
})); id: project.id,
name: project.name,
Icon: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>,
payload: { project: project.id },
};
});
}; };
const getStateColumns = (projectState: IProjectStateStore): IKanbanColumn[] | undefined => { const getStateColumns = (projectState: IStateStore): IKanbanColumn[] | undefined => {
const { projectStates } = projectState; const { projectStates } = projectState;
if (!projectStates) return; if (!projectStates) return;
@ -101,8 +111,10 @@ const getPriorityColumns = () => {
})); }));
}; };
const getLabelsColumns = (projectLabel: IProjectLabelStore) => { const getLabelsColumns = (projectLabel: ILabelRootStore) => {
const { projectLabels } = projectLabel; const {
project: { projectLabels },
} = projectLabel;
if (!projectLabels) return; if (!projectLabels) return;
@ -118,32 +130,185 @@ const getLabelsColumns = (projectLabel: IProjectLabelStore) => {
})); }));
}; };
const getAssigneeColumns = (projectMember: IProjectMemberStore) => { // const getAssigneeColumns = (projectMember: IProjectMemberStore) => {
const { projectMembers: users } = projectMember; // const { projectMembers: users } = projectMember;
if (!users) return; // if (!users) return;
return users.map((user) => { // return users.map((user) => {
const member = user.member; // const member = user.member;
return { // return {
id: member?.id, // id: member?.id,
name: member?.display_name || "", // name: member?.display_name || "",
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />, // Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
payload: { assignees: [member?.id] }, // payload: { assignees: [member?.id] },
}; // };
}); // });
// };
// const getCreatedByColumns = (projectMember: IProjectMemberStore) => {
// const { projectMembers: users } = projectMember;
// if (!users) return;
// return users.map((user) => {
// const member = user.member;
// return {
// id: member?.id,
// name: member?.display_name || "",
// Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
// payload: { created_by: member?.id },
// };
// });
// };
export const handleDragDrop = async (
source: DraggableLocation | null,
destination: DraggableLocation | null,
workspaceSlug: string,
projectId: string, // projectId for all views or user id in profile issues
store: IProjectIssues,
subGroupBy: string | null,
groupBy: string | null,
issueMap: IIssueStore | undefined,
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
viewId: string | null = null // it can be moduleId, cycleId
) => {
if (!issueMap || !issueWithIds || !source || !destination) return;
let updateIssue: any = {};
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
if (!sourceColumnId || !destinationColumnId) return;
const sourceGroupByColumnId = sourceColumnId[0] || null;
const destinationGroupByColumnId = destinationColumnId[0] || null;
const sourceSubGroupByColumnId = sourceColumnId[1] || null;
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
if (
!workspaceSlug ||
!projectId ||
!groupBy ||
!sourceGroupByColumnId ||
!destinationGroupByColumnId ||
!sourceSubGroupByColumnId ||
!sourceGroupByColumnId
)
return;
if (destinationGroupByColumnId === "issue-trash-box") {
const sourceIssues: string[] = subGroupBy
? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
: (issueWithIds as IGroupedIssues)[sourceGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1);
if (removed) {
if (viewId) return await store?.removeIssue(workspaceSlug, projectId, removed); //, viewId);
else return await store?.removeIssue(workspaceSlug, projectId, removed);
}
} else {
const sourceIssues = subGroupBy
? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
: (issueWithIds as IGroupedIssues)[sourceGroupByColumnId];
const destinationIssues = subGroupBy
? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][destinationGroupByColumnId]
: (issueWithIds as IGroupedIssues)[destinationGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1);
const removedIssueDetail = issueMap.allIssues[removed];
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
updateIssue = {
id: removedIssueDetail?.id,
};
// for both horizontal and vertical dnd
updateIssue = {
...updateIssue,
...handleSortOrder(destinationIssues, destination.index, issueMap),
};
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
if (sourceGroupByColumnId != destinationGroupByColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
}
} else {
if (subGroupBy === "state")
updateIssue = {
...updateIssue,
state: destinationSubGroupByColumnId,
priority: destinationGroupByColumnId,
};
if (subGroupBy === "priority")
updateIssue = {
...updateIssue,
state: destinationGroupByColumnId,
priority: destinationSubGroupByColumnId,
};
}
} else {
updateIssue = {
id: removedIssueDetail?.id,
};
// for both horizontal and vertical dnd
updateIssue = {
...updateIssue,
...handleSortOrder(destinationIssues, destination.index, issueMap),
};
// for horizontal dnd
if (sourceColumnId != destinationColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: destinationGroupByColumnId };
}
}
if (updateIssue && updateIssue?.id) {
if (viewId) return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue); //, viewId);
else return await store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
}
}
}; };
const getCreatedByColumns = (projectMember: IProjectMemberStore) => { const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueStore) => {
const { projectMembers: users } = projectMember; const sortOrderDefaultValue = 65535;
if (!users) return; let currentIssueState = {};
return users.map((user) => { if (destinationIssues && destinationIssues.length > 0) {
const member = user.member; if (destinationIndex === 0) {
return { const destinationIssueId = destinationIssues[destinationIndex];
id: member?.id, currentIssueState = {
name: member?.display_name || "", ...currentIssueState,
Icon: <Avatar name={member?.display_name} src={member?.avatar} size="md" />, sort_order: issueMap.allIssues[destinationIssueId].sort_order - sortOrderDefaultValue,
payload: { created_by: member?.id }, };
} else if (destinationIndex === destinationIssues.length) {
const destinationIssueId = destinationIssues[destinationIndex - 1];
currentIssueState = {
...currentIssueState,
sort_order: issueMap.allIssues[destinationIssueId].sort_order + sortOrderDefaultValue,
};
} else {
const destinationTopIssueId = destinationIssues[destinationIndex - 1];
const destinationBottomIssueId = destinationIssues[destinationIndex];
currentIssueState = {
...currentIssueState,
sort_order:
(issueMap.allIssues[destinationTopIssueId].sort_order +
issueMap.allIssues[destinationBottomIssueId].sort_order) /
2,
};
}
} else {
currentIssueState = {
...currentIssueState,
sort_order: sortOrderDefaultValue,
}; };
}); }
return currentIssueState;
}; };

View File

@ -0,0 +1,32 @@
import { observer } from "mobx-react-lite";
import { ReactNode } from "react";
import {
ICycleIssuesFilterStore,
IModuleIssuesFilterStore,
IProfileIssuesFilterStore,
IProjectIssuesFilterStore,
IViewIssuesFilterStore,
} from "store_legacy/issues";
import { IIssueDisplayProperties } from "types";
interface IWithDisplayPropertiesHOC {
issuesFilter:
| IProjectIssuesFilterStore
| IModuleIssuesFilterStore
| ICycleIssuesFilterStore
| IViewIssuesFilterStore
| IProfileIssuesFilterStore;
getShouldRenderProperty: (displayProperties: IIssueDisplayProperties) => boolean;
children: ReactNode;
}
export const WithDisplayPropertiesHOC = observer(
({ issuesFilter, getShouldRenderProperty, children }: IWithDisplayPropertiesHOC) => {
const displayProperties = issuesFilter.issueFilters.displayProperties;
const shouldRenderProperty = getShouldRenderProperty(displayProperties);
if (!shouldRenderProperty) return null;
return <>{children}</>;
}
);

View File

@ -1,11 +1,8 @@
import React from "react";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
import useSWR from "swr"; import useSWR from "swr";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
// TODO: update this
import useStoreIssues from "hooks/use-store-issues";
// components // components
import { import {
ListLayout, ListLayout,
@ -17,6 +14,8 @@ import {
ProjectEmptyState, ProjectEmptyState,
} from "components/issues"; } from "components/issues";
import { Spinner } from "@plane/ui"; import { Spinner } from "@plane/ui";
import { useIssues } from "hooks/store/use-issues";
import { EIssuesStoreType } from "constants/issue";
// hooks // hooks
export const ProjectLayoutRoot: React.FC = observer(() => { export const ProjectLayoutRoot: React.FC = observer(() => {
@ -25,16 +24,20 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
const { const {
projectIssues: { loader, getIssues, fetchIssues }, issues: { loader, getIssues, fetchIssues },
projectIssuesFilter: { issueFilters, fetchFilters }, issuesFilter: { issueFilters, fetchFilters },
} = useMobxStore(); } = useIssues(EIssuesStoreType.PROJECT);
useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { useSWR(
if (workspaceSlug && projectId) { workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null,
await fetchFilters(workspaceSlug, projectId); async () => {
await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); if (workspaceSlug && projectId) {
} await fetchFilters(workspaceSlug, projectId);
}); await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader");
}
},
{ revalidateOnFocus: false, refreshInterval: 600000, revalidateOnMount: true }
);
// TODO: update this // TODO: update this
// const { // const {

View File

@ -408,3 +408,13 @@ export const groupReactionEmojis = (reactions: any) => {
return _groupedEmojis; return _groupedEmojis;
}; };
export enum EIssuesStoreType {
MODULE,
CYClE,
PROJECT,
VIEW,
DRAFT,
ARCHIVED,
GLOBAL,
}

View File

@ -0,0 +1,20 @@
import { useContext } from "react";
// mobx store
import { MobxStoreContext } from "lib/mobx/store-provider";
// types
import { EIssuesStoreType } from "constants/issue";
export const useIssues = (storeType?: EIssuesStoreType) => {
const context = useContext(MobxStoreContext);
if (context === undefined) throw new Error("useMobxStore must be used within MobxStoreProvider");
switch (storeType) {
case EIssuesStoreType.PROJECT:
return {
issues: context.issue.projectIssues,
issuesFilter: context.issue.projectIssuesFilter,
issueMap: context.issue.issues,
};
default:
return { issueMap: context.issue.issues };
}
};

View File

@ -1,6 +1,6 @@
import { createContext, useContext } from "react"; import { createContext, useContext } from "react";
// mobx store
import { RootStore } from "store/root.store"; import { RootStore } from "store/root.store";
// mobx store
let rootStore: RootStore = new RootStore(); let rootStore: RootStore = new RootStore();

View File

@ -26,7 +26,7 @@ export class IssueStore implements IIssueStore {
makeObservable(this, { makeObservable(this, {
// observable // observable
allIssues: observable.ref, allIssues: observable,
// actions // actions
addIssue: action, addIssue: action,
updateIssue: action, updateIssue: action,

View File

@ -0,0 +1,76 @@
import { action, computed, makeObservable, observable, runInAction } from "mobx";
import { IssueRootStore } from "./root.store";
// types
export interface IIssueKanBanViewStore {
kanBanToggle: {
groupByHeaderMinMax: string[];
subgroupByIssuesVisibility: string[];
};
// computed
canUserDragDrop: boolean;
canUserDragDropVertically: boolean;
canUserDragDropHorizontally: boolean;
// actions
handleKanBanToggle: (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => void;
}
export class IssueKanBanViewStore implements IIssueKanBanViewStore {
kanBanToggle: {
groupByHeaderMinMax: string[];
subgroupByIssuesVisibility: string[];
} = { groupByHeaderMinMax: [], subgroupByIssuesVisibility: [] };
// root store
rootStore;
constructor(_rootStore: IssueRootStore) {
makeObservable(this, {
kanBanToggle: observable,
// computed
canUserDragDrop: computed,
canUserDragDropVertically: computed,
canUserDragDropHorizontally: computed,
// actions
handleKanBanToggle: action,
});
this.rootStore = _rootStore;
}
get canUserDragDrop() {
return true;
if (this.rootStore.issueDetail.peekId) return false;
if (
this.rootStore?.issueFilter?.userDisplayFilters?.order_by &&
this.rootStore?.issueFilter?.userDisplayFilters?.order_by === "sort_order" &&
this.rootStore?.issueFilter?.userDisplayFilters?.group_by &&
["state", "priority"].includes(this.rootStore?.issueFilter?.userDisplayFilters?.group_by)
) {
if (!this.rootStore?.issueFilter?.userDisplayFilters?.sub_group_by) 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 false;
}
get canUserDragDropHorizontally() {
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],
};
};
}

View File

@ -14,6 +14,7 @@ import {
TIssueGroupByOptions, TIssueGroupByOptions,
TLoader, TLoader,
TUnGroupedIssues, TUnGroupedIssues,
ViewFlags,
} from "types"; } from "types";
export interface IProjectIssues { export interface IProjectIssues {
@ -28,6 +29,7 @@ export interface IProjectIssues {
updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>; updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial<IIssue>) => Promise<IIssue>;
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>; quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>;
viewFlags: ViewFlags;
} }
export class ProjectIssues extends IssueHelperStore implements IProjectIssues { export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
@ -38,6 +40,12 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues {
issueService; issueService;
// root store // root store
rootIssueStore: IIssueRootStore; rootIssueStore: IIssueRootStore;
//viewData
viewFlags = {
enableQuickAdd: true,
enableIssueCreation: true,
enableInlineEditing: true,
};
constructor(_rootStore: IIssueRootStore) { constructor(_rootStore: IIssueRootStore) {
super(_rootStore); super(_rootStore);

View File

@ -18,6 +18,7 @@ import {
} from "./project-views"; } from "./project-views";
import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived"; import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived";
import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft"; import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft";
import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store";
export interface IIssueRootStore { export interface IIssueRootStore {
currentUserId: string | undefined; currentUserId: string | undefined;
@ -59,6 +60,8 @@ export interface IIssueRootStore {
draftIssuesFilter: IDraftIssuesFilter; draftIssuesFilter: IDraftIssuesFilter;
draftIssues: IDraftIssues; draftIssues: IDraftIssues;
issueKanBanView: IIssueKanBanViewStore;
} }
export class IssueRootStore { export class IssueRootStore {
@ -102,6 +105,8 @@ export class IssueRootStore {
draftIssuesFilter: IDraftIssuesFilter; draftIssuesFilter: IDraftIssuesFilter;
draftIssues: IDraftIssues; draftIssues: IDraftIssues;
issueKanBanView: IIssueKanBanViewStore;
constructor(rootStore: RootStore) { constructor(rootStore: RootStore) {
makeObservable(this, { makeObservable(this, {
currentUserId: observable.ref, currentUserId: observable.ref,
@ -160,5 +165,7 @@ export class IssueRootStore {
this.draftIssuesFilter = new DraftIssuesFilter(this); this.draftIssuesFilter = new DraftIssuesFilter(this);
this.draftIssues = new DraftIssues(); this.draftIssues = new DraftIssues();
this.issueKanBanView = new IssueKanBanViewStore(this);
} }
} }

View File

@ -11,7 +11,6 @@ import { IssueRootStore, IIssueRootStore } from "./issue/root.store";
import { IStateStore, StateStore } from "./state.store"; import { IStateStore, StateStore } from "./state.store";
import { IPageStore, PageStore } from "./page.store"; import { IPageStore, PageStore } from "./page.store";
import { ILabelRootStore, LabelRootStore } from "./label"; import { ILabelRootStore, LabelRootStore } from "./label";
enableStaticRendering(typeof window === "undefined"); enableStaticRendering(typeof window === "undefined");
export class RootStore { export class RootStore {

View File

@ -272,3 +272,9 @@ export type TUnGroupedIssues = string[];
export interface IIssueResponse { export interface IIssueResponse {
[issue_id: string]: IIssue; [issue_id: string]: IIssue;
} }
export interface ViewFlags {
enableQuickAdd: boolean;
enableIssueCreation: boolean;
enableInlineEditing: boolean;
}