From fd1f262bf7a40b853612cc906fa677d2180aff59 Mon Sep 17 00:00:00 2001 From: rahulramesha Date: Fri, 15 Dec 2023 17:57:43 +0530 Subject: [PATCH] Kanban new store implementation for project issues --- web/components/issues/delete-issue-modal.tsx | 12 +- .../issue-layouts/kanban/base-kanban-root.tsx | 101 ++++--- .../issues/issue-layouts/kanban/block.tsx | 51 ++-- .../issue-layouts/kanban/blocks-list.tsx | 30 ++- .../issues/issue-layouts/kanban/default.tsx | 54 ++-- .../issue-layouts/kanban/kanban-group.tsx | 29 +- .../issue-layouts/kanban/properties.tsx | 92 +++++-- .../kanban/roots/project-root.tsx | 46 +--- .../issues/issue-layouts/kanban/swimlanes.tsx | 63 +++-- .../issues/issue-layouts/kanban/utils.tsx | 251 +++++++++++++++--- .../with-display-properties-HOC.tsx | 32 +++ .../roots/project-layout-root.tsx | 27 +- web/constants/issue.ts | 10 + web/hooks/store/use-issues.ts | 20 ++ web/lib/mobx/store-provider.tsx | 2 +- web/store/issue/issue.store.ts | 2 +- web/store/issue/issue_kanban_view.store.ts | 76 ++++++ web/store/issue/project/issue.store.ts | 8 + web/store/issue/root.store.ts | 7 + web/store/root.store.ts | 1 - web/types/issues.d.ts | 6 + 21 files changed, 672 insertions(+), 248 deletions(-) create mode 100644 web/components/issues/issue-layouts/properties/with-display-properties-HOC.tsx create mode 100644 web/hooks/store/use-issues.ts create mode 100644 web/store/issue/issue_kanban_view.store.ts diff --git a/web/components/issues/delete-issue-modal.tsx b/web/components/issues/delete-issue-modal.tsx index 2f53a825f..10eeb5166 100644 --- a/web/components/issues/delete-issue-modal.tsx +++ b/web/components/issues/delete-issue-modal.tsx @@ -6,17 +6,21 @@ import { Button } from "@plane/ui"; // hooks import useToast from "hooks/use-toast"; // types -import type { IIssue } from "types"; +import { useIssues } from "hooks/store/use-issues"; type Props = { isOpen: boolean; handleClose: () => void; - data: IIssue; + dataId: string; onSubmit?: () => Promise; }; export const DeleteIssueModal: React.FC = (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); @@ -93,7 +97,7 @@ export const DeleteIssueModal: React.FC = (props) => {

