From 2796754e5fc476213b50239cc4275736bb744516 Mon Sep 17 00:00:00 2001 From: rahulramesha <71900764+rahulramesha@users.noreply.github.com> Date: Wed, 29 Nov 2023 19:58:27 +0530 Subject: [PATCH] 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 --- .../inbox/modals/create-issue-modal.tsx | 144 +++++++++++-- .../issue-layouts/kanban/base-kanban-root.tsx | 18 +- .../issues/issue-layouts/kanban/block.tsx | 6 +- .../issue-layouts/kanban/roots/cycle-root.tsx | 5 +- .../kanban/roots/module-root.tsx | 27 +-- .../kanban/roots/profile-issues-root.tsx | 2 +- .../kanban/roots/project-root.tsx | 5 +- .../kanban/roots/project-view-root.tsx | 5 +- .../issue-layouts/list/base-list-root.tsx | 6 +- .../list/roots/profile-issues-root.tsx | 2 +- .../issue-layouts/roots/cycle-layout-root.tsx | 2 +- .../roots/module-layout-root.tsx | 2 +- .../roots/project-layout-root.tsx | 2 +- .../spreadsheet/base-spreadsheet-root.tsx | 5 +- web/store/inbox/inbox_filters.store.ts | 5 +- .../issues/base-issue-kanban-helper.store.ts | 194 ++++++++++-------- web/store/issues/profile/issue.store.ts | 7 +- 17 files changed, 290 insertions(+), 147 deletions(-) diff --git a/web/components/inbox/modals/create-issue-modal.tsx b/web/components/inbox/modals/create-issue-modal.tsx index 3a0746da6..0ab6b3d85 100644 --- a/web/components/inbox/modals/create-issue-modal.tsx +++ b/web/components/inbox/modals/create-issue-modal.tsx @@ -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()}> diff --git a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx index 2b4c12aef..aa3ba503e 100644 --- a/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx +++ b/web/components/issues/issue-layouts/kanban/base-kanban-root.tsx @@ -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); diff --git a/web/components/issues/issue-layouts/kanban/block.tsx b/web/components/issues/issue-layouts/kanban/block.tsx index 571982426..316d88144 100644 --- a/web/components/issues/issue-layouts/kanban/block.tsx +++ b/web/components/issues/issue-layouts/kanban/block.tsx @@ -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" diff --git a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx index 7fead980c..6bc0db584 100644 --- a/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/cycle-root.tsx @@ -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( diff --git a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx index 257378e78..20130269c 100644 --- a/web/components/issues/issue-layouts/kanban/roots/module-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/module-root.tsx @@ -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( diff --git a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx index 6b8c9c2d0..affbab2d8 100644 --- a/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/profile-issues-root.tsx @@ -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); }, }; diff --git a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx index bc085b37c..aa17eedaa 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-root.tsx @@ -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( diff --git a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx index 3ad8b65cf..1315d284a 100644 --- a/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx +++ b/web/components/issues/issue-layouts/kanban/roots/project-view-root.tsx @@ -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( diff --git a/web/components/issues/issue-layouts/list/base-list-root.tsx b/web/components/issues/issue-layouts/list/base-list-root.tsx index 4cde57e7a..0b20a0ffe 100644 --- a/web/components/issues/issue-layouts/list/base-list-root.tsx +++ b/web/components/issues/issue-layouts/list/base-list-root.tsx @@ -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} diff --git a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx index 0a1e9ff6f..933fef331 100644 --- a/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx +++ b/web/components/issues/issue-layouts/list/roots/profile-issues-root.tsx @@ -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); }, }; diff --git a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx index 76fcb0a95..39d4c01e4 100644 --- a/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/cycle-layout-root.tsx @@ -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"> diff --git a/web/components/issues/issue-layouts/roots/module-layout-root.tsx b/web/components/issues/issue-layouts/roots/module-layout-root.tsx index 54fe84309..4d5db8dfb 100644 --- a/web/components/issues/issue-layouts/roots/module-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/module-layout-root.tsx @@ -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"> diff --git a/web/components/issues/issue-layouts/roots/project-layout-root.tsx b/web/components/issues/issue-layouts/roots/project-layout-root.tsx index c19b45fb7..704e69db0 100644 --- a/web/components/issues/issue-layouts/roots/project-layout-root.tsx +++ b/web/components/issues/issue-layouts/roots/project-layout-root.tsx @@ -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"> diff --git a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx index 36d2e05a9..db0aa37e1 100644 --- a/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx +++ b/web/components/issues/issue-layouts/spreadsheet/base-spreadsheet-root.tsx @@ -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 diff --git a/web/store/inbox/inbox_filters.store.ts b/web/store/inbox/inbox_filters.store.ts index 8a7c7ff37..c5758ad9d 100644 --- a/web/store/inbox/inbox_filters.store.ts +++ b/web/store/inbox/inbox_filters.store.ts @@ -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; diff --git a/web/store/issues/base-issue-kanban-helper.store.ts b/web/store/issues/base-issue-kanban-helper.store.ts index 6ab32bbc5..62b25fe22 100644 --- a/web/store/issues/base-issue-kanban-helper.store.ts +++ b/web/store/issues/base-issue-kanban-helper.store.ts @@ -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); + } } }; } diff --git a/web/store/issues/profile/issue.store.ts b/web/store/issues/profile/issue.store.ts index 727f7f623..96f3976e0 100644 --- a/web/store/issues/profile/issue.store.ts +++ b/web/store/issues/profile/issue.store.ts @@ -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 = {};