= 2 ? "bg-custom-primary-100 h-4 w-4" : " h-3 w-3 bg-onboarding-background-100"
+ step >= 2 ? "bg-custom-primary-100 h-3 w-3" : " h-2 w-2 bg-onboarding-background-100"
}`}
/>
= 3 ? "bg-custom-primary-100" : "bg-onboarding-background-100"}`} />
= 3 ? "bg-custom-primary-100 h-4 w-4" : "h-3 w-3 bg-onboarding-background-100"
+ step >= 3 ? "bg-custom-primary-100 h-3 w-3" : "h-2 w-2 bg-onboarding-background-100"
}`}
/>
diff --git a/web/components/api-token/ApiTokenForm/ApiTokenDescription.tsx b/web/components/api-token/ApiTokenForm/ApiTokenDescription.tsx
deleted file mode 100644
index d17e4662e..000000000
--- a/web/components/api-token/ApiTokenForm/ApiTokenDescription.tsx
+++ /dev/null
@@ -1,55 +0,0 @@
-import { TextArea } from "@plane/ui";
-import { Control, Controller, FieldErrors } from "react-hook-form";
-import { IApiToken } from "types/api_token";
-import { IApiFormFields } from "./types";
-import { Dispatch, SetStateAction } from "react";
-
-interface IApiTokenDescription {
- generatedToken: IApiToken | null | undefined;
- control: Control
;
- focusDescription: boolean;
- setFocusTitle: Dispatch>;
- setFocusDescription: Dispatch>;
-}
-
-export const ApiTokenDescription = ({
- generatedToken,
- control,
- focusDescription,
- setFocusTitle,
- setFocusDescription,
-}: IApiTokenDescription) => (
-
- focusDescription ? (
-
);
diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
index 8b12b9b11..ac71259f2 100644
--- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
+++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-root.tsx
@@ -7,65 +7,53 @@ import { useMobxStore } from "lib/mobx/store-provider";
import { AppliedFiltersList } from "components/issues";
// types
import { IIssueFilterOptions } from "types";
+import { EFilterType } from "store/issues/types";
export const ProjectAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
const { workspaceSlug, projectId } = router.query;
const {
- issueFilter: issueFilterStore,
projectLabel: { projectLabels },
projectState: projectStateStore,
projectMember: { projectMembers },
+ projectIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();
- const userFilters = issueFilterStore.userFilters;
+ const userFilters = issueFilters?.filters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
- Object.entries(userFilters).forEach(([key, value]) => {
+ Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) return;
-
if (Array.isArray(value) && value.length === 0) return;
-
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
if (!workspaceSlug || !projectId) return;
-
- // remove all values of the key if value is null
if (!value) {
- issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
- filters: {
- [key]: null,
- },
+ updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
+ [key]: null,
});
return;
}
- // remove the passed value from the key
- let newValues = issueFilterStore.userFilters?.[key] ?? [];
+ let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
- issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
- filters: {
- [key]: newValues,
- },
+ updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, {
+ [key]: newValues,
});
};
const handleClearAllFilters = () => {
if (!workspaceSlug || !projectId) return;
-
const newFilters: IIssueFilterOptions = {};
- Object.keys(userFilters).forEach((key) => {
+ Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
-
- issueFilterStore.updateUserFilters(workspaceSlug.toString(), projectId.toString(), {
- filters: { ...newFilters },
- });
+ updateFilters(workspaceSlug.toString(), projectId.toString(), EFilterType.FILTERS, { ...newFilters });
};
// return if no filters are applied
diff --git a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
index 65462d277..09a10e704 100644
--- a/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
+++ b/web/components/issues/issue-layouts/filters/applied-filters/roots/project-view-root.tsx
@@ -12,65 +12,78 @@ import { Button } from "@plane/ui";
import { areFiltersDifferent } from "helpers/filter.helper";
// types
import { IIssueFilterOptions } from "types";
+import { EFilterType } from "store/issues/types";
export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
const router = useRouter();
- const { workspaceSlug, projectId, viewId } = router.query;
+ const { workspaceSlug, projectId, viewId } = router.query as {
+ workspaceSlug: string;
+ projectId: string;
+ viewId: string;
+ };
const {
projectLabel: { projectLabels },
- projectMember: { projectMembers },
projectState: projectStateStore,
+ projectMember: { projectMembers },
projectViews: projectViewsStore,
projectViewFilters: projectViewFiltersStore,
+ viewIssuesFilter: { issueFilters, updateFilters },
} = useMobxStore();
const viewDetails = viewId ? projectViewsStore.viewDetails[viewId.toString()] : undefined;
const storedFilters = viewId ? projectViewFiltersStore.storedFilters[viewId.toString()] : undefined;
+ const userFilters = issueFilters?.filters;
// filters whose value not null or empty array
const appliedFilters: IIssueFilterOptions = {};
- Object.entries(storedFilters ?? {}).forEach(([key, value]) => {
+ Object.entries(userFilters ?? {}).forEach(([key, value]) => {
if (!value) return;
-
if (Array.isArray(value) && value.length === 0) return;
-
appliedFilters[key as keyof IIssueFilterOptions] = value;
});
const handleRemoveFilter = (key: keyof IIssueFilterOptions, value: string | null) => {
- if (!viewId) return;
-
- // remove all values of the key if value is null
+ if (!workspaceSlug || !projectId) return;
if (!value) {
- projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
- [key]: null,
- });
+ updateFilters(
+ workspaceSlug,
+ projectId,
+ EFilterType.FILTERS,
+ {
+ [key]: null,
+ },
+ viewId
+ );
return;
}
- // remove the passed value from the key
- let newValues = storedFilters?.[key] ?? [];
+ let newValues = issueFilters?.filters?.[key] ?? [];
newValues = newValues.filter((val) => val !== value);
- projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
- [key]: newValues,
- });
+ updateFilters(
+ workspaceSlug,
+ projectId,
+ EFilterType.FILTERS,
+ {
+ [key]: newValues,
+ },
+ viewId
+ );
};
const handleClearAllFilters = () => {
- if (!workspaceSlug || !projectId || !viewId) return;
-
+ if (!workspaceSlug || !projectId) return;
const newFilters: IIssueFilterOptions = {};
- Object.keys(storedFilters ?? {}).forEach((key) => {
+ Object.keys(userFilters ?? {}).forEach((key) => {
newFilters[key as keyof IIssueFilterOptions] = null;
});
-
- projectViewFiltersStore.updateStoredFilters(viewId.toString(), {
- ...newFilters,
- });
+ updateFilters(workspaceSlug, projectId, EFilterType.FILTERS, { ...newFilters }, viewId);
};
+ // return if no filters are applied
+ if (Object.keys(appliedFilters).length === 0) return null;
+
const handleUpdateView = () => {
if (!workspaceSlug || !projectId || !viewId || !viewDetails) return;
@@ -82,17 +95,6 @@ export const ProjectViewAppliedFiltersRoot: React.FC = observer(() => {
});
};
- // update stored filters when view details are fetched
- useEffect(() => {
- if (!viewId || !viewDetails) return;
-
- if (!projectViewFiltersStore.storedFilters[viewId.toString()])
- projectViewFiltersStore.updateStoredFilters(viewId.toString(), viewDetails?.query_data ?? {});
- }, [projectViewFiltersStore, viewDetails, viewId]);
-
- // return if no filters are applied
- if (Object.keys(appliedFilters).length === 0) return null;
-
return (
= observer((props: IBaseGanttRoot) => {
+ const { issueFiltersStore, issueStore, viewId } = props;
+
+ const router = useRouter();
+ const { workspaceSlug } = router.query as { workspaceSlug: string; projectId: string };
+
+ const { projectDetails } = useProjectDetails();
+
+ const appliedDisplayFilters = issueFiltersStore.issueFilters?.displayFilters;
+
+ const issuesResponse = issueStore.getIssues;
+ const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
+
+ const issues = issueIds.map((id) => issuesResponse?.[id]);
+
+ const updateIssue = (issue: IIssue, payload: IBlockUpdateData) => {
+ if (!workspaceSlug) return;
+
+ //Todo fix sort order in the structure
+ issueStore.updateIssue(workspaceSlug, issue.project, issue.id, {
+ start_date: payload.start_date,
+ target_date: payload.target_date,
+ });
+ };
+
+ const isAllowed = (projectDetails?.member_role || 0) >= EUserWorkspaceRoles.MEMBER;
+
+ return (
+ <>
+
+ }
+ sidebarToRender={(props) => (
+
+ )}
+ enableBlockLeftResize={isAllowed}
+ enableBlockRightResize={isAllowed}
+ enableBlockMove={isAllowed}
+ enableReorder={appliedDisplayFilters?.order_by === "sort_order" && isAllowed}
+ />
+
+ >
+ );
+});
diff --git a/web/components/issues/issue-layouts/gantt/cycle-root.tsx b/web/components/issues/issue-layouts/gantt/cycle-root.tsx
index 56a17db15..70f92a933 100644
--- a/web/components/issues/issue-layouts/gantt/cycle-root.tsx
+++ b/web/components/issues/issue-layouts/gantt/cycle-root.tsx
@@ -1,57 +1,15 @@
-import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
-import useProjectDetails from "hooks/use-project-details";
// components
-import { IssueGanttBlock } from "components/issues";
-import {
- GanttChartRoot,
- IBlockUpdateData,
- renderIssueBlocksStructure,
- IssueGanttSidebar,
-} from "components/gantt-chart";
-// types
-import { IIssueUnGroupedStructure } from "store/issue";
-import { IIssue } from "types";
+import { BaseGanttRoot } from "./base-gantt-root";
+import { useRouter } from "next/router";
export const CycleGanttLayout: React.FC = observer(() => {
const router = useRouter();
- const { workspaceSlug, cycleId } = router.query;
+ const { cycleId } = router.query as { cycleId: string };
- const { projectDetails } = useProjectDetails();
+ const { cycleIssues: cycleIssueStore, cycleIssuesFilter: cycleIssueFilterStore } = useMobxStore();
- const { cycleIssue: cycleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
-
- const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
-
- const issues = cycleIssueStore.getIssues;
-
- const updateIssue = (block: any, payload: IBlockUpdateData) => {
- if (!workspaceSlug || !cycleId) return;
-
- cycleIssueStore.updateGanttIssueStructure(workspaceSlug.toString(), cycleId.toString(), block, payload);
- };
-
- const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
-
- return (
- <>
-
- }
- sidebarToRender={(props) => }
- enableBlockLeftResize={isAllowed}
- enableBlockRightResize={isAllowed}
- enableBlockMove={isAllowed}
- enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
- />
-
- >
- );
+ return ;
});
diff --git a/web/components/issues/issue-layouts/gantt/index.ts b/web/components/issues/issue-layouts/gantt/index.ts
index 87899ae83..c598e624a 100644
--- a/web/components/issues/issue-layouts/gantt/index.ts
+++ b/web/components/issues/issue-layouts/gantt/index.ts
@@ -1,6 +1,6 @@
export * from "./blocks";
export * from "./cycle-root";
+export * from "./quick-add-issue-form";
export * from "./module-root";
+export * from "./project-root";
export * from "./project-view-root";
-export * from "./root";
-export * from "./inline-create-issue-form";
diff --git a/web/components/issues/issue-layouts/gantt/module-root.tsx b/web/components/issues/issue-layouts/gantt/module-root.tsx
index c5a880bbe..49467c558 100644
--- a/web/components/issues/issue-layouts/gantt/module-root.tsx
+++ b/web/components/issues/issue-layouts/gantt/module-root.tsx
@@ -1,57 +1,15 @@
-import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
// hooks
import { useMobxStore } from "lib/mobx/store-provider";
-import useProjectDetails from "hooks/use-project-details";
// components
-import { IssueGanttBlock, IssueGanttSidebarBlock } from "components/issues";
-import {
- GanttChartRoot,
- IBlockUpdateData,
- renderIssueBlocksStructure,
- IssueGanttSidebar,
-} from "components/gantt-chart";
-// types
-import { IIssueUnGroupedStructure } from "store/issue";
-import { IIssue } from "types";
+import { BaseGanttRoot } from "./base-gantt-root";
+import { useRouter } from "next/router";
export const ModuleGanttLayout: React.FC = observer(() => {
const router = useRouter();
- const { workspaceSlug, moduleId } = router.query;
+ const { moduleId } = router.query as { moduleId: string };
- const { projectDetails } = useProjectDetails();
+ const { moduleIssues: moduleIssueStore, moduleIssuesFilter: moduleIssueFilterStore } = useMobxStore();
- const { moduleIssue: moduleIssueStore, issueFilter: issueFilterStore } = useMobxStore();
-
- const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
-
- const issues = moduleIssueStore.getIssues;
-
- const updateIssue = (block: any, payload: IBlockUpdateData) => {
- if (!workspaceSlug || !moduleId) return;
-
- moduleIssueStore.updateGanttIssueStructure(workspaceSlug.toString(), moduleId.toString(), block, payload);
- };
-
- const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
-
- return (
- <>
-
- }
- blockToRender={(data: IIssue) => }
- enableBlockLeftResize={isAllowed}
- enableBlockRightResize={isAllowed}
- enableBlockMove={isAllowed}
- enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
- />
-
- >
- );
+ return ;
});
diff --git a/web/components/issues/issue-layouts/gantt/project-root.tsx b/web/components/issues/issue-layouts/gantt/project-root.tsx
new file mode 100644
index 000000000..d35d95d05
--- /dev/null
+++ b/web/components/issues/issue-layouts/gantt/project-root.tsx
@@ -0,0 +1,12 @@
+import React from "react";
+import { observer } from "mobx-react-lite";
+// hooks
+import { useMobxStore } from "lib/mobx/store-provider";
+// components
+import { BaseGanttRoot } from "./base-gantt-root";
+
+export const GanttLayout: React.FC = observer(() => {
+ const { projectIssues: projectIssuesStore, projectIssuesFilter: projectIssueFiltersStore } = useMobxStore();
+
+ return ;
+});
diff --git a/web/components/issues/issue-layouts/gantt/project-view-root.tsx b/web/components/issues/issue-layouts/gantt/project-view-root.tsx
index c06afd817..3155aae6f 100644
--- a/web/components/issues/issue-layouts/gantt/project-view-root.tsx
+++ b/web/components/issues/issue-layouts/gantt/project-view-root.tsx
@@ -1,59 +1,11 @@
-import { useRouter } from "next/router";
import { observer } from "mobx-react-lite";
-
// mobx store
import { useMobxStore } from "lib/mobx/store-provider";
-// hooks
-import useProjectDetails from "hooks/use-project-details";
// components
-import { IssueGanttBlock } from "components/issues";
-import {
- GanttChartRoot,
- IBlockUpdateData,
- renderIssueBlocksStructure,
- ProjectViewGanttSidebar,
-} from "components/gantt-chart";
-// types
-import { IIssueUnGroupedStructure } from "store/issue";
-import { IIssue } from "types";
+import { BaseGanttRoot } from "./base-gantt-root";
export const ProjectViewGanttLayout: React.FC = observer(() => {
- const router = useRouter();
- const { workspaceSlug, viewId } = router.query;
+ const { viewIssues: projectIssueViewStore, viewIssuesFilter: projectIssueViewFiltersStore } = useMobxStore();
- const { projectDetails } = useProjectDetails();
-
- const { projectViewIssues: projectViewIssuesStore, issueFilter: issueFilterStore } = useMobxStore();
-
- const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
-
- const issues = projectViewIssuesStore.getIssues;
-
- const updateIssue = (block: any, payload: IBlockUpdateData) => {
- if (!workspaceSlug || !viewId) return;
-
- projectViewIssuesStore.updateGanttIssueStructure(workspaceSlug.toString(), viewId.toString(), block, payload);
- };
-
- const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
-
- return (
- <>
-
-
}
- sidebarToRender={(props) => }
- enableBlockLeftResize={isAllowed}
- enableBlockRightResize={isAllowed}
- enableBlockMove={isAllowed}
- enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
- />
-
- >
- );
+ return ;
});
diff --git a/web/components/issues/issue-layouts/gantt/inline-create-issue-form.tsx b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx
similarity index 90%
rename from web/components/issues/issue-layouts/gantt/inline-create-issue-form.tsx
rename to web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx
index 0c87e6950..4e75804d8 100644
--- a/web/components/issues/issue-layouts/gantt/inline-create-issue-form.tsx
+++ b/web/components/issues/issue-layouts/gantt/quick-add-issue-form.tsx
@@ -20,6 +20,13 @@ import { createIssuePayload } from "helpers/issue.helper";
type Props = {
prePopulatedData?: Partial;
onSuccess?: (data: IIssue) => Promise | void;
+ quickAddCallback?: (
+ workspaceSlug: string,
+ projectId: string,
+ data: IIssue,
+ viewId?: string
+ ) => Promise;
+ viewId?: string;
};
const defaultValues: Partial = {
@@ -47,14 +54,14 @@ const Inputs = (props: any) => {
};
export const GanttInlineCreateIssueForm: React.FC = observer((props) => {
- const { prePopulatedData } = props;
+ const { prePopulatedData, quickAddCallback, viewId } = props;
// router
const router = useRouter();
- const { workspaceSlug, projectId } = router.query;
+ const { workspaceSlug, projectId } = router.query as { workspaceSlug: string; projectId: string };
// store
- const { workspace: workspaceStore, quickAddIssue: quickAddStore } = useMobxStore();
+ const { workspace: workspaceStore } = useMobxStore();
const { projectDetails } = useProjectDetails();
@@ -114,15 +121,7 @@ export const GanttInlineCreateIssueForm: React.FC = observer((props) => {
});
try {
- quickAddStore.createIssue(
- workspaceSlug.toString(),
- projectId.toString(),
- {
- group_id: null,
- sub_group_id: null,
- },
- payload
- );
+ quickAddCallback && quickAddCallback(workspaceSlug, projectId, payload, viewId);
setToastAlert({
type: "success",
diff --git a/web/components/issues/issue-layouts/gantt/root.tsx b/web/components/issues/issue-layouts/gantt/root.tsx
deleted file mode 100644
index d7ddd69b2..000000000
--- a/web/components/issues/issue-layouts/gantt/root.tsx
+++ /dev/null
@@ -1,58 +0,0 @@
-import React from "react";
-import { useRouter } from "next/router";
-import { observer } from "mobx-react-lite";
-// hooks
-import { useMobxStore } from "lib/mobx/store-provider";
-import useProjectDetails from "hooks/use-project-details";
-// components
-import { IssueGanttBlock } from "components/issues";
-import {
- GanttChartRoot,
- IBlockUpdateData,
- renderIssueBlocksStructure,
- IssueGanttSidebar,
-} from "components/gantt-chart";
-// types
-import { IIssueUnGroupedStructure } from "store/issue";
-import { IIssue } from "types";
-
-export const GanttLayout: React.FC = observer(() => {
- const router = useRouter();
- const { workspaceSlug } = router.query;
-
- const { projectDetails } = useProjectDetails();
-
- const { issue: issueStore, issueFilter: issueFilterStore } = useMobxStore();
-
- const appliedDisplayFilters = issueFilterStore.userDisplayFilters;
-
- const issues = issueStore.getIssues;
-
- const updateIssue = (block: IIssue, payload: IBlockUpdateData) => {
- if (!workspaceSlug) return;
-
- issueStore.updateGanttIssueStructure(workspaceSlug.toString(), block, payload);
- };
-
- const isAllowed = projectDetails?.member_role === 20 || projectDetails?.member_role === 15;
-
- return (
- <>
-
- }
- sidebarToRender={(props) => }
- enableBlockLeftResize={isAllowed}
- enableBlockRightResize={isAllowed}
- enableBlockMove={isAllowed}
- enableReorder={appliedDisplayFilters.order_by === "sort_order" && isAllowed}
- />
-
- >
- );
-});
diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
new file mode 100644
index 000000000..eedce7313
--- /dev/null
+++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx
@@ -0,0 +1,194 @@
+import { FC, useCallback, useState } from "react";
+import { useRouter } from "next/router";
+import { DragDropContext } from "@hello-pangea/dnd";
+import { observer } from "mobx-react-lite";
+// mobx store
+import { useMobxStore } from "lib/mobx/store-provider";
+// ui
+import { Spinner } from "@plane/ui";
+// types
+import { IIssue } from "types";
+import { EIssueActions } from "../types";
+import { ICycleIssuesStore, IModuleIssuesStore, IProjectIssuesStore, IViewIssuesStore } from "store/issues";
+import { IQuickActionProps } from "../list/list-view-types";
+import { IIssueKanBanViewStore } from "store/issue";
+// constants
+import { ISSUE_STATE_GROUPS, ISSUE_PRIORITIES } from "constants/issue";
+//components
+import { KanBan } from "./default";
+import { KanBanSwimLanes } from "./swimlanes";
+
+export interface IBaseKanBanLayout {
+ issueStore: IProjectIssuesStore | IModuleIssuesStore | ICycleIssuesStore | IViewIssuesStore;
+ kanbanViewStore: IIssueKanBanViewStore;
+ QuickActions: FC;
+ issueActions: {
+ [EIssueActions.DELETE]: (issue: IIssue) => void;
+ [EIssueActions.UPDATE]?: (issue: IIssue) => void;
+ [EIssueActions.REMOVE]?: (issue: IIssue) => void;
+ };
+ showLoader?: boolean;
+ viewId?: string;
+}
+
+export const BaseKanBanRoot: React.FC = observer((props: IBaseKanBanLayout) => {
+ const { issueStore, kanbanViewStore, QuickActions, issueActions, showLoader, viewId } = props;
+
+ const {
+ project: { workspaceProjects },
+ projectLabel: { projectLabels },
+ projectMember: { projectMembers },
+ projectState: projectStateStore,
+ projectIssuesFilter: issueFilterStore,
+ } = useMobxStore();
+
+ const issues = issueStore?.getIssues || {};
+ const issueIds = issueStore?.getIssuesIds || [];
+
+ const displayFilters = issueFilterStore?.issueFilters?.displayFilters;
+ const displayProperties = issueFilterStore?.issueFilters?.displayProperties || null;
+
+ const sub_group_by: string | null = displayFilters?.sub_group_by || null;
+
+ const group_by: string | null = displayFilters?.group_by || null;
+
+ const order_by: string | null = displayFilters?.order_by || null;
+
+ const userDisplayFilters = displayFilters || null;
+
+ const currentKanBanView: "swimlanes" | "default" = sub_group_by ? "swimlanes" : "default";
+
+ const [isDragStarted, setIsDragStarted] = useState(false);
+
+ const onDragStart = () => {
+ setIsDragStarted(true);
+ };
+
+ const onDragEnd = (result: any) => {
+ setIsDragStarted(false);
+
+ if (!result) return;
+
+ if (
+ result.destination &&
+ result.source &&
+ result.source.droppableId &&
+ result.destination.droppableId &&
+ result.destination.droppableId === result.source.droppableId &&
+ result.destination.index === result.source.index
+ )
+ return;
+
+ currentKanBanView === "default"
+ ? kanbanViewStore?.handleDragDrop(result.source, result.destination)
+ : kanbanViewStore?.handleSwimlaneDragDrop(result.source, result.destination);
+ };
+
+ const handleIssues = useCallback(
+ async (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
+ if (issueActions[action]) {
+ issueActions[action]!(issue);
+ }
+ },
+ [issueStore]
+ );
+
+ const handleKanBanToggle = (toggle: "groupByHeaderMinMax" | "subgroupByIssuesVisibility", value: string) => {
+ kanbanViewStore.handleKanBanToggle(toggle, value);
+ };
+
+ const states = projectStateStore?.projectStates || null;
+ const priorities = ISSUE_PRIORITIES || null;
+ const stateGroups = ISSUE_STATE_GROUPS || null;
+
+ return (
+ <>
+ {showLoader && issueStore?.loader === "mutation" && (
+
+
+
+ )}
+
+
+
+ {currentKanBanView === "default" ? (
+ (
+ handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
+ handleUpdate={
+ issueActions[EIssueActions.UPDATE]
+ ? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
+ : undefined
+ }
+ handleRemoveFromView={
+ issueActions[EIssueActions.REMOVE]
+ ? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
+ : undefined
+ }
+ />
+ )}
+ displayProperties={displayProperties}
+ kanBanToggle={kanbanViewStore?.kanBanToggle}
+ handleKanBanToggle={handleKanBanToggle}
+ states={states}
+ stateGroups={stateGroups}
+ priorities={priorities}
+ labels={projectLabels}
+ members={projectMembers?.map((m) => m.member) ?? null}
+ projects={workspaceProjects}
+ enableQuickIssueCreate
+ showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
+ isDragStarted={isDragStarted}
+ quickAddCallback={issueStore.quickAddIssue}
+ viewId={viewId}
+ />
+ ) : (
+ (
+ handleIssues(sub_group_by, group_by, issue, EIssueActions.DELETE)}
+ handleUpdate={
+ issueActions[EIssueActions.UPDATE]
+ ? async (data) => handleIssues(sub_group_by, group_by, data, EIssueActions.UPDATE)
+ : undefined
+ }
+ handleRemoveFromView={
+ issueActions[EIssueActions.REMOVE]
+ ? async () => handleIssues(sub_group_by, group_by, issue, EIssueActions.REMOVE)
+ : undefined
+ }
+ />
+ )}
+ displayProperties={displayProperties}
+ kanBanToggle={kanbanViewStore?.kanBanToggle}
+ handleKanBanToggle={handleKanBanToggle}
+ states={states}
+ stateGroups={stateGroups}
+ priorities={priorities}
+ labels={projectLabels}
+ members={projectMembers?.map((m) => m.member) ?? null}
+ projects={workspaceProjects}
+ showEmptyGroup={userDisplayFilters?.show_empty_groups || true}
+ isDragStarted={isDragStarted}
+ />
+ )}
+
+
+ >
+ );
+});
diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx
index 9d4c8aedd..94f497b43 100644
--- a/web/components/issues/issue-layouts/kanban/block.tsx
+++ b/web/components/issues/issue-layouts/kanban/block.tsx
@@ -5,6 +5,7 @@ import { Tooltip } from "@plane/ui";
import { IssuePeekOverview } from "components/issues/issue-peek-overview";
// types
import { IIssueDisplayProperties, IIssue } from "types";
+import { EIssueActions } from "../types";
interface IssueBlockProps {
sub_group_id: string;
@@ -13,14 +14,9 @@ interface IssueBlockProps {
issue: IIssue;
isDragDisabled: boolean;
showEmptyGroup: boolean;
- handleIssues: (
- sub_group_by: string | null,
- group_by: string | null,
- issue: IIssue,
- action: "update" | "delete"
- ) => void;
+ handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
- displayProperties: IIssueDisplayProperties;
+ displayProperties: IIssueDisplayProperties | null;
}
export const KanbanIssueBlock: React.FC = (props) => {
@@ -37,7 +33,7 @@ export const KanbanIssueBlock: React.FC = (props) => {
} = props;
const updateIssue = (sub_group_by: string | null, group_by: string | null, issueToUpdate: IIssue) => {
- if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, "update");
+ if (issueToUpdate) handleIssues(sub_group_by, group_by, issueToUpdate, EIssueActions.UPDATE);
};
return (
@@ -79,7 +75,7 @@ export const KanbanIssueBlock: React.FC = (props) => {
!sub_group_id && sub_group_id === "null" ? null : sub_group_id,
!columnId && columnId === "null" ? null : columnId,
{ ...issue, ...issueToUpdate },
- "update"
+ EIssueActions.UPDATE
);
}}
>
diff --git a/web/components/issues/issue-layouts/kanban/blocks-list.tsx b/web/components/issues/issue-layouts/kanban/blocks-list.tsx
index ea1b4c840..26e2555b4 100644
--- a/web/components/issues/issue-layouts/kanban/blocks-list.tsx
+++ b/web/components/issues/issue-layouts/kanban/blocks-list.tsx
@@ -1,21 +1,19 @@
// components
import { KanbanIssueBlock } from "components/issues";
import { IIssueDisplayProperties, IIssue } from "types";
+import { EIssueActions } from "../types";
+import { IIssueResponse } from "store/issues/types";
interface IssueBlocksListProps {
sub_group_id: string;
columnId: string;
- issues: IIssue[];
+ issues: IIssueResponse;
+ issueIds: string[];
isDragDisabled: boolean;
showEmptyGroup: boolean;
- handleIssues: (
- sub_group_by: string | null,
- group_by: string | null,
- issue: IIssue,
- action: "update" | "delete"
- ) => void;
+ handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
- displayProperties: IIssueDisplayProperties;
+ displayProperties: IIssueDisplayProperties | null;
}
export const KanbanIssueBlocksList: React.FC = (props) => {
@@ -23,6 +21,7 @@ export const KanbanIssueBlocksList: React.FC = (props) =>
sub_group_id,
columnId,
issues,
+ issueIds,
showEmptyGroup,
isDragDisabled,
handleIssues,
@@ -32,22 +31,28 @@ export const KanbanIssueBlocksList: React.FC = (props) =>
return (
<>
- {issues && issues.length > 0 ? (
+ {issueIds && issueIds.length > 0 ? (
<>
- {issues.map((issue, index) => (
-
- ))}
+ {issueIds.map((issueId, index) => {
+ if (!issues[issueId]) return null;
+
+ const issue = issues[issueId];
+
+ return (
+
+ );
+ })}
>
) : (
!isDragDisabled && (
diff --git a/web/components/issues/issue-layouts/kanban/default.tsx b/web/components/issues/issue-layouts/kanban/default.tsx
index eee31e7c3..203979edf 100644
--- a/web/components/issues/issue-layouts/kanban/default.tsx
+++ b/web/components/issues/issue-layouts/kanban/default.tsx
@@ -5,40 +5,47 @@ import { Droppable } from "@hello-pangea/dnd";
import { useMobxStore } from "lib/mobx/store-provider";
// components
import { KanBanGroupByHeaderRoot } from "./headers/group-by-root";
-import { KanbanIssueBlocksList, BoardInlineCreateIssueForm } from "components/issues";
+import { KanbanIssueBlocksList, KanBanQuickAddIssueForm } from "components/issues";
// types
-import { IIssueDisplayProperties, IIssue } from "types";
+import { IIssueDisplayProperties, IIssue, IState } from "types";
// constants
import { getValueFromObject } from "constants/issue";
import { replaceUnderscoreIfSnakeCase } from "helpers/string.helper";
+import { EIssueActions } from "../types";
+import { IIssueResponse, IGroupedIssues, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
export interface IGroupByKanBan {
- issues: any;
+ issues: IIssueResponse;
+ issueIds: any;
sub_group_by: string | null;
group_by: string | null;
order_by: string | null;
sub_group_id: string;
list: any;
listKey: string;
+ states: IState[] | null;
isDragDisabled: boolean;
- handleIssues: (
- sub_group_by: string | null,
- group_by: string | null,
- issue: IIssue,
- action: "update" | "delete"
- ) => void;
+ handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
showEmptyGroup: boolean;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
- displayProperties: IIssueDisplayProperties;
+ displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any;
handleKanBanToggle: any;
enableQuickIssueCreate?: boolean;
isDragStarted?: boolean;
+ quickAddCallback?: (
+ workspaceSlug: string,
+ projectId: string,
+ data: IIssue,
+ viewId?: string
+ ) => Promise;
+ viewId?: string;
}
const GroupByKanBan: React.FC = observer((props) => {
const {
issues,
+ issueIds,
sub_group_by,
group_by,
order_by,
@@ -54,6 +61,8 @@ const GroupByKanBan: React.FC = observer((props) => {
handleKanBanToggle,
enableQuickIssueCreate,
isDragStarted,
+ quickAddCallback,
+ viewId,
} = props;
const verticalAlignPosition = (_list: any) =>
@@ -74,7 +83,7 @@ const GroupByKanBan: React.FC = observer((props) => {
column_value={_list}
sub_group_by={sub_group_by}
group_by={group_by}
- issues_count={issues?.[getValueFromObject(_list, listKey) as string]?.length || 0}
+ issues_count={issueIds?.[getValueFromObject(_list, listKey) as string]?.length || 0}
kanBanToggle={kanBanToggle}
handleKanBanToggle={handleKanBanToggle}
/>
@@ -102,7 +111,8 @@ const GroupByKanBan: React.FC = observer((props) => {
= observer((props) => {
{enableQuickIssueCreate && (
-
)}
@@ -152,19 +165,15 @@ const GroupByKanBan: React.FC = observer((props) => {
});
export interface IKanBan {
- issues: any;
+ issues: IIssueResponse;
+ issueIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues;
sub_group_by: string | null;
group_by: string | null;
order_by: string | null;
sub_group_id?: string;
- handleIssues: (
- sub_group_by: string | null,
- group_by: string | null,
- issue: IIssue,
- action: "update" | "delete"
- ) => void;
+ handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => void;
quickActions: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => React.ReactNode;
- displayProperties: IIssueDisplayProperties;
+ displayProperties: IIssueDisplayProperties | null;
kanBanToggle: any;
handleKanBanToggle: any;
showEmptyGroup: boolean;
@@ -176,11 +185,19 @@ export interface IKanBan {
projects: any;
enableQuickIssueCreate?: boolean;
isDragStarted?: boolean;
+ quickAddCallback?: (
+ workspaceSlug: string,
+ projectId: string,
+ data: IIssue,
+ viewId?: string
+ ) => Promise;
+ viewId?: string;
}
export const KanBan: React.FC = observer((props) => {
const {
issues,
+ issueIds,
sub_group_by,
group_by,
order_by,
@@ -199,6 +216,8 @@ export const KanBan: React.FC = observer((props) => {
projects,
enableQuickIssueCreate,
isDragStarted,
+ quickAddCallback,
+ viewId,
} = props;
const { issueKanBanView: issueKanBanViewStore } = useMobxStore();
@@ -208,12 +227,14 @@ export const KanBan: React.FC = observer((props) => {
{group_by && group_by === "project" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "state" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "state_detail.group" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "priority" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "labels" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "assignees" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
{group_by && group_by === "created_by" && (
= observer((props) => {
handleKanBanToggle={handleKanBanToggle}
enableQuickIssueCreate={enableQuickIssueCreate}
isDragStarted={isDragStarted}
+ quickAddCallback={quickAddCallback}
+ viewId={viewId}
/>
)}
diff --git a/web/components/issues/issue-layouts/kanban/index.ts b/web/components/issues/issue-layouts/kanban/index.ts
index 761f32a77..d0a391163 100644
--- a/web/components/issues/issue-layouts/kanban/index.ts
+++ b/web/components/issues/issue-layouts/kanban/index.ts
@@ -1,4 +1,4 @@
export * from "./block";
export * from "./roots";
export * from "./blocks-list";
-export * from "./inline-create-issue-form";
+export * from "./quick-add-issue-form";
diff --git a/web/components/issues/issue-layouts/kanban/properties.tsx b/web/components/issues/issue-layouts/kanban/properties.tsx
index a17947550..a0285cb9d 100644
--- a/web/components/issues/issue-layouts/kanban/properties.tsx
+++ b/web/components/issues/issue-layouts/kanban/properties.tsx
@@ -17,7 +17,7 @@ export interface IKanBanProperties {
columnId: string;
issue: IIssue;
handleIssues: (sub_group_by: string | null, group_by: string | null, issue: IIssue) => void;
- displayProperties: IIssueDisplayProperties;
+ displayProperties: IIssueDisplayProperties | null;
showEmptyGroup: boolean;
}
@@ -87,7 +87,7 @@ export const KanBanProperties: React.FC
= observer((props) =>
{displayProperties && displayProperties?.state && (
= observer((props) =>
)}
{/* label */}
- {displayProperties && displayProperties?.labels && (showEmptyGroup || issue?.labels.length > 0) && (
+ {displayProperties && displayProperties?.labels && (
= observer((props) =>
)}
{/* start date */}
- {displayProperties && displayProperties?.start_date && (showEmptyGroup || issue?.start_date) && (
+ {displayProperties && displayProperties?.start_date && (
handleStartDate(date)}
@@ -126,7 +126,7 @@ export const KanBanProperties: React.FC = observer((props) =>
)}
{/* target/due date */}
- {displayProperties && displayProperties?.due_date && (showEmptyGroup || issue?.target_date) && (
+ {displayProperties && displayProperties?.due_date && (
handleTargetDate(date)}
@@ -135,6 +135,18 @@ export const KanBanProperties: React.FC = observer((props) =>
/>
)}
+ {/* assignee */}
+ {displayProperties && displayProperties?.assignee && (
+
+ )}
+
{/* estimates */}
{displayProperties && displayProperties?.estimate && (
= observer((props) =>
)}
-
- {/* assignee */}
- {displayProperties && displayProperties?.assignee && (showEmptyGroup || issue?.assignees.length > 0) && (
-