Are you sure you want to delete issue{" "} - {data?.project_detail?.identifier}-{data?.sequence_id} + {issue?.project_detail?.identifier}-{issue?.sequence_id} {""}? All of the data related to the issue will be permanently removed. This action cannot be undone. diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index cd89b64be..3fab2dc0c 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -12,35 +12,26 @@ import { IIssue } from "types"; import { EIssueActions } from "../types"; import { ICycleIssuesFilterStore, - ICycleIssuesStore, IModuleIssuesFilterStore, - IModuleIssuesStore, IProfileIssuesFilterStore, - IProfileIssuesStore, - IProjectDraftIssuesStore, IProjectIssuesFilterStore, - IProjectIssuesStore, IViewIssuesFilterStore, - IViewIssuesStore, } from "store_legacy/issues"; import { IQuickActionProps } from "../list/list-view-types"; -import { IIssueKanBanViewStore } from "store_legacy/issue"; +import { IProjectIssues } from "store/issue/project"; //components import { KanBan } from "./default"; import { KanBanSwimLanes } from "./swimlanes"; import { EProjectStore } from "store_legacy/command-palette.store"; import { DeleteIssueModal, IssuePeekOverview } from "components/issues"; 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 { - issueStore: - | IProjectIssuesStore - | IModuleIssuesStore - | ICycleIssuesStore - | IViewIssuesStore - | IProjectDraftIssuesStore - | IProfileIssuesStore; - issuesFilterStore: + issues: IProjectIssues; + issuesFilter: | IProjectIssuesFilterStore | IModuleIssuesFilterStore | ICycleIssuesFilterStore @@ -56,14 +47,6 @@ export interface IBaseKanBanLayout { showLoader?: boolean; viewId?: string; currentStore?: EProjectStore; - handleDragDrop?: ( - source: any, - destination: any, - subGroupBy: string | null, - groupBy: string | null, - issues: any, - issueWithIds: any - ) => Promise; addIssuesToView?: (issueIds: string[]) => Promise; canEditPropertiesBasedOnProject?: (projectId: string) => boolean; } @@ -76,33 +59,31 @@ type KanbanDragState = { export const BaseKanBanRoot: React.FC = observer((props: IBaseKanBanLayout) => { const { - issueStore, - issuesFilterStore, + issues, + issuesFilter, kanbanViewStore, QuickActions, issueActions, showLoader, viewId, currentStore, - handleDragDrop, addIssuesToView, canEditPropertiesBasedOnProject, } = props; // router const router = useRouter(); - const { workspaceSlug, peekIssueId, peekProjectId } = router.query; + const { workspaceSlug, projectId, peekIssueId, peekProjectId } = router.query; // store hooks const { membership: { currentProjectRole }, } = useUser(); + const { issueMap } = useIssues(); // toast alert const { setToastAlert } = useToast(); - const issues = issueStore?.getIssues || {}; - const issueIds = issueStore?.getIssuesIds || []; + const issueIds = issues?.getIssuesIds || []; - const displayFilters = issuesFilterStore?.issueFilters?.displayFilters; - const displayProperties = issuesFilterStore?.issueFilters?.displayProperties || null; + const displayFilters = issuesFilter?.issueFilters?.displayFilters; const sub_group_by: string | null = displayFilters?.sub_group_by || null; const group_by: string | null = displayFilters?.group_by || null; @@ -111,7 +92,7 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas const KanBanView = sub_group_by ? KanBanSwimLanes : KanBan; - const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issueStore?.viewFlags || {}; + const { enableInlineEditing, enableQuickAdd, enableIssueCreation } = issues?.viewFlags || {}; // states const [isDragStarted, setIsDragStarted] = useState(false); @@ -161,15 +142,23 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas }); setDeleteIssueModal(true); } else { - await handleDragDrop(result.source, result.destination, sub_group_by, group_by, issues, issueIds).catch( - (err) => { - setToastAlert({ - title: "Error", - type: "error", - message: err.detail ?? "Failed to perform this action", - }); - } - ); + await handleDragDrop( + result.source, + result.destination, + workspaceSlug.toString(), + projectId.toString(), + 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 = observer((props: IBas const handleDeleteIssue = async () => { if (!handleDragDrop) return; - await handleDragDrop(dragState.source, dragState.destination, sub_group_by, group_by, issues, issueIds).finally( - () => { - setDeleteIssueModal(false); - setDragState({}); - } - ); + await handleDragDrop( + dragState.source, + dragState.destination, + workspaceSlug.toString(), + projectId.toString(), + issues, + sub_group_by, + group_by, + issueMap, + issueIds + ).finally(() => { + setDeleteIssueModal(false); + setDragState({}); + }); }; const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => { @@ -217,13 +214,13 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas return ( <> setDeleteIssueModal(false)} onSubmit={handleDeleteIssue} /> - {showLoader && issueStore?.loader === "init-loader" && ( + {showLoader && issues?.loader === "init-loader" && (

