fix: v3 issues for the layouts (#2941)

* fix drag n drop exception error

* fix peek overlay close buttons

* fix project empty state view

* fix cycle and module empty state view

* add ai options to inbox issue creation

* fix inbox filters for viewers

* fix inbox filters for viewers for project

* disable editing permission for members and viewers

* define accurate types for drag and drop
This commit is contained in:
rahulramesha 2023-11-29 19:58:27 +05:30 committed by GitHub
parent 3488dc3014
commit 2796754e5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 290 additions and 147 deletions

View File

@ -16,6 +16,10 @@ import { Button, Input, ToggleSwitch } from "@plane/ui";
// types // types
import { IIssue } from "types"; import { IIssue } from "types";
import useEditorSuggestions from "hooks/use-editor-suggestions"; import useEditorSuggestions from "hooks/use-editor-suggestions";
import { GptAssistantModal } from "components/core";
import { Sparkle } from "lucide-react";
import useToast from "hooks/use-toast";
import { AIService } from "services/ai.service";
type Props = { type Props = {
isOpen: boolean; isOpen: boolean;
@ -31,6 +35,7 @@ const defaultValues: Partial<IIssue> = {
}; };
// services // services
const aiService = new AIService();
const fileService = new FileService(); const fileService = new FileService();
export const CreateInboxIssueModal: React.FC<Props> = observer((props) => { export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
@ -38,21 +43,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
// states // states
const [createMore, setCreateMore] = useState(false); const [createMore, setCreateMore] = useState(false);
const [gptAssistantModal, setGptAssistantModal] = useState(false);
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
const editorRef = useRef<any>(null); const editorRef = useRef<any>(null);
const { setToastAlert } = useToast();
const editorSuggestion = useEditorSuggestions(); const editorSuggestion = useEditorSuggestions();
const router = useRouter(); const router = useRouter();
const { workspaceSlug, projectId, inboxId } = router.query; const { workspaceSlug, projectId, inboxId } = router.query as {
workspaceSlug: string;
projectId: string;
inboxId: string;
};
const { inboxIssueDetails: inboxIssueDetailsStore, trackEvent: { postHogEventTracker } } = useMobxStore(); const {
inboxIssueDetails: inboxIssueDetailsStore,
trackEvent: { postHogEventTracker },
appConfig: { envConfig },
} = useMobxStore();
const { const {
control, control,
formState: { errors, isSubmitting }, formState: { errors, isSubmitting },
handleSubmit, handleSubmit,
reset, reset,
watch,
getValues,
setValue,
} = useForm({ defaultValues }); } = useForm({ defaultValues });
const handleClose = () => { const handleClose = () => {
@ -60,6 +79,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
reset(defaultValues); reset(defaultValues);
}; };
const issueName = watch("name");
const handleFormSubmit = async (formData: Partial<IIssue>) => { const handleFormSubmit = async (formData: Partial<IIssue>) => {
if (!workspaceSlug || !projectId || !inboxId) return; if (!workspaceSlug || !projectId || !inboxId) return;
@ -70,24 +91,66 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`); router.push(`/${workspaceSlug}/projects/${projectId}/inbox/${inboxId}?inboxIssueId=${res.issue_inbox[0].id}`);
handleClose(); handleClose();
} else reset(defaultValues); } else reset(defaultValues);
postHogEventTracker( postHogEventTracker("ISSUE_CREATE", {
"ISSUE_CREATE", ...res,
{ state: "SUCCESS",
...res, });
state: "SUCCESS" })
} .catch((error) => {
);
}).catch((error) => {
console.log(error); console.log(error);
postHogEventTracker( postHogEventTracker("ISSUE_CREATE", {
"ISSUE_CREATE", state: "FAILED",
{ });
state: "FAILED"
}
);
}); });
}; };
const handleAiAssistance = async (response: string) => {
if (!workspaceSlug || !projectId) return;
setValue("description", {});
setValue("description_html", `${watch("description_html")}<p>${response}</p>`);
editorRef.current?.setEditorValue(`${watch("description_html")}`);
};
const handleAutoGenerateDescription = async () => {
if (!workspaceSlug || !projectId || !issueName) return;
setIAmFeelingLucky(true);
aiService
.createGptTask(workspaceSlug as string, projectId as string, {
prompt: issueName,
task: "Generate a proper description for this issue.",
})
.then((res) => {
if (res.response === "")
setToastAlert({
type: "error",
title: "Error!",
message:
"Issue title isn't informative enough to generate the description. Please try with a different title.",
});
else handleAiAssistance(res.response_html);
})
.catch((err) => {
const error = err?.data?.error;
if (err.status === 429)
setToastAlert({
type: "error",
title: "Error!",
message: error || "You have reached the maximum number of requests of 50 requests per month per user.",
});
else
setToastAlert({
type: "error",
title: "Error!",
message: error || "Some error occurred. Please try again.",
});
})
.finally(() => setIAmFeelingLucky(false));
};
return ( return (
<Transition.Root show={isOpen} as={React.Fragment}> <Transition.Root show={isOpen} as={React.Fragment}>
<Dialog as="div" className="relative z-20" onClose={onClose}> <Dialog as="div" className="relative z-20" onClose={onClose}>
@ -146,7 +209,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
)} )}
/> />
</div> </div>
<div> <div className="relative">
<div className="flex justify-end">
{issueName && issueName !== "" && (
<button
type="button"
className={`flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90 ${
iAmFeelingLucky ? "cursor-wait" : ""
}`}
onClick={handleAutoGenerateDescription}
disabled={iAmFeelingLucky}
>
{iAmFeelingLucky ? (
"Generating response..."
) : (
<>
<Sparkle className="h-4 w-4" />I{"'"}m feeling lucky
</>
)}
</button>
)}
<button
type="button"
className="flex items-center gap-1 rounded px-1.5 py-1 text-xs hover:bg-custom-background-90"
onClick={() => setGptAssistantModal((prevData) => !prevData)}
>
<Sparkle className="h-4 w-4" />
AI
</button>
</div>
<Controller <Controller
name="description_html" name="description_html"
control={control} control={control}
@ -168,6 +259,23 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
/> />
)} )}
/> />
{envConfig?.has_openai_configured && (
<GptAssistantModal
isOpen={gptAssistantModal}
handleClose={() => {
setGptAssistantModal(false);
// this is done so that the title do not reset after gpt popover closed
reset(getValues());
}}
inset="top-2 left-0"
content=""
htmlContent={watch("description_html")}
onResponse={(response) => {
handleAiAssistance(response);
}}
projectId={projectId}
/>
)}
</div> </div>
<div className="flex flex-wrap items-center gap-2"> <div className="flex flex-wrap items-center gap-2">
@ -188,7 +296,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
onClick={() => setCreateMore((prevData) => !prevData)} onClick={() => setCreateMore((prevData) => !prevData)}
> >
<span className="text-xs">Create more</span> <span className="text-xs">Create more</span>
<ToggleSwitch value={createMore} onChange={() => { }} size="md" /> <ToggleSwitch value={createMore} onChange={() => {}} size="md" />
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant="neutral-primary" size="sm" onClick={() => handleClose()}> <Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>

View File

@ -1,6 +1,6 @@
import { FC, useCallback, useState } from "react"; import { FC, useCallback, useState } from "react";
import { DragDropContext, DropResult, Droppable } from "@hello-pangea/dnd";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { DragDropContext, Droppable } from "@hello-pangea/dnd";
import { observer } from "mobx-react-lite"; import { observer } from "mobx-react-lite";
// mobx store // mobx store
import { useMobxStore } from "lib/mobx/store-provider"; import { useMobxStore } from "lib/mobx/store-provider";
@ -89,8 +89,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
projectLabel: { projectLabels }, projectLabel: { projectLabels },
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
user: userStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
const issues = issueStore?.getIssues || {}; const issues = issueStore?.getIssues || {};
const issueIds = issueStore?.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];
@ -114,7 +118,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
setIsDragStarted(true); setIsDragStarted(true);
}; };
const onDragEnd = (result: any) => { const onDragEnd = (result: DropResult) => {
setIsDragStarted(false); setIsDragStarted(false);
if (!result) return; if (!result) return;
@ -159,7 +163,11 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
<div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}> <div className={`relative min-w-full w-max min-h-full h-max bg-custom-background-90 px-3`}>
<DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}> <DragDropContext onDragStart={onDragStart} onDragEnd={onDragEnd}>
<div className={`fixed left-1/2 -translate-x-1/2 z-40 w-72 top-3 flex items-center justify-center mx-3`}> <div
className={`fixed left-1/2 -translate-x-1/2 ${
isDragStarted ? "z-40" : ""
} w-72 top-3 flex items-center justify-center mx-3`}
>
<Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}> <Droppable droppableId="issue-trash-box" isDropDisabled={!isDragStarted}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
@ -216,7 +224,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
quickAddCallback={issueStore?.quickAddIssue} quickAddCallback={issueStore?.quickAddIssue}
viewId={viewId} viewId={viewId}
disableIssueCreation={!enableIssueCreation} disableIssueCreation={!enableIssueCreation}
isReadOnly={!enableInlineEditing} isReadOnly={!enableInlineEditing || !isEditingAllowed}
currentStore={currentStore} currentStore={currentStore}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}
/> />
@ -257,7 +265,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
isDragStarted={isDragStarted} isDragStarted={isDragStarted}
disableIssueCreation={true} disableIssueCreation={true}
enableQuickIssueCreate={enableQuickAdd} enableQuickIssueCreate={enableQuickAdd}
isReadOnly={!enableInlineEditing} isReadOnly={!enableInlineEditing || !isEditingAllowed}
currentStore={currentStore} currentStore={currentStore}
addIssuesToView={(issues) => { addIssuesToView={(issues) => {
console.log("kanban existingIds", issues); console.log("kanban existingIds", issues);

View File

@ -50,9 +50,13 @@ export const KanbanIssueBlock: React.FC<IssueBlockProps> = (props) => {
}); });
}; };
let draggableId = issue.id;
if (columnId) draggableId = `${draggableId}__${columnId}`;
if (sub_group_id) draggableId = `${draggableId}__${sub_group_id}`;
return ( return (
<> <>
<Draggable draggableId={issue.id} index={index}> <Draggable draggableId={draggableId} index={index}>
{(provided, snapshot) => ( {(provided, snapshot) => (
<div <div
className="group/kanban-block relative p-1.5 hover:cursor-default" className="group/kanban-block relative p-1.5 hover:cursor-default"

View File

@ -11,6 +11,7 @@ import { EIssueActions } from "../../types";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
export interface ICycleKanBanLayout {} export interface ICycleKanBanLayout {}
@ -51,8 +52,8 @@ export const CycleKanBanLayout: React.FC = observer(() => {
destination: any, destination: any,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: IIssue[], issues: IIssueResponse | undefined,
issueWithIds: any issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => { ) => {
if (kanBanHelperStore.handleDragDrop) if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop( kanBanHelperStore.handleDragDrop(

View File

@ -11,6 +11,7 @@ import { IIssue } from "types";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
export interface IModuleKanBanLayout {} export interface IModuleKanBanLayout {}
@ -30,28 +31,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
kanBanHelpers: kanBanHelperStore, kanBanHelpers: kanBanHelperStore,
} = useMobxStore(); } = useMobxStore();
// const handleIssues = useCallback(
// (sub_group_by: string | null, group_by: string | null, issue: IIssue, action: EIssueActions) => {
// if (!workspaceSlug || !moduleId) return;
// if (action === "update") {
// moduleIssueStore.updateIssueStructure(group_by, sub_group_by, issue);
// issueDetailStore.updateIssue(workspaceSlug.toString(), issue.project, issue.id, issue);
// }
// if (action === "delete") moduleIssueStore.deleteIssue(group_by, sub_group_by, issue);
// if (action === "remove" && issue.bridge_id) {
// moduleIssueStore.deleteIssue(group_by, null, issue);
// moduleIssueStore.removeIssueFromModule(
// workspaceSlug.toString(),
// issue.project,
// moduleId.toString(),
// issue.bridge_id
// );
// }
// },
// [moduleIssueStore, issueDetailStore, moduleId, workspaceSlug]
// );
const issueActions = { const issueActions = {
[EIssueActions.UPDATE]: async (issue: IIssue) => { [EIssueActions.UPDATE]: async (issue: IIssue) => {
if (!workspaceSlug || !moduleId) return; if (!workspaceSlug || !moduleId) return;
@ -73,8 +52,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
destination: any, destination: any,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: IIssue[], issues: IIssueResponse | undefined,
issueWithIds: any issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => { ) => {
if (kanBanHelperStore.handleDragDrop) if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop( kanBanHelperStore.handleDragDrop(

View File

@ -30,7 +30,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
[EIssueActions.DELETE]: async (issue: IIssue) => { [EIssueActions.DELETE]: async (issue: IIssue) => {
if (!workspaceSlug || !userId) return; if (!workspaceSlug || !userId) return;
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id); await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
}, },
}; };

View File

@ -10,6 +10,7 @@ import { IIssue } from "types";
import { EIssueActions } from "../../types"; import { EIssueActions } from "../../types";
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
export interface IKanBanLayout {} export interface IKanBanLayout {}
@ -42,8 +43,8 @@ export const KanBanLayout: React.FC = observer(() => {
destination: any, destination: any,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: IIssue[], issues: IIssueResponse | undefined,
issueWithIds: any issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => { ) => {
if (kanBanHelperStore.handleDragDrop) if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop( kanBanHelperStore.handleDragDrop(

View File

@ -10,6 +10,7 @@ import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
// components // components
import { BaseKanBanRoot } from "../base-kanban-root"; import { BaseKanBanRoot } from "../base-kanban-root";
import { EProjectStore } from "store/command-palette.store"; import { EProjectStore } from "store/command-palette.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
export interface IViewKanBanLayout {} export interface IViewKanBanLayout {}
@ -42,8 +43,8 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
destination: any, destination: any,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: IIssue[], issues: IIssueResponse | undefined,
issueWithIds: any issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
) => { ) => {
if (kanBanHelperStore.handleDragDrop) if (kanBanHelperStore.handleDragDrop)
kanBanHelperStore.handleDragDrop( kanBanHelperStore.handleDragDrop(

View File

@ -79,8 +79,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
projectMember: { projectMembers }, projectMember: { projectMembers },
projectState: projectStateStore, projectState: projectStateStore,
projectLabel: { projectLabels }, projectLabel: { projectLabels },
user: userStore,
} = useMobxStore(); } = useMobxStore();
const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
const issueIds = issueStore?.getIssuesIds || []; const issueIds = issueStore?.getIssuesIds || [];
const issues = issueStore?.getIssues; const issues = issueStore?.getIssues;
@ -142,7 +146,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
viewId={viewId} viewId={viewId}
quickAddCallback={issueStore?.quickAddIssue} quickAddCallback={issueStore?.quickAddIssue}
enableIssueQuickAdd={!!enableQuickAdd} enableIssueQuickAdd={!!enableQuickAdd}
isReadonly={!enableInlineEditing} isReadonly={!enableInlineEditing || !isEditingAllowed}
disableIssueCreation={!enableIssueCreation} disableIssueCreation={!enableIssueCreation}
currentStore={currentStore} currentStore={currentStore}
addIssuesToView={addIssuesToView} addIssuesToView={addIssuesToView}

View File

@ -30,7 +30,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => { [EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
if (!workspaceSlug || !userId) return; if (!workspaceSlug || !userId) return;
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id); await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
}, },
}; };

View File

@ -68,7 +68,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{Object.keys(getIssues ?? {}).length == 0 ? ( {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} /> <CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
) : ( ) : (
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">

View File

@ -53,7 +53,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{Object.keys(getIssues ?? {}).length == 0 ? ( {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} /> <ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
) : ( ) : (
<div className="h-full w-full overflow-auto"> <div className="h-full w-full overflow-auto">

View File

@ -53,7 +53,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
</div> </div>
) : ( ) : (
<> <>
{Object.keys(getIssues ?? {}).length == 0 ? ( {Object.keys(getIssues ?? {}).length == 0 && !loader ? (
<ProjectEmptyState /> <ProjectEmptyState />
) : ( ) : (
<div className="w-full h-full relative overflow-auto"> <div className="w-full h-full relative overflow-auto">

View File

@ -48,7 +48,8 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
user: userStore, user: userStore,
} = useMobxStore(); } = useMobxStore();
const user = userStore.currentUser; const { currentProjectRole } = userStore;
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
const issuesResponse = issueStore.getIssues; const issuesResponse = issueStore.getIssues;
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues; const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
@ -103,7 +104,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
labels={projectLabels || undefined} labels={projectLabels || undefined}
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined} states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
handleIssues={handleIssues} handleIssues={handleIssues}
disableUserActions={false} disableUserActions={!isEditingAllowed}
quickAddCallback={issueStore.quickAddIssue} quickAddCallback={issueStore.quickAddIssue}
viewId={viewId} viewId={viewId}
enableQuickCreateIssue enableQuickCreateIssue

View File

@ -132,7 +132,10 @@ export class InboxFiltersStore implements IInboxFiltersStore {
}; };
}); });
await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps }); const userRole = this.rootStore.user?.projectMemberInfo?.[projectId]?.role || 0;
if (userRole > 10) {
await this.inboxService.patchInbox(workspaceSlug, projectId, inboxId, { view_props: newViewProps });
}
} catch (error) { } catch (error) {
runInAction(() => { runInAction(() => {
this.error = error; this.error = error;

View File

@ -1,15 +1,30 @@
import { DraggableLocation } from "@hello-pangea/dnd";
import { IProjectIssuesStore } from "./project-issues/project/issue.store";
import { IModuleIssuesStore } from "./project-issues/module/issue.store";
import { ICycleIssuesStore } from "./project-issues/cycle/issue.store";
import { IViewIssuesStore } from "./project-issues/project-view/issue.store";
import { IProjectDraftIssuesStore } from "./project-issues/draft/issue.store";
import { IProfileIssuesStore } from "./profile/issue.store";
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "./types";
export interface IKanBanHelpers { export interface IKanBanHelpers {
// actions // actions
handleDragDrop: ( handleDragDrop: (
source: any, source: DraggableLocation | null,
destination: any, destination: DraggableLocation | null,
workspaceSlug: string, workspaceSlug: string,
projectId: string, projectId: string, // projectId for all views or user id in profile issues
store: any, store:
| IProjectIssuesStore
| IModuleIssuesStore
| ICycleIssuesStore
| IViewIssuesStore
| IProjectDraftIssuesStore
| IProfileIssuesStore,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: any, issues: IIssueResponse | undefined,
issueWithIds: any, issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
viewId?: string | null viewId?: string | null
) => void; ) => void;
} }
@ -53,108 +68,125 @@ export class KanBanHelpers implements IKanBanHelpers {
}; };
handleDragDrop = async ( handleDragDrop = async (
source: any, source: DraggableLocation | null,
destination: any, destination: DraggableLocation | null,
workspaceSlug: string, workspaceSlug: string,
projectId: string, // projectId for all views or user id in profile issues projectId: string, // projectId for all views or user id in profile issues
store: any, store:
| IProjectIssuesStore
| IModuleIssuesStore
| ICycleIssuesStore
| IViewIssuesStore
| IProjectDraftIssuesStore
| IProfileIssuesStore,
subGroupBy: string | null, subGroupBy: string | null,
groupBy: string | null, groupBy: string | null,
issues: any, issues: IIssueResponse | undefined,
issueWithIds: any, issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
viewId: string | null = null // it can be moduleId, cycleId viewId: string | null = null // it can be moduleId, cycleId
) => { ) => {
if (issues && issueWithIds) { if (!issues || !issueWithIds || !source || !destination) return;
let updateIssue: any = {};
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null; let updateIssue: any = {};
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
const sourceGroupByColumnId = sourceColumnId[0] || null; const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
const destinationGroupByColumnId = destinationColumnId[0] || null; const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
const sourceSubGroupByColumnId = sourceColumnId[1] || null; if (!sourceColumnId || !destinationColumnId) return;
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
if (!workspaceSlug || !projectId || !groupBy || !sourceGroupByColumnId || !destinationGroupByColumnId) return; const sourceGroupByColumnId = sourceColumnId[0] || null;
const destinationGroupByColumnId = destinationColumnId[0] || null;
if (destinationGroupByColumnId === "issue-trash-box") { const sourceSubGroupByColumnId = sourceColumnId[1] || null;
const sourceIssues = subGroupBy const destinationSubGroupByColumnId = destinationColumnId[1] || null;
? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
: issueWithIds[sourceGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1); if (
!workspaceSlug ||
!projectId ||
!groupBy ||
!sourceGroupByColumnId ||
!destinationGroupByColumnId ||
!sourceSubGroupByColumnId ||
!sourceGroupByColumnId
)
return;
console.log("removed", removed); if (destinationGroupByColumnId === "issue-trash-box") {
const sourceIssues: string[] = subGroupBy
? (issueWithIds as ISubGroupedIssues)[sourceSubGroupByColumnId][sourceGroupByColumnId]
: (issueWithIds as IGroupedIssues)[sourceGroupByColumnId];
if (removed) { const [removed] = sourceIssues.splice(source.index, 1);
if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
else store?.removeIssue(workspaceSlug, projectId, removed);
}
} else {
const sourceIssues = subGroupBy
? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
: issueWithIds[sourceGroupByColumnId];
const destinationIssues = subGroupBy
? issueWithIds[sourceSubGroupByColumnId][destinationGroupByColumnId]
: issueWithIds[destinationGroupByColumnId];
const [removed] = sourceIssues.splice(source.index, 1); console.log("removed", removed);
const removedIssueDetail = issues[removed];
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) { if (removed) {
updateIssue = { if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
id: removedIssueDetail?.id, else 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];
// for both horizontal and vertical dnd const [removed] = sourceIssues.splice(source.index, 1);
updateIssue = { const removedIssueDetail = issues[removed];
...updateIssue,
...this.handleSortOrder(destinationIssues, destination.index, issues),
};
if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) { if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
if (sourceGroupByColumnId != destinationGroupByColumnId) { updateIssue = {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId }; id: removedIssueDetail?.id,
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 // for both horizontal and vertical dnd
updateIssue = { updateIssue = {
...updateIssue, ...updateIssue,
...this.handleSortOrder(destinationIssues, destination.index, issues), ...this.handleSortOrder(destinationIssues, destination.index, issues),
}; };
// for horizontal dnd if (sourceSubGroupByColumnId === destinationSubGroupByColumnId) {
if (sourceColumnId != destinationColumnId) { if (sourceGroupByColumnId != destinationGroupByColumnId) {
if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId }; if (groupBy === "state") updateIssue = { ...updateIssue, state: destinationGroupByColumnId };
if (groupBy === "priority") updateIssue = { ...updateIssue, priority: 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,
};
if (updateIssue && updateIssue?.id) { // for both horizontal and vertical dnd
if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId); updateIssue = {
else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue); ...updateIssue,
...this.handleSortOrder(destinationIssues, destination.index, issues),
};
// 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) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
}
} }
}; };
} }

View File

@ -40,9 +40,9 @@ export interface IProfileIssuesStore {
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
removeIssue: ( removeIssue: (
workspaceSlug: string, workspaceSlug: string,
userId: string,
projectId: string, projectId: string,
issueId: string issueId: string,
userId?: string
) => Promise<IIssue | undefined>; ) => Promise<IIssue | undefined>;
quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise<IIssue | undefined>; quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise<IIssue | undefined>;
viewFlags: ViewFlags; viewFlags: ViewFlags;
@ -275,7 +275,8 @@ export class ProfileIssuesStore extends IssueBaseStore implements IProfileIssues
} }
}; };
removeIssue = async (workspaceSlug: string, userId: string, projectId: string, issueId: string) => { removeIssue = async (workspaceSlug: string, projectId: string, issueId: string, userId?: string) => {
if (!userId) return;
try { try {
let _issues = { ...this.issues }; let _issues = { ...this.issues };
if (!_issues) _issues = {}; if (!_issues) _issues = {};