mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
Kanban new store implementation for project issues
This commit is contained in:
parent
78bc9f615f
commit
fd1f262bf7
@ -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<void>;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
@ -93,7 +97,7 @@ export const DeleteIssueModal: React.FC<Props> = (props) => {
|
||||
<p className="text-sm text-custom-text-200">
|
||||
Are you sure you want to delete issue{" "}
|
||||
<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>
|
||||
{""}? All of the data related to the issue will be permanently removed. This action cannot be
|
||||
undone.
|
||||
|
@ -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<IIssue | undefined>;
|
||||
addIssuesToView?: (issueIds: string[]) => Promise<IIssue>;
|
||||
canEditPropertiesBasedOnProject?: (projectId: string) => boolean;
|
||||
}
|
||||
@ -76,33 +59,31 @@ type KanbanDragState = {
|
||||
|
||||
export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = 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<IBaseKanBanLayout> = 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<boolean>(false);
|
||||
@ -161,15 +142,23 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = 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<IBaseKanBanLayout> = 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<IBaseKanBanLayout> = observer((props: IBas
|
||||
return (
|
||||
<>
|
||||
<DeleteIssueModal
|
||||
data={dragState.draggedIssueId ? issues[dragState.draggedIssueId] : ({} as IIssue)}
|
||||
dataId={dragState.draggedIssueId}
|
||||
isOpen={deleteIssueModal}
|
||||
handleClose={() => setDeleteIssueModal(false)}
|
||||
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">
|
||||
<Spinner className="h-5 w-5" />
|
||||
</div>
|
||||
@ -254,18 +251,18 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
</div>
|
||||
|
||||
<KanBanView
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
issueIds={issueIds}
|
||||
issuesFilter={issuesFilter}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={renderQuickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanbanViewStore?.kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
quickAddCallback={issues?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation || !isEditingAllowed}
|
||||
canEditProperties={canEditProperties}
|
||||
|
@ -1,39 +1,57 @@
|
||||
import { memo } from "react";
|
||||
import { Draggable } from "@hello-pangea/dnd";
|
||||
import isEqual from "lodash/isEqual";
|
||||
// components
|
||||
import { KanBanProperties } from "./properties";
|
||||
// ui
|
||||
import { Tooltip } from "@plane/ui";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { IIssue } from "types";
|
||||
import { EIssueActions } from "../types";
|
||||
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 {
|
||||
sub_group_id: string;
|
||||
columnId: string;
|
||||
index: number;
|
||||
issue: IIssue;
|
||||
issuesFilter:
|
||||
| IProjectIssuesFilterStore
|
||||
| IModuleIssuesFilterStore
|
||||
| ICycleIssuesFilterStore
|
||||
| IViewIssuesFilterStore
|
||||
| IProfileIssuesFilterStore;
|
||||
isDragDisabled: boolean;
|
||||
showEmptyGroup: boolean;
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => 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<IssueDetailsBlockProps> = (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<IssueDetailsBlockProps> = (props) => {
|
||||
|
||||
return (
|
||||
<>
|
||||
{displayProperties && displayProperties?.key && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.key}
|
||||
>
|
||||
<div className="relative">
|
||||
<div className="line-clamp-1 text-xs text-custom-text-300">
|
||||
{issue.project_detail.identifier}-{issue.sequence_id}
|
||||
</div>
|
||||
<div className="absolute -top-1 right-0 hidden group-hover/kanban-block:block">{quickActions(issue)}</div>
|
||||
</div>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
<Tooltip tooltipHeading="Title" tooltipContent={issue.name}>
|
||||
<div className="line-clamp-2 text-sm font-medium text-custom-text-100" onClick={handleIssuePeekOverview}>
|
||||
{issue.name}
|
||||
@ -68,8 +89,8 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (props) => {
|
||||
<div>
|
||||
<KanBanProperties
|
||||
issue={issue}
|
||||
issuesFilter={issuesFilter}
|
||||
handleIssues={updateIssue}
|
||||
displayProperties={displayProperties}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
isReadOnly={isReadOnly}
|
||||
/>
|
||||
@ -78,15 +99,7 @@ const KanbanIssueDetailsBlock: React.FC<IssueDetailsBlockProps> = (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<IssueBlockProps> = (props) => {
|
||||
const {
|
||||
@ -94,11 +107,11 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
columnId,
|
||||
index,
|
||||
issue,
|
||||
issuesFilter,
|
||||
isDragDisabled,
|
||||
showEmptyGroup,
|
||||
handleIssues,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
canEditProperties,
|
||||
} = props;
|
||||
|
||||
@ -128,10 +141,10 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
|
||||
>
|
||||
<KanbanIssueMemoBlock
|
||||
issue={issue}
|
||||
issuesFilter={issuesFilter}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
isReadOnly={!canEditIssueProperties}
|
||||
/>
|
||||
</div>
|
||||
|
@ -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<IssueBlocksListProps> = (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<IssueBlocksListProps> = (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 (
|
||||
<KanbanIssueBlock
|
||||
key={`kanban-issue-block-${issue.id}`}
|
||||
index={index}
|
||||
issue={issue}
|
||||
issuesFilter={issuesFilter}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
columnId={columnId}
|
||||
sub_group_id={sub_group_id}
|
||||
isDragDisabled={isDragDisabled}
|
||||
|
@ -5,16 +5,31 @@ import { useMobxStore } from "lib/mobx/store-provider";
|
||||
import { HeaderGroupByCard } from "./headers/group-by-card";
|
||||
import { KanbanGroup } from "./kanban-group";
|
||||
// types
|
||||
import { IIssueDisplayProperties, IIssue } from "types";
|
||||
import { IIssue } from "types";
|
||||
// constants
|
||||
import { columnTypes, getKanbanColumns, IKanbanColumn } from "./utils";
|
||||
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 { 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 {
|
||||
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;
|
||||
@ -22,7 +37,6 @@ export interface IGroupByKanBan {
|
||||
handleIssues: (issue: IIssue, action: EIssueActions) => 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<IGroupByKanBan> = observer((props) => {
|
||||
const {
|
||||
issues,
|
||||
issueMap,
|
||||
issueIds,
|
||||
issuesFilter,
|
||||
sub_group_by,
|
||||
group_by,
|
||||
sub_group_id = "null",
|
||||
@ -50,7 +65,6 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
handleIssues,
|
||||
showEmptyGroup,
|
||||
quickActions,
|
||||
displayProperties,
|
||||
kanBanToggle,
|
||||
handleKanBanToggle,
|
||||
enableQuickIssueCreate,
|
||||
@ -62,9 +76,12 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = 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<IGroupByKanBan> = observer((props) => {
|
||||
)}
|
||||
<KanbanGroup
|
||||
groupId={_list.id}
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
issueIds={issueIds}
|
||||
issuesFilter={issuesFilter}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={sub_group_id}
|
||||
@ -108,7 +126,6 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = observer((props) => {
|
||||
handleIssues={handleIssues}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
quickAddCallback={quickAddCallback}
|
||||
viewId={viewId}
|
||||
@ -124,14 +141,19 @@ const GroupByKanBan: React.FC<IGroupByKanBan> = 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<IKanBan> = 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<IKanBan> = observer((props) => {
|
||||
return (
|
||||
<div className="relative h-full w-full">
|
||||
<GroupByKanBan
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
issuesFilter={issuesFilter}
|
||||
issueIds={issueIds}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
@ -185,7 +208,6 @@ export const KanBan: React.FC<IKanBan> = observer((props) => {
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
enableQuickIssueCreate={enableQuickIssueCreate}
|
||||
|
@ -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 ? (
|
||||
<KanbanIssueBlocksList
|
||||
sub_group_id={sub_group_id}
|
||||
columnId={groupId}
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
issueIds={issueIds?.[groupId] || []}
|
||||
issuesFilter={issuesFilter}
|
||||
isDragDisabled={isDragDisabled}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
canEditProperties={canEditProperties}
|
||||
/>
|
||||
) : null}
|
||||
|
@ -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<IKanBanProperties> = 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<IKanBanProperties> = observer((props) =>
|
||||
<div className="flex flex-wrap items-center gap-2 whitespace-nowrap">
|
||||
{/* basic properties */}
|
||||
{/* state */}
|
||||
{displayProperties && displayProperties?.state && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.state}
|
||||
>
|
||||
<IssuePropertyState
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.state || null}
|
||||
@ -64,20 +80,27 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* priority */}
|
||||
{displayProperties && displayProperties?.priority && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.priority}
|
||||
>
|
||||
<IssuePropertyPriority
|
||||
value={issue?.priority || null}
|
||||
onChange={handlePriority}
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* label */}
|
||||
{displayProperties && displayProperties?.labels && (
|
||||
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties && displayProperties?.labels}
|
||||
>
|
||||
<IssuePropertyLabels
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.labels || null}
|
||||
@ -86,30 +109,39 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* start date */}
|
||||
{displayProperties && displayProperties?.start_date && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.start_date}
|
||||
>
|
||||
<IssuePropertyDate
|
||||
value={issue?.start_date || null}
|
||||
onChange={(date: string) => handleStartDate(date)}
|
||||
disabled={isReadOnly}
|
||||
type="start_date"
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* target/due date */}
|
||||
{displayProperties && displayProperties?.due_date && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.due_date}
|
||||
>
|
||||
<IssuePropertyDate
|
||||
value={issue?.target_date || null}
|
||||
onChange={(date: string) => handleTargetDate(date)}
|
||||
disabled={isReadOnly}
|
||||
type="target_date"
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* assignee */}
|
||||
{displayProperties && displayProperties?.assignee && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.assignee}
|
||||
>
|
||||
<IssuePropertyAssignee
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.assignees || null}
|
||||
@ -119,10 +151,13 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
||||
disabled={isReadOnly}
|
||||
multiple
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* estimates */}
|
||||
{displayProperties && displayProperties?.estimate && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.estimate}
|
||||
>
|
||||
<IssuePropertyEstimates
|
||||
projectId={issue?.project_detail?.id || null}
|
||||
value={issue?.estimate_point || null}
|
||||
@ -130,38 +165,49 @@ export const KanBanProperties: React.FC<IKanBanProperties> = observer((props) =>
|
||||
disabled={isReadOnly}
|
||||
hideDropdownArrow
|
||||
/>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* extra render properties */}
|
||||
{/* 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}`}>
|
||||
<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} />
|
||||
<div className="text-xs">{issue.sub_issues_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* 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}`}>
|
||||
<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} />
|
||||
<div className="text-xs">{issue.attachment_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
|
||||
{/* link */}
|
||||
{displayProperties && displayProperties?.link && !!issue?.link_count && (
|
||||
<WithDisplayPropertiesHOC
|
||||
issuesFilter={issuesFilter}
|
||||
getShouldRenderProperty={(displayProperties) => displayProperties?.link && !!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">
|
||||
<Link className="h-3 w-3 flex-shrink-0" strokeWidth={2} />
|
||||
<div className="text-xs">{issue.link_count}</div>
|
||||
</div>
|
||||
</Tooltip>
|
||||
)}
|
||||
</WithDisplayPropertiesHOC>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
@ -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 (
|
||||
<BaseKanBanRoot
|
||||
issueActions={issueActions}
|
||||
issuesFilterStore={issuesFilterStore}
|
||||
issueStore={issueStore}
|
||||
issues={issues}
|
||||
issuesFilter={issuesFilter}
|
||||
kanbanViewStore={issueKanBanViewStore}
|
||||
showLoader={true}
|
||||
QuickActions={ProjectIssueQuickActions}
|
||||
currentStore={EProjectStore.PROJECT}
|
||||
handleDragDrop={handleDragDrop}
|
||||
/>
|
||||
);
|
||||
});
|
||||
|
@ -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<ISubGroupSwimlaneHeader> = ({
|
||||
);
|
||||
|
||||
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<ISubGroupSwimlane> = 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<ISubGroupSwimlane> = observer((props) => {
|
||||
{!kanBanToggle?.subgroupByIssuesVisibility.includes(_list.id) && (
|
||||
<div className="relative">
|
||||
<KanBan
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
issueIds={issueIds?.[_list.id]}
|
||||
issuesFilter={issuesFilter}
|
||||
sub_group_by={sub_group_by}
|
||||
group_by={group_by}
|
||||
sub_group_id={_list.id}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
@ -150,13 +162,18 @@ const SubGroupSwimlane: React.FC<ISubGroupSwimlane> = 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<IKanBanSwimLanes> = 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<IKanBanSwimLanes> = 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<IKanBanSwimLanes> = observer((props) => {
|
||||
|
||||
{sub_group_by && (
|
||||
<SubGroupSwimlane
|
||||
issues={issues}
|
||||
issueMap={issueMap}
|
||||
list={subGroupByList}
|
||||
issueIds={issueIds}
|
||||
issuesFilter={issuesFilter}
|
||||
group_by={group_by}
|
||||
sub_group_by={sub_group_by}
|
||||
handleIssues={handleIssues}
|
||||
quickActions={quickActions}
|
||||
displayProperties={displayProperties}
|
||||
kanBanToggle={kanBanToggle}
|
||||
handleKanBanToggle={handleKanBanToggle}
|
||||
showEmptyGroup={showEmptyGroup}
|
||||
|
@ -1,9 +1,14 @@
|
||||
import { DraggableLocation } from "@hello-pangea/dnd";
|
||||
import { Avatar, PriorityIcon, StateGroupIcon } from "@plane/ui";
|
||||
import { ISSUE_PRIORITIES, ISSUE_STATE_GROUPS } from "constants/issue";
|
||||
import { renderEmoji } from "helpers/emoji.helper";
|
||||
import { ReactElement } from "react";
|
||||
import { IProjectLabelStore, IProjectMemberStore, IProjectStateStore, IProjectStore } from "store_legacy/project";
|
||||
import { IIssue } from "types";
|
||||
import { IIssueStore } from "store/issue/issue.store";
|
||||
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 =
|
||||
| "project"
|
||||
@ -24,9 +29,9 @@ export interface IKanbanColumn {
|
||||
export const getKanbanColumns = (
|
||||
groupBy: columnTypes | null,
|
||||
project: IProjectStore,
|
||||
projectLabel: IProjectLabelStore,
|
||||
projectMember: IProjectMemberStore,
|
||||
projectState: IProjectStateStore
|
||||
projectLabel: ILabelRootStore,
|
||||
projectState: IStateStore
|
||||
//projectMember?: IProjectMemberStore,
|
||||
): IKanbanColumn[] | undefined => {
|
||||
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: <div className="w-6 h-6">{renderEmoji(project.emoji || "")}</div>,
|
||||
payload: { project: project.id },
|
||||
}));
|
||||
return projectIds.map((projectId) => {
|
||||
const project = projectMap[projectId];
|
||||
|
||||
if (project)
|
||||
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;
|
||||
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: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
payload: { assignees: [member?.id] },
|
||||
};
|
||||
});
|
||||
// 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: { 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 { 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: <Avatar name={member?.display_name} src={member?.avatar} size="md" />,
|
||||
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;
|
||||
};
|
||||
|
@ -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}</>;
|
||||
}
|
||||
);
|
@ -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 {
|
||||
|
@ -408,3 +408,13 @@ export const groupReactionEmojis = (reactions: any) => {
|
||||
|
||||
return _groupedEmojis;
|
||||
};
|
||||
|
||||
export enum EIssuesStoreType {
|
||||
MODULE,
|
||||
CYClE,
|
||||
PROJECT,
|
||||
VIEW,
|
||||
DRAFT,
|
||||
ARCHIVED,
|
||||
GLOBAL,
|
||||
}
|
||||
|
20
web/hooks/store/use-issues.ts
Normal file
20
web/hooks/store/use-issues.ts
Normal 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 };
|
||||
}
|
||||
};
|
@ -1,6 +1,6 @@
|
||||
import { createContext, useContext } from "react";
|
||||
// mobx store
|
||||
import { RootStore } from "store/root.store";
|
||||
// mobx store
|
||||
|
||||
let rootStore: RootStore = new RootStore();
|
||||
|
||||
|
@ -26,7 +26,7 @@ export class IssueStore implements IIssueStore {
|
||||
|
||||
makeObservable(this, {
|
||||
// observable
|
||||
allIssues: observable.ref,
|
||||
allIssues: observable,
|
||||
// actions
|
||||
addIssue: action,
|
||||
updateIssue: action,
|
||||
|
76
web/store/issue/issue_kanban_view.store.ts
Normal file
76
web/store/issue/issue_kanban_view.store.ts
Normal 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],
|
||||
};
|
||||
};
|
||||
}
|
@ -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<IIssue>) => Promise<IIssue>;
|
||||
removeIssue: (workspaceSlug: string, projectId: string, issueId: string) => Promise<IIssue>;
|
||||
quickAddIssue: (workspaceSlug: string, projectId: string, data: IIssue) => Promise<IIssue>;
|
||||
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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
6
web/types/issues.d.ts
vendored
6
web/types/issues.d.ts
vendored
@ -272,3 +272,9 @@ export type TUnGroupedIssues = string[];
|
||||
export interface IIssueResponse {
|
||||
[issue_id: string]: IIssue;
|
||||
}
|
||||
|
||||
export interface ViewFlags {
|
||||
enableQuickAdd: boolean;
|
||||
enableIssueCreation: boolean;
|
||||
enableInlineEditing: boolean;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user