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
|
// 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()}>
|
||||||
|
@ -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);
|
||||||
|
@ -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"
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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(
|
||||||
|
@ -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(
|
||||||
|
@ -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}
|
||||||
|
@ -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);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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">
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -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 = {};
|
||||||
|
Loading…
Reference in New Issue
Block a user