[WEB-927, WEB-928] fix: inbox issue bug fixes and improvement (#4160)

* chore: inbox duplicate issue modal improvement

* chore: handled tab navigation in inbox issues and handled cross project inbox issues

* chore: fetch inbox issue activity once the issue is updated in inbox issue

* chore: disable duplicate inbox issue actions

* chore: duplicate issue mutation in the inbox issue

* chore: inbox create modal sidebar tab change updated

* chore: multiple date selection in the inbox issue filters

* chore: code refactor

* chore: removed project dependancy on the inbox store structure

---------

Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so>
This commit is contained in:
guru_sainath 2024-04-10 16:08:31 +05:30 committed by GitHub
parent d0cb00f28a
commit f45c2d12fd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 96 additions and 108 deletions

View File

@ -17,12 +17,12 @@ type Props = {
projectId: string;
issue: Partial<TIssue>;
issueOperations: TIssueOperations;
is_editable: boolean;
isEditable: boolean;
duplicateIssueDetails: TInboxDuplicateIssueDetails | undefined;
};
export const InboxIssueProperties: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issue, issueOperations, is_editable, duplicateIssueDetails } = props;
const { workspaceSlug, projectId, issue, issueOperations, isEditable, duplicateIssueDetails } = props;
const router = useRouter();
// store hooks
@ -35,7 +35,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
<div className="flex h-min w-full flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
<div className="h-min w-full overflow-y-auto px-5">
<h5 className="text-sm font-medium my-4">Properties</h5>
<div className={`divide-y-2 divide-custom-border-200 ${!is_editable ? "opacity-60" : ""}`}>
<div className={`divide-y-2 divide-custom-border-200 ${!isEditable ? "opacity-60" : ""}`}>
<div className="flex flex-col gap-3">
{/* State */}
<div className="flex items-center gap-2 h-8">
@ -50,7 +50,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
issue?.id && issueOperations.update(workspaceSlug, projectId, issue?.id, { state_id: val })
}
projectId={projectId?.toString() ?? ""}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="w-3/5 flex-grow group"
buttonContainerClassName="w-full text-left"
@ -71,7 +71,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
onChange={(val) =>
issue?.id && issueOperations.update(workspaceSlug, projectId, issue?.id, { assignee_ids: val })
}
disabled={!is_editable}
disabled={!isEditable}
projectId={projectId?.toString() ?? ""}
placeholder="Add assignees"
multiple
@ -99,7 +99,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
onChange={(val) =>
issue?.id && issueOperations.update(workspaceSlug, projectId, issue?.id, { priority: val })
}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="border-with-text"
className="w-3/5 flex-grow rounded px-2 hover:bg-custom-background-80"
buttonContainerClassName="w-full text-left"
@ -108,7 +108,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
</div>
</div>
</div>
<div className={`divide-y-2 divide-custom-border-200 mt-3 ${!is_editable ? "opacity-60" : ""}`}>
<div className={`divide-y-2 divide-custom-border-200 mt-3 ${!isEditable ? "opacity-60" : ""}`}>
<div className="flex flex-col gap-3">
{/* Due Date */}
<div className="flex items-center gap-2 h-8">
@ -126,7 +126,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
})
}
minDate={minDate ?? undefined}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="w-3/5 flex-grow group"
buttonContainerClassName="w-full text-left"
@ -147,7 +147,7 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issue?.id}
disabled={!is_editable}
disabled={!isEditable}
isInboxIssue
onLabelUpdate={(val: string[]) =>
issue?.id && issueOperations.update(workspaceSlug, projectId, issue?.id, { label_ids: val })

View File

@ -22,14 +22,14 @@ type Props = {
workspaceSlug: string;
projectId: string;
inboxIssue: IInboxIssueStore;
is_editable: boolean;
isEditable: boolean;
isSubmitting: "submitting" | "submitted" | "saved";
setIsSubmitting: Dispatch<SetStateAction<"submitting" | "submitted" | "saved">>;
};
export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
const router = useRouter();
const { workspaceSlug, projectId, inboxIssue, is_editable, isSubmitting, setIsSubmitting } = props;
const { workspaceSlug, projectId, inboxIssue, isEditable, isSubmitting, setIsSubmitting } = props;
// hooks
const { currentUser } = useUser();
const { setShowAlert } = useReloadConfirmations(isSubmitting === "submitting");
@ -126,7 +126,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
disabled={!isEditable}
value={issue.name}
/>
@ -136,7 +136,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
issueId={issue.id}
value={issueDescription}
initialValue={issueDescription}
disabled={!is_editable}
disabled={!isEditable}
issueOperations={issueOperations}
setIsSubmitting={(value) => setIsSubmitting(value)}
/>
@ -156,7 +156,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
projectId={projectId}
issue={issue}
issueOperations={issueOperations}
is_editable={is_editable}
isEditable={isEditable}
duplicateIssueDetails={inboxIssue?.duplicate_issue_detail}
/>

View File

@ -32,11 +32,11 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
{ revalidateOnFocus: false }
);
const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const isEditable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
if (!inboxIssue) return <></>;
const isIssueAcceptedOrDeclined = [-1, 1].includes(inboxIssue.status);
const isIssueDisabled = [-1, 1, 2].includes(inboxIssue.status);
return (
<>
@ -54,7 +54,7 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={projectId}
inboxIssue={inboxIssue}
is_editable={is_editable && !isIssueAcceptedOrDeclined}
isEditable={isEditable && !isIssueDisabled}
isSubmitting={isSubmitting}
setIsSubmitting={setIsSubmitting}
/>

View File

@ -38,12 +38,6 @@ export const FilterDate: FC<Props> = observer((props) => {
const handleFilterValue = (value: string): string[] => (filterValue?.includes(value) ? [] : uniq(concat(value)));
const handleCustomFilterValue = (value: string[]): string[] => {
const finalOptions: string[] = [...filterValue];
value.forEach((v) => (finalOptions?.includes(v) ? [] : finalOptions.push(v)));
return uniq(finalOptions);
};
const isCustomDateSelected = () => {
const isValidDateSelected = filterValue?.filter((f) => isDate(f.split(";")[0])) || [];
return isValidDateSelected.length > 0 ? true : false;
@ -64,7 +58,7 @@ export const FilterDate: FC<Props> = observer((props) => {
<DateFilterModal
handleClose={() => setIsDateFilterModalOpen(false)}
isOpen={isDateFilterModalOpen}
onSelect={(val) => handleInboxIssueFilters(filterKey, handleCustomFilterValue(val))}
onSelect={(val) => handleInboxIssueFilters(filterKey, val)}
title="Created date"
/>
)}

View File

@ -52,7 +52,7 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
const workspaceStore = useWorkspace();
const workspaceId = workspaceStore.getWorkspaceBySlug(workspaceSlug as string)?.id as string;
// store hooks
const { createInboxIssue } = useProjectInbox();
const { createInboxIssue, handleCurrentTab } = useProjectInbox();
const {
config: { envConfig },
} = useApplication();
@ -79,7 +79,8 @@ export const CreateInboxIssueModal: React.FC<Props> = observer((props) => {
await createInboxIssue(workspaceSlug.toString(), projectId.toString(), formData)
.then((res) => {
if (!createMore) {
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/?inboxIssueId=${res?.issue?.id}`);
router.push(`/${workspaceSlug}/projects/${projectId}/inbox/?currentTab=open&inboxIssueId=${res?.issue?.id}`);
handleCurrentTab("open");
handleClose();
} else {
reset(defaultValues);

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState } from "react";
import React, { useState } from "react";
import { useRouter } from "next/router";
import useSWR from "swr";
import { Search } from "lucide-react";
@ -7,7 +7,7 @@ import { Combobox, Dialog, Transition } from "@headlessui/react";
// icons
// components
// ui
import { Button, TOAST_TYPE, setToast } from "@plane/ui";
import { TOAST_TYPE, setToast } from "@plane/ui";
import { EmptyState } from "@/components/empty-state";
// services
// constants
@ -29,7 +29,6 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
const { isOpen, onClose, onSubmit, value } = props;
const [query, setQuery] = useState("");
const [selectedItem, setSelectedItem] = useState<string>("");
const router = useRouter();
const { workspaceSlug, projectId, issueId } = router.query;
@ -48,18 +47,11 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
: null
);
useEffect(() => {
if (!value) {
setSelectedItem("");
return;
} else setSelectedItem(value);
}, [value]);
const handleClose = () => {
onClose();
};
const handleSubmit = () => {
const handleSubmit = (selectedItem: string) => {
if (!selectedItem || selectedItem.length === 0)
return setToast({
title: "Error",
@ -99,12 +91,7 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
leaveTo="opacity-0 scale-95"
>
<Dialog.Panel className="relative mx-auto max-w-2xl transform rounded-lg bg-custom-background-100 shadow-custom-shadow-md transition-all">
<Combobox
value={selectedItem}
onChange={(value) => {
setSelectedItem(value);
}}
>
<Combobox value={value} onChange={handleSubmit}>
<div className="relative m-1">
<Search
className="pointer-events-none absolute left-4 top-3.5 h-5 w-5 text-custom-text-100 text-opacity-40"
@ -175,17 +162,6 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
)}
</Combobox.Options>
</Combobox>
{filteredIssues.length > 0 && (
<div className="flex items-center justify-end gap-2 p-3">
<Button variant="neutral-primary" size="sm" onClick={handleClose}>
Cancel
</Button>
<Button variant="primary" size="sm" onClick={handleSubmit}>
Mark as original
</Button>
</div>
)}
</Dialog.Panel>
</Transition.Child>
</div>

View File

@ -9,7 +9,7 @@ import { InboxIssueStatus } from "@/components/inbox";
import { cn } from "@/helpers/common.helper";
import { renderFormattedDate } from "@/helpers/date-time.helper";
// hooks
import { useLabel } from "@/hooks/store";
import { useLabel, useProjectInbox } from "@/hooks/store";
import { usePlatformOS } from "@/hooks/use-platform-os";
// store
import { IInboxIssueStore } from "@/store/inbox/inbox-issue.store";
@ -27,6 +27,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
const router = useRouter();
const { inboxIssueId } = router.query;
// store
const { currentTab } = useProjectInbox();
const { projectLabels } = useLabel();
const { isMobile } = usePlatformOS();
const issue = inboxIssue.issue;
@ -54,7 +55,7 @@ export const InboxIssueListItem: FC<InboxIssueListItemProps> = observer((props)
<Link
id={`inbox-issue-list-item-${issue.id}`}
key={`${projectId}_${issue.id}`}
href={`/${workspaceSlug}/projects/${projectId}/inbox?inboxIssueId=${issue.id}`}
href={`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}&inboxIssueId=${issue.id}`}
onClick={(e) => handleIssueRedirection(e, issue.id)}
>
<div

View File

@ -75,7 +75,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
)}
onClick={() => {
if (currentTab != option?.key) handleCurrentTab(option?.key);
router.push(`/${workspaceSlug}/projects/${projectId}/inbox`);
router.push(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${option?.key}`);
}}
>
<div>{option?.label}</div>

View File

@ -21,11 +21,11 @@ type Props = {
projectId: string;
issueId: string;
issueOperations: TIssueOperations;
is_editable: boolean;
isEditable: boolean;
};
export const IssueMainContent: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId, issueOperations, is_editable } = props;
const { workspaceSlug, projectId, issueId, issueOperations, isEditable } = props;
// states
const [isSubmitting, setIsSubmitting] = useState<"submitting" | "submitted" | "saved">("saved");
// hooks
@ -90,7 +90,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
isSubmitting={isSubmitting}
setIsSubmitting={(value) => setIsSubmitting(value)}
issueOperations={issueOperations}
disabled={!is_editable}
disabled={!isEditable}
value={issue.name}
/>
@ -100,7 +100,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
issueId={issue.id}
value={issueDescription}
initialValue={issueDescription}
disabled={!is_editable}
disabled={!isEditable}
issueOperations={issueOperations}
setIsSubmitting={(value) => setIsSubmitting(value)}
/>
@ -120,7 +120,7 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
projectId={projectId}
parentIssueId={issueId}
currentUser={currentUser}
disabled={!is_editable}
disabled={!isEditable}
/>
)}
</div>
@ -129,10 +129,10 @@ export const IssueMainContent: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
disabled={!is_editable}
disabled={!isEditable}
/>
<IssueActivity workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={!is_editable} />
<IssueActivity workspaceSlug={workspaceSlug} projectId={projectId} issueId={issueId} disabled={!isEditable} />
</>
);
});

