forked from github/plane
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:
parent
3488dc3014
commit
2796754e5f
@ -16,6 +16,10 @@ import { Button, Input, ToggleSwitch } from "@plane/ui";
|
||||
// types
|
||||
import { IIssue } from "types";
|
||||
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 = {
|
||||
isOpen: boolean;
|
||||
@ -31,6 +35,7 @@ const defaultValues: Partial<IIssue> = {
|
||||
};
|
||||
|
||||
// services
|
||||
const aiService = new AIService();
|
||||
const fileService = new FileService();
|
||||
|
||||
export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
@ -38,21 +43,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
|
||||
// states
|
||||
const [createMore, setCreateMore] = useState(false);
|
||||
const [gptAssistantModal, setGptAssistantModal] = useState(false);
|
||||
const [iAmFeelingLucky, setIAmFeelingLucky] = useState(false);
|
||||
|
||||
const editorRef = useRef<any>(null);
|
||||
|
||||
const { setToastAlert } = useToast();
|
||||
const editorSuggestion = useEditorSuggestions();
|
||||
|
||||
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 {
|
||||
control,
|
||||
formState: { errors, isSubmitting },
|
||||
handleSubmit,
|
||||
reset,
|
||||
watch,
|
||||
getValues,
|
||||
setValue,
|
||||
} = useForm({ defaultValues });
|
||||
|
||||
const handleClose = () => {
|
||||
@ -60,6 +79,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
reset(defaultValues);
|
||||
};
|
||||
|
||||
const issueName = watch("name");
|
||||
|
||||
const handleFormSubmit = async (formData: Partial<IIssue>) => {
|
||||
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}`);
|
||||
handleClose();
|
||||
} else reset(defaultValues);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
...res,
|
||||
state: "SUCCESS"
|
||||
}
|
||||
);
|
||||
}).catch((error) => {
|
||||
postHogEventTracker("ISSUE_CREATE", {
|
||||
...res,
|
||||
state: "SUCCESS",
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
postHogEventTracker(
|
||||
"ISSUE_CREATE",
|
||||
{
|
||||
state: "FAILED"
|
||||
}
|
||||
);
|
||||
postHogEventTracker("ISSUE_CREATE", {
|
||||
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 (
|
||||
<Transition.Root show={isOpen} as={React.Fragment}>
|
||||
<Dialog as="div" className="relative z-20" onClose={onClose}>
|
||||
@ -146,7 +209,35 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
)}
|
||||
/>
|
||||
</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
|
||||
name="description_html"
|
||||
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 className="flex flex-wrap items-center gap-2">
|
||||
@ -188,7 +296,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
|
||||
onClick={() => setCreateMore((prevData) => !prevData)}
|
||||
>
|
||||
<span className="text-xs">Create more</span>
|
||||
<ToggleSwitch value={createMore} onChange={() => { }} size="md" />
|
||||
<ToggleSwitch value={createMore} onChange={() => {}} size="md" />
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<Button variant="neutral-primary" size="sm" onClick={() => handleClose()}>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { FC, useCallback, useState } from "react";
|
||||
import { DragDropContext, DropResult, Droppable } from "@hello-pangea/dnd";
|
||||
import { useRouter } from "next/router";
|
||||
import { DragDropContext, Droppable } from "@hello-pangea/dnd";
|
||||
import { observer } from "mobx-react-lite";
|
||||
// mobx store
|
||||
import { useMobxStore } from "lib/mobx/store-provider";
|
||||
@ -89,8 +89,12 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
projectLabel: { projectLabels },
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
|
||||
|
||||
const issues = issueStore?.getIssues || {};
|
||||
const issueIds = issueStore?.getIssuesIds || [];
|
||||
|
||||
@ -114,7 +118,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
setIsDragStarted(true);
|
||||
};
|
||||
|
||||
const onDragEnd = (result: any) => {
|
||||
const onDragEnd = (result: DropResult) => {
|
||||
setIsDragStarted(false);
|
||||
|
||||
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`}>
|
||||
<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}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
@ -216,7 +224,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
viewId={viewId}
|
||||
disableIssueCreation={!enableIssueCreation}
|
||||
isReadOnly={!enableInlineEditing}
|
||||
isReadOnly={!enableInlineEditing || !isEditingAllowed}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
/>
|
||||
@ -257,7 +265,7 @@ export const BaseKanBanRoot: React.FC<IBaseKanBanLayout> = observer((props: IBas
|
||||
isDragStarted={isDragStarted}
|
||||
disableIssueCreation={true}
|
||||
enableQuickIssueCreate={enableQuickAdd}
|
||||
isReadOnly={!enableInlineEditing}
|
||||
isReadOnly={!enableInlineEditing || !isEditingAllowed}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={(issues) => {
|
||||
console.log("kanban existingIds", issues);
|
||||
|
@ -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 (
|
||||
<>
|
||||
<Draggable draggableId={issue.id} index={index}>
|
||||
<Draggable draggableId={draggableId} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
<div
|
||||
className="group/kanban-block relative p-1.5 hover:cursor-default"
|
||||
|
@ -11,6 +11,7 @@ import { EIssueActions } from "../../types";
|
||||
// components
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
|
||||
export interface ICycleKanBanLayout {}
|
||||
|
||||
@ -51,8 +52,8 @@ export const CycleKanBanLayout: React.FC = observer(() => {
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssue[],
|
||||
issueWithIds: any
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (kanBanHelperStore.handleDragDrop)
|
||||
kanBanHelperStore.handleDragDrop(
|
||||
|
@ -11,6 +11,7 @@ import { IIssue } from "types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
|
||||
export interface IModuleKanBanLayout {}
|
||||
|
||||
@ -30,28 +31,6 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
kanBanHelpers: kanBanHelperStore,
|
||||
} = 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 = {
|
||||
[EIssueActions.UPDATE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !moduleId) return;
|
||||
@ -73,8 +52,8 @@ export const ModuleKanBanLayout: React.FC = observer(() => {
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssue[],
|
||||
issueWithIds: any
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (kanBanHelperStore.handleDragDrop)
|
||||
kanBanHelperStore.handleDragDrop(
|
||||
|
@ -30,7 +30,7 @@ export const ProfileIssuesKanBanLayout: React.FC = observer(() => {
|
||||
[EIssueActions.DELETE]: async (issue: IIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
|
||||
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
|
||||
await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -10,6 +10,7 @@ import { IIssue } from "types";
|
||||
import { EIssueActions } from "../../types";
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
|
||||
export interface IKanBanLayout {}
|
||||
|
||||
@ -42,8 +43,8 @@ export const KanBanLayout: React.FC = observer(() => {
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssue[],
|
||||
issueWithIds: any
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (kanBanHelperStore.handleDragDrop)
|
||||
kanBanHelperStore.handleDragDrop(
|
||||
|
@ -10,6 +10,7 @@ import { ProjectIssueQuickActions } from "../../quick-action-dropdowns";
|
||||
// components
|
||||
import { BaseKanBanRoot } from "../base-kanban-root";
|
||||
import { EProjectStore } from "store/command-palette.store";
|
||||
import { IGroupedIssues, IIssueResponse, ISubGroupedIssues, TUnGroupedIssues } from "store/issues/types";
|
||||
|
||||
export interface IViewKanBanLayout {}
|
||||
|
||||
@ -42,8 +43,8 @@ export const ProjectViewKanBanLayout: React.FC = observer(() => {
|
||||
destination: any,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: IIssue[],
|
||||
issueWithIds: any
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined
|
||||
) => {
|
||||
if (kanBanHelperStore.handleDragDrop)
|
||||
kanBanHelperStore.handleDragDrop(
|
||||
|
@ -79,8 +79,12 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
projectMember: { projectMembers },
|
||||
projectState: projectStateStore,
|
||||
projectLabel: { projectLabels },
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const { currentProjectRole } = userStore;
|
||||
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
|
||||
|
||||
const issueIds = issueStore?.getIssuesIds || [];
|
||||
const issues = issueStore?.getIssues;
|
||||
|
||||
@ -142,7 +146,7 @@ export const BaseListRoot = observer((props: IBaseListRoot) => {
|
||||
viewId={viewId}
|
||||
quickAddCallback={issueStore?.quickAddIssue}
|
||||
enableIssueQuickAdd={!!enableQuickAdd}
|
||||
isReadonly={!enableInlineEditing}
|
||||
isReadonly={!enableInlineEditing || !isEditingAllowed}
|
||||
disableIssueCreation={!enableIssueCreation}
|
||||
currentStore={currentStore}
|
||||
addIssuesToView={addIssuesToView}
|
||||
|
@ -30,7 +30,7 @@ export const ProfileIssuesListLayout: FC = observer(() => {
|
||||
[EIssueActions.DELETE]: async (group_by: string | null, issue: IIssue) => {
|
||||
if (!workspaceSlug || !userId) return;
|
||||
|
||||
await profileIssuesStore.removeIssue(workspaceSlug, userId, issue.project, issue.id);
|
||||
await profileIssuesStore.removeIssue(workspaceSlug, issue.project, issue.id, userId);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -68,7 +68,7 @@ export const CycleLayoutRoot: React.FC = observer(() => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||
{Object.keys(getIssues ?? {}).length == 0 && !loader ? (
|
||||
<CycleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} cycleId={cycleId} />
|
||||
) : (
|
||||
<div className="h-full w-full overflow-auto">
|
||||
|
@ -53,7 +53,7 @@ export const ModuleLayoutRoot: React.FC = observer(() => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||
{Object.keys(getIssues ?? {}).length == 0 && !loader ? (
|
||||
<ModuleEmptyState workspaceSlug={workspaceSlug} projectId={projectId} moduleId={moduleId} />
|
||||
) : (
|
||||
<div className="h-full w-full overflow-auto">
|
||||
|
@ -53,7 +53,7 @@ export const ProjectLayoutRoot: React.FC = observer(() => {
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{Object.keys(getIssues ?? {}).length == 0 ? (
|
||||
{Object.keys(getIssues ?? {}).length == 0 && !loader ? (
|
||||
<ProjectEmptyState />
|
||||
) : (
|
||||
<div className="w-full h-full relative overflow-auto">
|
||||
|
@ -48,7 +48,8 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
user: userStore,
|
||||
} = useMobxStore();
|
||||
|
||||
const user = userStore.currentUser;
|
||||
const { currentProjectRole } = userStore;
|
||||
const isEditingAllowed = [15, 20].includes(currentProjectRole || 0);
|
||||
|
||||
const issuesResponse = issueStore.getIssues;
|
||||
const issueIds = (issueStore.getIssuesIds ?? []) as TUnGroupedIssues;
|
||||
@ -103,7 +104,7 @@ export const BaseSpreadsheetRoot = observer((props: IBaseSpreadsheetRoot) => {
|
||||
labels={projectLabels || undefined}
|
||||
states={projectId ? projectStateStore.states?.[projectId.toString()] : undefined}
|
||||
handleIssues={handleIssues}
|
||||
disableUserActions={false}
|
||||
disableUserActions={!isEditingAllowed}
|
||||
quickAddCallback={issueStore.quickAddIssue}
|
||||
viewId={viewId}
|
||||
enableQuickCreateIssue
|
||||
|
@ -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) {
|
||||
runInAction(() => {
|
||||
this.error = error;
|
||||
|
@ -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 {
|
||||
// actions
|
||||
handleDragDrop: (
|
||||
source: any,
|
||||
destination: any,
|
||||
source: DraggableLocation | null,
|
||||
destination: DraggableLocation | null,
|
||||
workspaceSlug: string,
|
||||
projectId: string,
|
||||
store: any,
|
||||
projectId: string, // projectId for all views or user id in profile issues
|
||||
store:
|
||||
| IProjectIssuesStore
|
||||
| IModuleIssuesStore
|
||||
| ICycleIssuesStore
|
||||
| IViewIssuesStore
|
||||
| IProjectDraftIssuesStore
|
||||
| IProfileIssuesStore,
|
||||
subGroupBy: string | null,
|
||||
groupBy: string | null,
|
||||
issues: any,
|
||||
issueWithIds: any,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
|
||||
viewId?: string | null
|
||||
) => void;
|
||||
}
|
||||
@ -53,108 +68,125 @@ export class KanBanHelpers implements IKanBanHelpers {
|
||||
};
|
||||
|
||||
handleDragDrop = async (
|
||||
source: any,
|
||||
destination: any,
|
||||
source: DraggableLocation | null,
|
||||
destination: DraggableLocation | null,
|
||||
workspaceSlug: 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,
|
||||
groupBy: string | null,
|
||||
issues: any,
|
||||
issueWithIds: any,
|
||||
issues: IIssueResponse | undefined,
|
||||
issueWithIds: IGroupedIssues | ISubGroupedIssues | TUnGroupedIssues | undefined,
|
||||
viewId: string | null = null // it can be moduleId, cycleId
|
||||
) => {
|
||||
if (issues && issueWithIds) {
|
||||
let updateIssue: any = {};
|
||||
if (!issues || !issueWithIds || !source || !destination) return;
|
||||
|
||||
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
|
||||
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
|
||||
let updateIssue: any = {};
|
||||
|
||||
const sourceGroupByColumnId = sourceColumnId[0] || null;
|
||||
const destinationGroupByColumnId = destinationColumnId[0] || null;
|
||||
const sourceColumnId = (source?.droppableId && source?.droppableId.split("__")) || null;
|
||||
const destinationColumnId = (destination?.droppableId && destination?.droppableId.split("__")) || null;
|
||||
|
||||
const sourceSubGroupByColumnId = sourceColumnId[1] || null;
|
||||
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
|
||||
if (!sourceColumnId || !destinationColumnId) return;
|
||||
|
||||
if (!workspaceSlug || !projectId || !groupBy || !sourceGroupByColumnId || !destinationGroupByColumnId) return;
|
||||
const sourceGroupByColumnId = sourceColumnId[0] || null;
|
||||
const destinationGroupByColumnId = destinationColumnId[0] || null;
|
||||
|
||||
if (destinationGroupByColumnId === "issue-trash-box") {
|
||||
const sourceIssues = subGroupBy
|
||||
? issueWithIds[sourceSubGroupByColumnId][sourceGroupByColumnId]
|
||||
: issueWithIds[sourceGroupByColumnId];
|
||||
const sourceSubGroupByColumnId = sourceColumnId[1] || null;
|
||||
const destinationSubGroupByColumnId = destinationColumnId[1] || null;
|
||||
|
||||
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) {
|
||||
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);
|
||||
|
||||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
const removedIssueDetail = issues[removed];
|
||||
console.log("removed", removed);
|
||||
|
||||
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
if (removed) {
|
||||
if (viewId) store?.removeIssue(workspaceSlug, projectId, removed, viewId);
|
||||
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
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...this.handleSortOrder(destinationIssues, destination.index, issues),
|
||||
};
|
||||
const [removed] = sourceIssues.splice(source.index, 1);
|
||||
const removedIssueDetail = issues[removed];
|
||||
|
||||
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,
|
||||
};
|
||||
if (subGroupBy && sourceSubGroupByColumnId && destinationSubGroupByColumnId) {
|
||||
updateIssue = {
|
||||
id: removedIssueDetail?.id,
|
||||
};
|
||||
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...this.handleSortOrder(destinationIssues, destination.index, issues),
|
||||
};
|
||||
// for both horizontal and vertical dnd
|
||||
updateIssue = {
|
||||
...updateIssue,
|
||||
...this.handleSortOrder(destinationIssues, destination.index, issues),
|
||||
};
|
||||
|
||||
// for horizontal dnd
|
||||
if (sourceColumnId != destinationColumnId) {
|
||||
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,
|
||||
};
|
||||
|
||||
if (updateIssue && updateIssue?.id) {
|
||||
if (viewId) store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue, viewId);
|
||||
else store?.updateIssue(workspaceSlug, projectId, updateIssue.id, updateIssue);
|
||||
// for both horizontal and vertical dnd
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -40,9 +40,9 @@ export interface IProfileIssuesStore {
|
||||
) => Promise<IIssue | undefined>;
|
||||
removeIssue: (
|
||||
workspaceSlug: string,
|
||||
userId: string,
|
||||
projectId: string,
|
||||
issueId: string
|
||||
issueId: string,
|
||||
userId?: string
|
||||
) => Promise<IIssue | undefined>;
|
||||
quickAddIssue: (workspaceSlug: string, userId: string, data: IIssue) => Promise<IIssue | undefined>;
|
||||
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 {
|
||||
let _issues = { ...this.issues };
|
||||
if (!_issues) _issues = {};
|
||||
|
Loading…
Reference in New Issue
Block a user