@@ -254,18 +251,18 @@ export const BaseKanBanRoot: React.FC = observer((props: IBas void; quickActions: (issue: IIssue) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; canEditProperties: (projectId: string | undefined) => boolean; } interface IssueDetailsBlockProps { issue: IIssue; showEmptyGroup: boolean; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (issue: IIssue) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; isReadOnly: boolean; } const KanbanIssueDetailsBlock: React.FC = (props) => { - const { issue, showEmptyGroup, handleIssues, quickActions, displayProperties, isReadOnly } = props; + const { issue, showEmptyGroup, handleIssues, quickActions, isReadOnly, issuesFilter } = props; const router = useRouter(); @@ -52,14 +70,17 @@ const KanbanIssueDetailsBlock: React.FC = (props) => { return ( <> - {displayProperties && displayProperties?.key && ( + displayProperties?.key} + >
{issue.project_detail.identifier}-{issue.sequence_id}
{quickActions(issue)}
- )} +
{issue.name} @@ -68,8 +89,8 @@ const KanbanIssueDetailsBlock: React.FC = (props) => {
@@ -78,15 +99,7 @@ const KanbanIssueDetailsBlock: React.FC = (props) => { ); }; -const validateMemo = (prevProps: IssueDetailsBlockProps, nextProps: IssueDetailsBlockProps) => { - if (prevProps.issue !== nextProps.issue) return false; - if (!isEqual(prevProps.displayProperties, nextProps.displayProperties)) { - return false; - } - return true; -}; - -const KanbanIssueMemoBlock = memo(KanbanIssueDetailsBlock, validateMemo); +const KanbanIssueMemoBlock = memo(KanbanIssueDetailsBlock); export const KanbanIssueBlock: React.FC = (props) => { const { @@ -94,11 +107,11 @@ export const KanbanIssueBlock: React.FC = (props) => { columnId, index, issue, + issuesFilter, isDragDisabled, showEmptyGroup, handleIssues, quickActions, - displayProperties, canEditProperties, } = props; @@ -128,10 +141,10 @@ export const KanbanIssueBlock: React.FC = (props) => { >
diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx index 16ae94f9d..ca94faf12 100644 --- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx +++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx @@ -1,21 +1,33 @@ import { memo } from "react"; //types -import { IIssueDisplayProperties, IIssue } from "types"; +import { IIssue } from "types"; import { EIssueActions } from "../types"; -import { IIssueResponse } from "store_legacy/issues/types"; // components import { KanbanIssueBlock } from "components/issues"; +import { IIssueStore } from "store/issue/issue.store"; +import { + ICycleIssuesFilterStore, + IModuleIssuesFilterStore, + IProfileIssuesFilterStore, + IProjectIssuesFilterStore, + IViewIssuesFilterStore, +} from "store_legacy/issues"; interface IssueBlocksListProps { sub_group_id: string; columnId: string; - issues: IIssueResponse; + issueMap: IIssueStore; issueIds: string[]; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; isDragDisabled: boolean; showEmptyGroup: boolean; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; canEditProperties: (projectId: string | undefined) => boolean; } @@ -23,13 +35,13 @@ const KanbanIssueBlocksListMemo: React.FC = (props) => { const { sub_group_id, columnId, - issues, + issueMap, issueIds, + issuesFilter, showEmptyGroup, isDragDisabled, handleIssues, quickActions, - displayProperties, canEditProperties, } = props; @@ -38,19 +50,19 @@ const KanbanIssueBlocksListMemo: React.FC = (props) => { {issueIds && issueIds.length > 0 ? ( <> {issueIds.map((issueId, index) => { - if (!issues[issueId]) return null; + const issue = issueMap.allIssues[issueId]; - const issue = issues[issueId]; + if (!issue) return null; return ( void; showEmptyGroup: boolean; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; kanBanToggle: any; handleKanBanToggle: any; enableQuickIssueCreate?: boolean; @@ -41,8 +55,9 @@ export interface IGroupByKanBan { const GroupByKanBan: React.FC = observer((props) => { const { - issues, + issueMap, issueIds, + issuesFilter, sub_group_by, group_by, sub_group_id = "null", @@ -50,7 +65,6 @@ const GroupByKanBan: React.FC = observer((props) => { handleIssues, showEmptyGroup, quickActions, - displayProperties, kanBanToggle, handleKanBanToggle, enableQuickIssueCreate, @@ -62,9 +76,12 @@ const GroupByKanBan: React.FC = observer((props) => { canEditProperties, } = 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; @@ -99,8 +116,9 @@ const GroupByKanBan: React.FC = observer((props) => { )} = observer((props) => { handleIssues={handleIssues} showEmptyGroup={showEmptyGroup} quickActions={quickActions} - displayProperties={displayProperties} enableQuickIssueCreate={enableQuickIssueCreate} quickAddCallback={quickAddCallback} viewId={viewId} @@ -124,14 +141,19 @@ const GroupByKanBan: React.FC = observer((props) => { }); export interface IKanBan { - issues: IIssueResponse; + issueMap: IIssueStore; issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; sub_group_by: string | null; group_by: string | null; sub_group_id?: string; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; kanBanToggle: any; handleKanBanToggle: any; showEmptyGroup: boolean; @@ -151,14 +173,14 @@ export interface IKanBan { export const KanBan: React.FC = observer((props) => { const { - issues, + issueMap, issueIds, + issuesFilter, sub_group_by, group_by, sub_group_id = "null", handleIssues, quickActions, - displayProperties, kanBanToggle, handleKanBanToggle, showEmptyGroup, @@ -176,7 +198,8 @@ export const KanBan: React.FC = observer((props) => { return (
= observer((props) => { showEmptyGroup={showEmptyGroup} handleIssues={handleIssues} quickActions={quickActions} - displayProperties={displayProperties} kanBanToggle={kanBanToggle} handleKanBanToggle={handleKanBanToggle} enableQuickIssueCreate={enableQuickIssueCreate} diff --git a/web/components/issues/issue-layouts/kanban/kanban-group.tsx b/web/components/issues/issue-layouts/kanban/kanban-group.tsx index 828ec9a75..293194aa7 100644 --- a/web/components/issues/issue-layouts/kanban/kanban-group.tsx +++ b/web/components/issues/issue-layouts/kanban/kanban-group.tsx @@ -1,14 +1,28 @@ import { Droppable } from "@hello-pangea/dnd"; //types -import { IIssue, IIssueDisplayProperties, IIssueResponse } from "types"; +import { IIssue } from "types"; import { EIssueActions } from "../types"; //components import { KanBanQuickAddIssueForm, KanbanIssueBlocksList } from "."; +import { IIssueStore } from "store/issue/issue.store"; +import { + ICycleIssuesFilterStore, + IModuleIssuesFilterStore, + IProfileIssuesFilterStore, + IProjectIssuesFilterStore, + IViewIssuesFilterStore, +} from "store_legacy/issues"; interface IKanbanGroup { groupId: string; - issues: IIssueResponse; + issueMap: IIssueStore; issueIds: any; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; sub_group_by: string | null; group_by: string | null; sub_group_id: string; @@ -16,7 +30,6 @@ interface IKanbanGroup { handleIssues: (issue: IIssue, action: EIssueActions) => void; showEmptyGroup: boolean; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; enableQuickIssueCreate?: boolean; quickAddCallback?: ( workspaceSlug: string, @@ -36,14 +49,14 @@ export const KanbanGroup = (props: IKanbanGroup) => { sub_group_id, group_by, sub_group_by, - issues, + issueMap, + issuesFilter, verticalPosition, issueIds, isDragDisabled, showEmptyGroup, handleIssues, quickActions, - displayProperties, canEditProperties, enableQuickIssueCreate, disableIssueCreation, @@ -62,17 +75,17 @@ export const KanbanGroup = (props: IKanbanGroup) => { {...provided.droppableProps} ref={provided.innerRef} > - {issues && !verticalPosition ? ( + {!verticalPosition ? ( ) : null} diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx index e5f4f863a..0463269ff 100644 --- a/web/components/issues/issue-layouts/kanban/properties.tsx +++ b/web/components/issues/issue-layouts/kanban/properties.tsx @@ -10,18 +10,31 @@ import { IssuePropertyAssignee } from "../properties/assignee"; import { IssuePropertyEstimates } from "../properties/estimates"; import { IssuePropertyDate } from "../properties/date"; 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 { issue: IIssue; handleIssues: (issue: IIssue) => void; - displayProperties: IIssueDisplayProperties | null; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; showEmptyGroup: boolean; isReadOnly: boolean; } export const KanBanProperties: React.FC = observer((props) => { - const { issue, handleIssues, displayProperties, isReadOnly } = props; + const { issue, handleIssues, issuesFilter, isReadOnly } = props; const handleState = (state: IState) => { handleIssues({ ...issue, state: state.id }); @@ -55,7 +68,10 @@ export const KanBanProperties: React.FC = observer((props) =>
{/* basic properties */} {/* state */} - {displayProperties && displayProperties?.state && ( + displayProperties && displayProperties?.state} + > = observer((props) => disabled={isReadOnly} hideDropdownArrow /> - )} + {/* priority */} - {displayProperties && displayProperties?.priority && ( + displayProperties && displayProperties?.priority} + > - )} + {/* label */} - {displayProperties && displayProperties?.labels && ( + + displayProperties && displayProperties?.labels} + > = observer((props) => disabled={isReadOnly} hideDropdownArrow /> - )} + {/* start date */} - {displayProperties && displayProperties?.start_date && ( + displayProperties?.start_date} + > handleStartDate(date)} disabled={isReadOnly} type="start_date" /> - )} + {/* target/due date */} - {displayProperties && displayProperties?.due_date && ( + displayProperties?.due_date} + > handleTargetDate(date)} disabled={isReadOnly} type="target_date" /> - )} + {/* assignee */} - {displayProperties && displayProperties?.assignee && ( + displayProperties?.assignee} + > = observer((props) => disabled={isReadOnly} multiple /> - )} + {/* estimates */} - {displayProperties && displayProperties?.estimate && ( + displayProperties?.estimate} + > = observer((props) => disabled={isReadOnly} hideDropdownArrow /> - )} + {/* extra render properties */} {/* sub-issues */} - {displayProperties && displayProperties?.sub_issue_count && !!issue?.sub_issues_count && ( + displayProperties?.sub_issue_count && !!issue?.sub_issues_count} + >
{issue.sub_issues_count}
- )} +
{/* attachments */} - {displayProperties && displayProperties?.attachment_count && !!issue?.attachment_count && ( + + displayProperties?.attachment_count && !!issue?.attachment_count + } + >
{issue.attachment_count}
- )} +
{/* link */} - {displayProperties && displayProperties?.link && !!issue?.link_count && ( + displayProperties?.link && !!issue?.link_count} + >
{issue.link_count}
- )} +
); }); diff --git a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx index c33577444..08f9f9e2f 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx @@ -11,67 +11,45 @@ import { IIssue } from "types"; // constants import { EIssueActions } from "../../types"; 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 const KanBanLayout: React.FC = observer(() => { const router = useRouter(); - const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string }; + const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string }; - const { - projectIssues: issueStore, - projectIssuesFilter: issuesFilterStore, - issueKanBanView: issueKanBanViewStore, - kanBanHelpers: kanBanHelperStore, - } = useMobxStore(); + const { issueKanBanView: issueKanBanViewStore } = useMobxStore(); + + const { issues, issuesFilter } = useIssues(EIssuesStoreType.PROJECT); const issueActions = useMemo( () => ({ [EIssueActions.UPDATE]: async (issue: IIssue) => { 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) => { 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 ( ); }); diff --git a/web/components/issues/issue-layouts/kanban/swimlanes.tsx b/web/components/issues/issue-layouts/kanban/swimlanes.tsx index 0de48bb45..d77e79bc2 100644 --- a/web/components/issues/issue-layouts/kanban/swimlanes.tsx +++ b/web/components/issues/issue-layouts/kanban/swimlanes.tsx @@ -1,17 +1,24 @@ import { observer } from "mobx-react-lite"; -//mobx -import { useMobxStore } from "lib/mobx/store-provider"; // components import { KanBan } from "./default"; import { HeaderSubGroupByCard } from "./headers/sub-group-by-card"; import { HeaderGroupByCard } from "./headers/group-by-card"; // types -import { IIssue, IIssueDisplayProperties } from "types"; -import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types"; +import { IIssue } from "types"; +import { IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store_legacy/issues/types"; // constants import { EIssueActions } from "../types"; import { EProjectStore } from "store_legacy/command-palette.store"; 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 { issueIds: any; @@ -51,12 +58,17 @@ const SubGroupSwimlaneHeader: React.FC = ({ ); interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { - issues: IIssueResponse; + issueMap: IIssueStore; issueIds: any; showEmptyGroup: boolean; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; kanBanToggle: any; handleKanBanToggle: any; isDragStarted?: boolean; @@ -75,14 +87,14 @@ interface ISubGroupSwimlane extends ISubGroupSwimlaneHeader { } const SubGroupSwimlane: React.FC = observer((props) => { const { - issues, + issueMap, issueIds, sub_group_by, group_by, list, handleIssues, quickActions, - displayProperties, + issuesFilter, kanBanToggle, handleKanBanToggle, showEmptyGroup, @@ -124,14 +136,14 @@ const SubGroupSwimlane: React.FC = observer((props) => { {!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
= observer((props) => { }); export interface IKanBanSwimLanes { - issues: IIssueResponse; + issueMap: IIssueStore; issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues; + issuesFilter: + | IProjectIssuesFilterStore + | IModuleIssuesFilterStore + | ICycleIssuesFilterStore + | IViewIssuesFilterStore + | IProfileIssuesFilterStore; sub_group_by: string | null; group_by: string | null; handleIssues: (issue: IIssue, action: EIssueActions) => void; quickActions: (issue: IIssue, customActionButton?: React.ReactElement) => React.ReactNode; - displayProperties: IIssueDisplayProperties | null; kanBanToggle: any; handleKanBanToggle: any; showEmptyGroup: boolean; @@ -177,13 +194,13 @@ export interface IKanBanSwimLanes { export const KanBanSwimLanes: React.FC = observer((props) => { const { - issues, + issueMap, issueIds, + issuesFilter, sub_group_by, group_by, handleIssues, quickActions, - displayProperties, kanBanToggle, handleKanBanToggle, showEmptyGroup, @@ -196,16 +213,12 @@ export const KanBanSwimLanes: React.FC = observer((props) => { viewId, } = 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 subGroupByList = getKanbanColumns( - sub_group_by as columnTypes, - project, - projectLabel, - projectMember, - projectState - ); + const groupByList = getKanbanColumns(group_by as columnTypes, project, projectLabel, projectState); + const subGroupByList = getKanbanColumns(sub_group_by as columnTypes, project, projectLabel, projectState); if (!groupByList || !subGroupByList) return null; @@ -224,14 +237,14 @@ export const KanBanSwimLanes: React.FC = observer((props) => { {sub_group_by && ( { switch (groupBy) { case "project": @@ -39,27 +44,32 @@ export const getKanbanColumns = ( return getPriorityColumns(); case "labels": return getLabelsColumns(projectLabel); - case "assignees": - return getAssigneeColumns(projectMember); - case "created_by": - return getCreatedByColumns(projectMember); + // case "assignees": + // return getAssigneeColumns(projectMember); + // case "created_by": + // return getCreatedByColumns(projectMember); } }; 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) => ({ - id: project.id, - name: project.name, - Icon:
{renderEmoji(project.emoji || "")}
, - payload: { project: project.id }, - })); + return projectIds.map((projectId) => { + const project = projectMap[projectId]; + + if (project) + return { + id: project.id, + name: project.name, + Icon:
{renderEmoji(project.emoji || "")}
, + payload: { project: project.id }, + }; + }); }; -const getStateColumns = (projectState: IProjectStateStore): IKanbanColumn[] | undefined => { +const getStateColumns = (projectState: IStateStore): IKanbanColumn[] | undefined => { const { projectStates } = projectState; if (!projectStates) return; @@ -101,8 +111,10 @@ const getPriorityColumns = () => { })); }; -const getLabelsColumns = (projectLabel: IProjectLabelStore) => { - const { projectLabels } = projectLabel; +const getLabelsColumns = (projectLabel: ILabelRootStore) => { + const { + project: { projectLabels }, + } = projectLabel; if (!projectLabels) return; @@ -118,32 +130,185 @@ const getLabelsColumns = (projectLabel: IProjectLabelStore) => { })); }; -const getAssigneeColumns = (projectMember: IProjectMemberStore) => { - const { projectMembers: users } = projectMember; - if (!users) return; +// const getAssigneeColumns = (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: , - payload: { assignees: [member?.id] }, - }; - }); +// return users.map((user) => { +// const member = user.member; +// return { +// id: member?.id, +// name: member?.display_name || "", +// Icon: , +// 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: , +// 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 { projectMembers: users } = projectMember; - if (!users) return; +const handleSortOrder = (destinationIssues: string[], destinationIndex: number, issueMap: IIssueStore) => { + const sortOrderDefaultValue = 65535; + let currentIssueState = {}; - return users.map((user) => { - const member = user.member; - return { - id: member?.id, - name: member?.display_name || "", - Icon: , - payload: { created_by: member?.id }, + if (destinationIssues && destinationIssues.length > 0) { + if (destinationIndex === 0) { + const destinationIssueId = destinationIssues[destinationIndex]; + currentIssueState = { + ...currentIssueState, + sort_order: issueMap.allIssues[destinationIssueId].sort_order - sortOrderDefaultValue, + }; + } 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; }; diff --git a/web/components/issues/issue-layouts/properties/with-display-properties-HOC.tsx b/web/components/issues/issue-layouts/properties/with-display-properties-HOC.tsx new file mode 100644 index 000000000..e32b25728 --- /dev/null +++ b/web/components/issues/issue-layouts/properties/with-display-properties-HOC.tsx @@ -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}; + } +); diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index cb2e20e21..b35b5c24a 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -1,11 +1,8 @@ -import React from "react"; import { useRouter } from "next/router"; import { observer } from "mobx-react-lite"; import useSWR from "swr"; // mobx store import { useMobxStore } from "lib/mobx/store-provider"; -// TODO: update this -import useStoreIssues from "hooks/use-store-issues"; // components import { ListLayout, @@ -17,6 +14,8 @@ import { ProjectEmptyState, } from "components/issues"; import { Spinner } from "@plane/ui"; +import { useIssues } from "hooks/store/use-issues"; +import { EIssuesStoreType } from "constants/issue"; // hooks 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 { - projectIssues: { loader, getIssues, fetchIssues }, - projectIssuesFilter: { issueFilters, fetchFilters }, - } = useMobxStore(); + issues: { loader, getIssues, fetchIssues }, + issuesFilter: { issueFilters, fetchFilters }, + } = useIssues(EIssuesStoreType.PROJECT); - useSWR(workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, async () => { - if (workspaceSlug && projectId) { - await fetchFilters(workspaceSlug, projectId); - await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); - } - }); + useSWR( + workspaceSlug && projectId ? `PROJECT_ISSUES_V3_${workspaceSlug}_${projectId}` : null, + async () => { + if (workspaceSlug && projectId) { + await fetchFilters(workspaceSlug, projectId); + await fetchIssues(workspaceSlug, projectId, getIssues ? "mutation" : "init-loader"); + } + }, + { revalidateOnFocus: false, refreshInterval: 600000, revalidateOnMount: true } + ); // TODO: update this // const { diff --git a/web/constants/issue.ts b/web/constants/issue.ts index c280df8bc..cd5b5c1ae 100644 --- a/web/constants/issue.ts +++ b/web/constants/issue.ts @@ -408,3 +408,13 @@ export const groupReactionEmojis = (reactions: any) => { return _groupedEmojis; }; + +export enum EIssuesStoreType { + MODULE, + CYClE, + PROJECT, + VIEW, + DRAFT, + ARCHIVED, + GLOBAL, +} diff --git a/web/hooks/store/use-issues.ts b/web/hooks/store/use-issues.ts new file mode 100644 index 000000000..381685a2c --- /dev/null +++ b/web/hooks/store/use-issues.ts @@ -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 }; + } +}; diff --git a/web/lib/mobx/store-provider.tsx b/web/lib/mobx/store-provider.tsx index e4b2684a6..aa9e3f1e5 100644 --- a/web/lib/mobx/store-provider.tsx +++ b/web/lib/mobx/store-provider.tsx @@ -1,6 +1,6 @@ import { createContext, useContext } from "react"; -// mobx store import { RootStore } from "store/root.store"; +// mobx store let rootStore: RootStore = new RootStore(); diff --git a/web/store/issue/issue.store.ts b/web/store/issue/issue.store.ts index d28eb5e47..df73be680 100644 --- a/web/store/issue/issue.store.ts +++ b/web/store/issue/issue.store.ts @@ -26,7 +26,7 @@ export class IssueStore implements IIssueStore { makeObservable(this, { // observable - allIssues: observable.ref, + allIssues: observable, // actions addIssue: action, updateIssue: action, diff --git a/web/store/issue/issue_kanban_view.store.ts b/web/store/issue/issue_kanban_view.store.ts new file mode 100644 index 000000000..0ea731447 --- /dev/null +++ b/web/store/issue/issue_kanban_view.store.ts @@ -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], + }; + }; +} diff --git a/web/store/issue/project/issue.store.ts b/web/store/issue/project/issue.store.ts index 063a6990e..37dd56579 100644 --- a/web/store/issue/project/issue.store.ts +++ b/web/store/issue/project/issue.store.ts @@ -14,6 +14,7 @@ import { TIssueGroupByOptions, TLoader, TUnGroupedIssues, + ViewFlags, } from "types"; export interface IProjectIssues { @@ -28,6 +29,7 @@ export interface IProjectIssues { updateIssue: (workspaceSlug: string, projectId: string, issueId: string, data: Partial) => Promise; removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise; quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise; + viewFlags: ViewFlags; } export class ProjectIssues extends IssueHelperStore implements IProjectIssues { @@ -38,6 +40,12 @@ export class ProjectIssues extends IssueHelperStore implements IProjectIssues { issueService; // root store rootIssueStore: IIssueRootStore; + //viewData + viewFlags = { + enableQuickAdd: true, + enableIssueCreation: true, + enableInlineEditing: true, + }; constructor(_rootStore: IIssueRootStore) { super(_rootStore); diff --git a/web/store/issue/root.store.ts b/web/store/issue/root.store.ts index 8a8aac2b2..1f14d7845 100644 --- a/web/store/issue/root.store.ts +++ b/web/store/issue/root.store.ts @@ -18,6 +18,7 @@ import { } from "./project-views"; import { IArchivedIssuesFilter, ArchivedIssuesFilter, IArchivedIssues, ArchivedIssues } from "./archived"; import { IDraftIssuesFilter, DraftIssuesFilter, IDraftIssues, DraftIssues } from "./draft"; +import { IIssueKanBanViewStore, IssueKanBanViewStore } from "./issue_kanban_view.store"; export interface IIssueRootStore { currentUserId: string | undefined; @@ -59,6 +60,8 @@ export interface IIssueRootStore { draftIssuesFilter: IDraftIssuesFilter; draftIssues: IDraftIssues; + + issueKanBanView: IIssueKanBanViewStore; } export class IssueRootStore { @@ -102,6 +105,8 @@ export class IssueRootStore { draftIssuesFilter: IDraftIssuesFilter; draftIssues: IDraftIssues; + issueKanBanView: IIssueKanBanViewStore; + constructor(rootStore: RootStore) { makeObservable(this, { currentUserId: observable.ref, @@ -160,5 +165,7 @@ export class IssueRootStore { this.draftIssuesFilter = new DraftIssuesFilter(this); this.draftIssues = new DraftIssues(); + + this.issueKanBanView = new IssueKanBanViewStore(this); } } diff --git a/web/store/root.store.ts b/web/store/root.store.ts index f09be78ee..f60e000d1 100644 --- a/web/store/root.store.ts +++ b/web/store/root.store.ts @@ -11,7 +11,6 @@ import { IssueRootStore, IIssueRootStore } from "./issue/root.store"; import { IStateStore, StateStore } from "./state.store"; import { IPageStore, PageStore } from "./page.store"; import { ILabelRootStore, LabelRootStore } from "./label"; - enableStaticRendering(typeof window === "undefined"); export class RootStore { diff --git a/web/types/issues.d.ts b/web/types/issues.d.ts index ce9f4453d..cf14760c8 100644 --- a/web/types/issues.d.ts +++ b/web/types/issues.d.ts @@ -272,3 +272,9 @@ export type TUnGroupedIssues = string[]; export interface IIssueResponse { [issue_id: string]: IIssue; } + +export interface ViewFlags { + enableQuickAdd: boolean; + enableIssueCreation: boolean; + enableInlineEditing: boolean; +}