View File

@ -340,7 +340,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
// issue details
const issue = getIssueById(issueId);
// checking if issue is editable, based on user role
const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const isEditable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
return (
<>
@ -362,7 +362,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
is_editable={!is_archived && is_editable}
isEditable={!is_archived && isEditable}
/>
</div>
<div
@ -375,7 +375,7 @@ export const IssueDetailRoot: FC<TIssueDetailRoot> = observer((props) => {
issueId={issueId}
issueOperations={issueOperations}
is_archived={is_archived}
is_editable={!is_archived && is_editable}
isEditable={!is_archived && isEditable}
/>
</div>
</div>

View File

@ -69,11 +69,11 @@ type Props = {
issueId: string;
issueOperations: TIssueOperations;
is_archived: boolean;
is_editable: boolean;
isEditable: boolean;
};
export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const { workspaceSlug, projectId, issueId, issueOperations, is_archived, is_editable } = props;
const { workspaceSlug, projectId, issueId, issueOperations, is_archived, isEditable } = props;
// states
const [deleteIssueModal, setDeleteIssueModal] = useState(false);
const [archiveIssueModal, setArchiveIssueModal] = useState(false);
@ -116,7 +116,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
const projectDetails = getProjectById(issue.project_id);
const stateDetails = getStateById(issue.state_id);
// auth
const isArchivingAllowed = !is_archived && issueOperations.archive && is_editable;
const isArchivingAllowed = !is_archived && issueOperations.archive && isEditable;
const isInArchivableGroup =
!!stateDetails && [STATE_GROUPS.completed.key, STATE_GROUPS.cancelled.key].includes(stateDetails?.group);
@ -179,7 +179,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
</button>
</Tooltip>
)}
{is_editable && (
{isEditable && (
<Tooltip tooltipContent="Delete" isMobile={isMobile}>
<button
type="button"
@ -197,7 +197,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<div className="h-full w-full overflow-y-auto px-6">
<h5 className="mt-6 text-sm font-medium">Properties</h5>
{/* TODO: render properties using a common component */}
<div className={`mb-2 mt-3 space-y-2.5 ${!is_editable ? "opacity-60" : ""}`}>
<div className={`mb-2 mt-3 space-y-2.5 ${!isEditable ? "opacity-60" : ""}`}>
<div className="flex h-8 items-center gap-2">
<div className="flex w-2/5 flex-shrink-0 items-center gap-1 text-sm text-custom-text-300">
<DoubleCircleIcon className="h-4 w-4 flex-shrink-0" />
@ -207,7 +207,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
value={issue?.state_id ?? undefined}
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { state_id: val })}
projectId={projectId?.toString() ?? ""}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="group w-3/5 flex-grow"
buttonContainerClassName="w-full text-left"
@ -225,7 +225,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<MemberDropdown
value={issue?.assignee_ids ?? undefined}
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { assignee_ids: val })}
disabled={!is_editable}
disabled={!isEditable}
projectId={projectId?.toString() ?? ""}
placeholder="Add assignees"
multiple
@ -249,7 +249,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
<PriorityDropdown
value={issue?.priority || undefined}
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { priority: val })}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="border-with-text"
className="w-3/5 flex-grow rounded px-2 hover:bg-custom-background-80"
buttonContainerClassName="w-full text-left"
@ -271,7 +271,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
})
}
maxDate={maxDate ?? undefined}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="group w-3/5 flex-grow"
buttonContainerClassName="w-full text-left"
@ -297,7 +297,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
})
}
minDate={minDate ?? undefined}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="group w-3/5 flex-grow"
buttonContainerClassName="w-full text-left"
@ -322,7 +322,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
value={issue?.estimate_point !== null ? issue.estimate_point : null}
onChange={(val) => issueOperations.update(workspaceSlug, projectId, issueId, { estimate_point: val })}
projectId={projectId}
disabled={!is_editable}
disabled={!isEditable}
buttonVariant="transparent-with-text"
className="group w-3/5 flex-grow"
buttonContainerClassName="w-full text-left"
@ -347,7 +347,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
)}
@ -364,7 +364,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
)}
@ -380,7 +380,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
issueOperations={issueOperations}
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
@ -395,7 +395,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
relationKey="relates_to"
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
@ -410,7 +410,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
relationKey="blocking"
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
@ -425,7 +425,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
relationKey="blocked_by"
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
@ -440,7 +440,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
projectId={projectId}
issueId={issueId}
relationKey="duplicate"
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
@ -454,7 +454,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
</div>
@ -464,7 +464,7 @@ export const IssueDetailsSidebar: React.FC<Props> = observer((props) => {
workspaceSlug={workspaceSlug}
projectId={projectId}
issueId={issueId}
disabled={!is_editable}
disabled={!isEditable}
/>
</div>
</div>

View File

@ -372,7 +372,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
const currentProjectRole = currentWorkspaceAllProjectsRole?.[peekIssue?.projectId];
// Check if issue is editable, based on user role
const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const isEditable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
const isLoading = !issue || loader ? true : false;
return (
@ -382,7 +382,7 @@ export const IssuePeekOverview: FC<IIssuePeekOverview> = observer((props) => {
issueId={peekIssue.issueId}
isLoading={isLoading}
is_archived={is_archived}
disabled={!is_editable}
disabled={!isEditable}
issueOperations={issueOperations}
/>
);

View File

@ -1,4 +1,4 @@
import { ReactElement } from "react";
import { ReactElement, useEffect } from "react";
import { observer } from "mobx-react";
import { useRouter } from "next/router";
// components
@ -9,7 +9,7 @@ import { InboxIssueRoot } from "@/components/inbox";
// constants
import { EmptyStateType } from "@/constants/empty-state";
// hooks
import { useProject } from "@/hooks/store";
import { useProject, useProjectInbox } from "@/hooks/store";
// layouts
import { AppLayout } from "@/layouts/app-layout";
// types
@ -18,9 +18,10 @@ import { NextPageWithLayout } from "@/lib/types";
const ProjectInboxPage: NextPageWithLayout = observer(() => {
/// router
const router = useRouter();
const { workspaceSlug, projectId, inboxIssueId } = router.query;
const { workspaceSlug, projectId, currentTab: navigationTab, inboxIssueId } = router.query;
// hooks
const { currentProjectDetails } = useProject();
const { currentTab, handleCurrentTab } = useProjectInbox();
if (!workspaceSlug || !projectId) return <></>;
@ -38,6 +39,10 @@ const ProjectInboxPage: NextPageWithLayout = observer(() => {
// derived values
const pageTitle = currentProjectDetails?.name ? `${currentProjectDetails?.name} - Inbox` : "Plane - Inbox";
useEffect(() => {
if (navigationTab && currentTab != navigationTab) handleCurrentTab(navigationTab === "open" ? "open" : "closed");
}, [currentTab, navigationTab, handleCurrentTab]);
return (
<div className="flex h-full flex-col">
<PageHead title={pageTitle} />

View File

@ -1,9 +1,10 @@
import set from "lodash/set";
import { makeObservable, observable, runInAction, action } from "mobx";
// services
// types
import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types";
// services
import { InboxIssueService } from "@/services/inbox";
// root store
import { RootStore } from "@/store/root.store";
export interface IInboxIssueStore {
isLoading: boolean;
@ -36,7 +37,7 @@ export class InboxIssueStore implements IInboxIssueStore {
// services
inboxIssueService;
constructor(workspaceSlug: string, projectId: string, data: TInboxIssue) {
constructor(workspaceSlug: string, projectId: string, data: TInboxIssue, private store: RootStore) {
this.id = data.id;
this.status = data.status;
this.issue = data?.issue;
@ -90,10 +91,14 @@ export class InboxIssueStore implements IInboxIssueStore {
if (!this.issue.id) return;
set(this, "status", inboxStatus);
set(this, "duplicate_to", issueId);
await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
const issueResponse = await this.inboxIssueService.update(this.workspaceSlug, this.projectId, this.issue.id, {
status: inboxStatus,
duplicate_to: issueId,
});
runInAction(() => {
this.duplicate_to = issueResponse.duplicate_to;
this.duplicate_issue_detail = issueResponse.duplicate_issue_detail;
});
} catch {
runInAction(() => {
set(this, "status", previousData.status);
@ -133,6 +138,8 @@ export class InboxIssueStore implements IInboxIssueStore {
set(inboxIssue, issueKey, issue[issueKey]);
});
await this.inboxIssueService.updateIssue(this.workspaceSlug, this.projectId, this.issue.id, issue);
// fetching activity
await this.store.issue.issueDetail.fetchActivities(this.workspaceSlug, this.projectId, this.issue.id);
} catch {
Object.keys(issue).forEach((key) => {
const issueKey = key as keyof TIssue;

View File

@ -29,7 +29,7 @@ export interface IProjectInboxStore {
inboxFilters: Partial<TInboxIssueFilter>;
inboxSorting: Partial<TInboxIssueSorting>;
inboxIssuePaginationInfo: TInboxIssuePaginationInfo | undefined;
inboxIssues: Record<string, IInboxIssueStore>;
inboxIssues: Record<string, IInboxIssueStore>; // issue_id -> IInboxIssueStore
// computed
getAppliedFiltersCount: number;
inboxIssuesArray: IInboxIssueStore[];
@ -177,8 +177,6 @@ export class ProjectInboxStore implements IProjectInboxStore {
set(this, "inboxFilters", undefined);
set(this, ["inboxSorting", "order_by"], "issue__created_at");
set(this, ["inboxSorting", "sort_by"], "desc");
set(this, ["inboxIssues"], {});
set(this, ["inboxIssuePaginationInfo"], undefined);
if (tab === "closed") set(this, ["inboxFilters", "status"], [-1, 1, 2]);
else set(this, ["inboxFilters", "status"], [-2]);
const { workspaceSlug, projectId } = this.store.app.router;
@ -187,16 +185,12 @@ export class ProjectInboxStore implements IProjectInboxStore {
handleInboxIssueFilters = <T extends keyof TInboxIssueFilter>(key: T, value: TInboxIssueFilter[T]) => {
set(this.inboxFilters, key, value);
set(this, ["inboxIssues"], {});
set(this, ["inboxIssuePaginationInfo"], undefined);
const { workspaceSlug, projectId } = this.store.app.router;
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
};
handleInboxIssueSorting = <T extends keyof TInboxIssueSorting>(key: T, value: TInboxIssueSorting[T]) => {
set(this.inboxSorting, key, value);
set(this, ["inboxIssues"], {});
set(this, ["inboxIssuePaginationInfo"], undefined);
const { workspaceSlug, projectId } = this.store.app.router;
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
};
@ -210,6 +204,8 @@ export class ProjectInboxStore implements IProjectInboxStore {
try {
if (loadingType) this.isLoading = loadingType;
else if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading";
set(this, ["inboxIssues"], {});
set(this, ["inboxIssuePaginationInfo"], undefined);
const queryParams = this.inboxIssueQueryParams(
this.inboxFilters,
@ -225,7 +221,11 @@ export class ProjectInboxStore implements IProjectInboxStore {
if (results && results.length > 0)
results.forEach((value: TInboxIssue) => {
if (this.getIssueInboxByIssueId(value?.issue?.id) === undefined)
set(this.inboxIssues, value?.issue?.id, new InboxIssueStore(workspaceSlug, projectId, value));
set(
this.inboxIssues,
[value?.issue?.id],
new InboxIssueStore(workspaceSlug, projectId, value, this.store)
);
});
});
} catch (error) {
@ -267,7 +267,11 @@ export class ProjectInboxStore implements IProjectInboxStore {
if (results && results.length > 0)
results.forEach((value: TInboxIssue) => {
if (this.getIssueInboxByIssueId(value?.issue?.id) === undefined)
set(this.inboxIssues, value?.issue?.id, new InboxIssueStore(workspaceSlug, projectId, value));
set(
this.inboxIssues,
[value?.issue?.id],
new InboxIssueStore(workspaceSlug, projectId, value, this.store)
);
});
});
} else set(this, ["inboxIssuePaginationInfo", "next_page_results"], false);
@ -302,7 +306,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
// fetching comments
await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId);
runInAction(() => {
set(this.inboxIssues, issueId, new InboxIssueStore(workspaceSlug, projectId, inboxIssue));
set(this.inboxIssues, [issueId], new InboxIssueStore(workspaceSlug, projectId, inboxIssue, this.store));
});
this.isLoading = undefined;
}
@ -325,8 +329,8 @@ export class ProjectInboxStore implements IProjectInboxStore {
runInAction(() => {
set(
this.inboxIssues,
inboxIssueResponse?.issue?.id,
new InboxIssueStore(workspaceSlug, projectId, inboxIssueResponse)
[inboxIssueResponse?.issue?.id],
new InboxIssueStore(workspaceSlug, projectId, inboxIssueResponse, this.store)
);
set(
this,