mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
[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:
parent
d0cb00f28a
commit
f45c2d12fd
@ -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 })
|
||||
|
@ -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}
|
||||
/>
|
||||
|
||||
|
@ -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}
|
||||
/>
|
||||
|
@ -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"
|
||||
/>
|
||||
)}
|
||||
|
@ -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);
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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} />
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}
|
||||
/>
|
||||
);
|
||||
|
@ -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} />
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
Loading…
Reference in New Issue
Block a user