forked from github/plane
[WEB-406] chore: project inbox improvement (#4151)
* chore: inbox issue status pill improvement * chore: loader inconsistancy resolved * chore: accepted and decline inbox issue validation * chore: removed clear all button in applied filters * chore: inbox issue create label improvement * chore: updated label filter * chore: updated fetching activites and comments * chore: inbox filters date * chore: removed the print statement * chore: inbox date filter updated * chore: handled custom date filter in inbox issue query params * chore: handled custom date filter in inbox issue single select * chore: inbox custom date filter updated * chore: inbox sidebar filter improvement * chore: inbox sidebar filter improvement * chore: duplicate issue detail * chore: duplicate inbox issue improvement * chore: lint issue resolved --------- Co-authored-by: Anmol Singh Bhatia <anmolsinghbhatia@plane.so> Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
3c2b2e3ed6
commit
1dac70ecbe
@ -55,6 +55,9 @@ class InboxIssueSerializer(BaseSerializer):
|
|||||||
|
|
||||||
class InboxIssueDetailSerializer(BaseSerializer):
|
class InboxIssueDetailSerializer(BaseSerializer):
|
||||||
issue = IssueDetailSerializer(read_only=True)
|
issue = IssueDetailSerializer(read_only=True)
|
||||||
|
duplicate_issue_detail = IssueInboxSerializer(
|
||||||
|
read_only=True, source="duplicate_to"
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = InboxIssue
|
model = InboxIssue
|
||||||
@ -63,6 +66,7 @@ class InboxIssueDetailSerializer(BaseSerializer):
|
|||||||
"status",
|
"status",
|
||||||
"duplicate_to",
|
"duplicate_to",
|
||||||
"snoozed_till",
|
"snoozed_till",
|
||||||
|
"duplicate_issue_detail",
|
||||||
"source",
|
"source",
|
||||||
"issue",
|
"issue",
|
||||||
]
|
]
|
||||||
|
@ -168,9 +168,8 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
).distinct()
|
).distinct()
|
||||||
|
|
||||||
def list(self, request, slug, project_id):
|
def list(self, request, slug, project_id):
|
||||||
workspace = Workspace.objects.filter(slug=slug).first()
|
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
workspace_id=workspace.id, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
filters = issue_filters(request.GET, "GET", "issue__")
|
filters = issue_filters(request.GET, "GET", "issue__")
|
||||||
inbox_issue = (
|
inbox_issue = (
|
||||||
@ -264,9 +263,8 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
notification=True,
|
notification=True,
|
||||||
origin=request.META.get("HTTP_ORIGIN"),
|
origin=request.META.get("HTTP_ORIGIN"),
|
||||||
)
|
)
|
||||||
workspace = Workspace.objects.filter(slug=slug).first()
|
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
workspace_id=workspace.id, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
# create an inbox issue
|
# create an inbox issue
|
||||||
inbox_issue = InboxIssue.objects.create(
|
inbox_issue = InboxIssue.objects.create(
|
||||||
@ -279,9 +277,8 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
return Response(serializer.data, status=status.HTTP_200_OK)
|
return Response(serializer.data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def partial_update(self, request, slug, project_id, issue_id):
|
def partial_update(self, request, slug, project_id, issue_id):
|
||||||
workspace = Workspace.objects.filter(slug=slug).first()
|
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
workspace_id=workspace.id, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
inbox_issue = InboxIssue.objects.get(
|
inbox_issue = InboxIssue.objects.get(
|
||||||
issue_id=issue_id,
|
issue_id=issue_id,
|
||||||
@ -307,9 +304,12 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
|
|
||||||
# Get issue data
|
# Get issue data
|
||||||
issue_data = request.data.pop("issue", False)
|
issue_data = request.data.pop("issue", False)
|
||||||
|
|
||||||
if bool(issue_data):
|
if bool(issue_data):
|
||||||
issue = self.get_queryset().filter(pk=inbox_issue.issue_id).first()
|
issue = Issue.objects.get(
|
||||||
|
pk=inbox_issue.issue_id,
|
||||||
|
workspace__slug=slug,
|
||||||
|
project_id=project_id,
|
||||||
|
)
|
||||||
# Only allow guests and viewers to edit name and description
|
# Only allow guests and viewers to edit name and description
|
||||||
if project_member.role <= 10:
|
if project_member.role <= 10:
|
||||||
# viewers and guests since only viewers and guests
|
# viewers and guests since only viewers and guests
|
||||||
@ -406,9 +406,8 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
return Response(serializer, status=status.HTTP_200_OK)
|
return Response(serializer, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
def retrieve(self, request, slug, project_id, issue_id):
|
def retrieve(self, request, slug, project_id, issue_id):
|
||||||
workspace = Workspace.objects.filter(slug=slug).first()
|
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
workspace_id=workspace.id, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
inbox_issue = (
|
inbox_issue = (
|
||||||
InboxIssue.objects.select_related("issue")
|
InboxIssue.objects.select_related("issue")
|
||||||
@ -445,9 +444,8 @@ class InboxIssueViewSet(BaseViewSet):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def destroy(self, request, slug, project_id, issue_id):
|
def destroy(self, request, slug, project_id, issue_id):
|
||||||
workspace = Workspace.objects.filter(slug=slug).first()
|
|
||||||
inbox_id = Inbox.objects.filter(
|
inbox_id = Inbox.objects.filter(
|
||||||
workspace_id=workspace.id, project_id=project_id
|
workspace__slug=slug, project_id=project_id
|
||||||
).first()
|
).first()
|
||||||
inbox_issue = InboxIssue.objects.get(
|
inbox_issue = InboxIssue.objects.get(
|
||||||
issue_id=issue_id,
|
issue_id=issue_id,
|
||||||
|
@ -52,9 +52,9 @@ def string_date_filter(
|
|||||||
filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration)
|
filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration)
|
||||||
else:
|
else:
|
||||||
if offset == "fromnow":
|
if offset == "fromnow":
|
||||||
filter[f"{date_filter}__lte"] = now + timedelta(days=duration)
|
filter[f"{date_filter}__lte"] = now + timedelta(weeks=duration)
|
||||||
else:
|
else:
|
||||||
filter[f"{date_filter}__lte"] = now - timedelta(days=duration)
|
filter[f"{date_filter}__lte"] = now - timedelta(weeks=duration)
|
||||||
|
|
||||||
|
|
||||||
def date_filter(filter, date_term, queries):
|
def date_filter(filter, date_term, queries):
|
||||||
|
16
packages/types/src/inbox.d.ts
vendored
16
packages/types/src/inbox.d.ts
vendored
@ -18,7 +18,7 @@ export type TInboxIssueFilter = {
|
|||||||
} & {
|
} & {
|
||||||
status: TInboxIssueStatus[] | undefined;
|
status: TInboxIssueStatus[] | undefined;
|
||||||
priority: TIssuePriorities[] | undefined;
|
priority: TIssuePriorities[] | undefined;
|
||||||
label: string[] | undefined;
|
labels: string[] | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
// sorting filters
|
// sorting filters
|
||||||
@ -50,21 +50,29 @@ export type TInboxIssueSortingOrderByQueryParam = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type TInboxIssuesQueryParams = {
|
export type TInboxIssuesQueryParams = {
|
||||||
[key in TInboxIssueFilter]: string;
|
[key in keyof TInboxIssueFilter]: string;
|
||||||
} & TInboxIssueSortingOrderByQueryParam & {
|
} & TInboxIssueSortingOrderByQueryParam & {
|
||||||
per_page: number;
|
per_page: number;
|
||||||
cursor: string;
|
cursor: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
// inbox issue types
|
// inbox issue types
|
||||||
|
|
||||||
|
export type TInboxDuplicateIssueDetails = {
|
||||||
|
id: string;
|
||||||
|
sequence_id: string;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type TInboxIssue = {
|
export type TInboxIssue = {
|
||||||
id: string;
|
id: string;
|
||||||
status: TInboxIssueStatus;
|
status: TInboxIssueStatus;
|
||||||
snoozed_till: Date | null;
|
snoozed_till: Date | undefined;
|
||||||
duplicate_to: string | null;
|
duplicate_to: string | undefined;
|
||||||
source: string;
|
source: string;
|
||||||
issue: TIssue;
|
issue: TIssue;
|
||||||
created_by: string;
|
created_by: string;
|
||||||
|
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
export type TInboxIssuePaginationInfo = TPaginationInfo & {
|
||||||
|
@ -178,7 +178,7 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||||||
{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id}
|
{getProjectById(issue.project_id)?.identifier}-{issue.sequence_id}
|
||||||
</h3>
|
</h3>
|
||||||
)}
|
)}
|
||||||
<InboxIssueStatus inboxIssue={inboxIssue} />
|
<InboxIssueStatus inboxIssue={inboxIssue} iconSize={12} />
|
||||||
<div className="flex items-center justify-end w-full">
|
<div className="flex items-center justify-end w-full">
|
||||||
<IssueUpdateStatus isSubmitting={isSubmitting} />
|
<IssueUpdateStatus isSubmitting={isSubmitting} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { CalendarCheck2, Signal, Tag } from "lucide-react";
|
import { useRouter } from "next/router";
|
||||||
import { TIssue } from "@plane/types";
|
import { CalendarCheck2, CopyPlus, Signal, Tag } from "lucide-react";
|
||||||
import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui";
|
import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types";
|
||||||
|
import { ControlLink, DoubleCircleIcon, Tooltip, UserGroupIcon } from "@plane/ui";
|
||||||
// components
|
// components
|
||||||
import { DateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "@/components/dropdowns";
|
import { DateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "@/components/dropdowns";
|
||||||
import { IssueLabel, TIssueOperations } from "@/components/issues";
|
import { IssueLabel, TIssueOperations } from "@/components/issues";
|
||||||
// helper
|
// helper
|
||||||
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper";
|
||||||
|
// hooks
|
||||||
|
import { useProject } from "@/hooks/store";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
@ -15,14 +18,18 @@ type Props = {
|
|||||||
issue: Partial<TIssue>;
|
issue: Partial<TIssue>;
|
||||||
issueOperations: TIssueOperations;
|
issueOperations: TIssueOperations;
|
||||||
is_editable: boolean;
|
is_editable: boolean;
|
||||||
|
duplicateIssueDetails: TInboxDuplicateIssueDetails | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const InboxIssueProperties: React.FC<Props> = observer((props) => {
|
export const InboxIssueProperties: React.FC<Props> = observer((props) => {
|
||||||
const { workspaceSlug, projectId, issue, issueOperations, is_editable } = props;
|
const { workspaceSlug, projectId, issue, issueOperations, is_editable, duplicateIssueDetails } = props;
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
// store hooks
|
||||||
|
const { currentProjectDetails } = useProject();
|
||||||
|
|
||||||
const minDate = issue.start_date ? getDate(issue.start_date) : null;
|
const minDate = issue.start_date ? getDate(issue.start_date) : null;
|
||||||
minDate?.setDate(minDate.getDate());
|
minDate?.setDate(minDate.getDate());
|
||||||
|
|
||||||
if (!issue || !issue?.id) return <></>;
|
if (!issue || !issue?.id) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="flex h-min w-full flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
|
<div className="flex h-min w-full flex-col divide-y-2 divide-custom-border-200 overflow-hidden">
|
||||||
@ -149,6 +156,29 @@ export const InboxIssueProperties: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* duplicate to*/}
|
||||||
|
{duplicateIssueDetails && (
|
||||||
|
<div className="flex min-h-8 gap-2">
|
||||||
|
<div className="flex w-2/5 flex-shrink-0 gap-1 pt-2 text-sm text-custom-text-300">
|
||||||
|
<CopyPlus className="h-4 w-4 flex-shrink-0" />
|
||||||
|
<span>Duplicate of</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ControlLink
|
||||||
|
href={`/${workspaceSlug}/projects/${projectId}/issues/${duplicateIssueDetails?.id}`}
|
||||||
|
onClick={() => {
|
||||||
|
router.push(`/${workspaceSlug}/projects/${projectId}/issues/${duplicateIssueDetails?.id}`);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip tooltipContent={`${duplicateIssueDetails?.name}`}>
|
||||||
|
<span className="flex items-center gap-1 cursor-pointer text-xs rounded px-1.5 py-1 pb-0.5 bg-custom-background-80 text-custom-text-200">
|
||||||
|
{`${currentProjectDetails?.identifier}-${duplicateIssueDetails?.sequence_id}`}
|
||||||
|
</span>
|
||||||
|
</Tooltip>
|
||||||
|
</ControlLink>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -164,6 +164,7 @@ export const InboxIssueMainContent: React.FC<Props> = observer((props) => {
|
|||||||
issue={issue}
|
issue={issue}
|
||||||
issueOperations={issueOperations}
|
issueOperations={issueOperations}
|
||||||
is_editable={is_editable}
|
is_editable={is_editable}
|
||||||
|
duplicateIssueDetails={inboxIssue?.duplicate_issue_detail}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="pb-12">
|
<div className="pb-12">
|
||||||
|
@ -35,6 +35,9 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
|
|||||||
const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER;
|
||||||
|
|
||||||
if (!inboxIssue) return <></>;
|
if (!inboxIssue) return <></>;
|
||||||
|
|
||||||
|
const isIssueAcceptedOrDeclined = [-1, 1].includes(inboxIssue.status);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="w-full h-full overflow-hidden relative flex flex-col">
|
<div className="w-full h-full overflow-hidden relative flex flex-col">
|
||||||
@ -51,7 +54,7 @@ export const InboxContentRoot: FC<TInboxContentRoot> = observer((props) => {
|
|||||||
workspaceSlug={workspaceSlug}
|
workspaceSlug={workspaceSlug}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
inboxIssue={inboxIssue}
|
inboxIssue={inboxIssue}
|
||||||
is_editable={is_editable}
|
is_editable={is_editable && !isIssueAcceptedOrDeclined}
|
||||||
isSubmitting={isSubmitting}
|
isSubmitting={isSubmitting}
|
||||||
setIsSubmitting={setIsSubmitting}
|
setIsSubmitting={setIsSubmitting}
|
||||||
/>
|
/>
|
||||||
|
@ -2,10 +2,10 @@ import { FC } from "react";
|
|||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { X } from "lucide-react";
|
import { X } from "lucide-react";
|
||||||
import { TInboxIssueFilterDateKeys } from "@plane/types";
|
import { TInboxIssueFilterDateKeys } from "@plane/types";
|
||||||
// constants
|
|
||||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters";
|
|
||||||
// helpers
|
// helpers
|
||||||
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
import { renderFormattedDate } from "@/helpers/date-time.helper";
|
||||||
|
// constants
|
||||||
|
import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
import { useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ export const InboxIssueAppliedFiltersDate: FC<InboxIssueAppliedFiltersDate> = ob
|
|||||||
// derived values
|
// derived values
|
||||||
const filteredValues = inboxFilters?.[filterKey] || [];
|
const filteredValues = inboxFilters?.[filterKey] || [];
|
||||||
const currentOptionDetail = (date: string) => {
|
const currentOptionDetail = (date: string) => {
|
||||||
const currentDate = DATE_BEFORE_FILTER_OPTIONS.find((d) => d.value === date);
|
const currentDate = PAST_DURATION_FILTER_OPTIONS.find((d) => d.value === date);
|
||||||
if (currentDate) return currentDate;
|
if (currentDate) return currentDate;
|
||||||
const dateSplit = date.split(";");
|
const dateSplit = date.split(";");
|
||||||
return {
|
return {
|
||||||
|
@ -13,13 +13,13 @@ export const InboxIssueAppliedFiltersLabel: FC = observer(() => {
|
|||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||||
const { getLabelById } = useLabel();
|
const { getLabelById } = useLabel();
|
||||||
// derived values
|
// derived values
|
||||||
const filteredValues = inboxFilters?.label || [];
|
const filteredValues = inboxFilters?.labels || [];
|
||||||
const currentOptionDetail = (labelId: string) => getLabelById(labelId) || undefined;
|
const currentOptionDetail = (labelId: string) => getLabelById(labelId) || undefined;
|
||||||
|
|
||||||
const handleFilterValue = (value: string): string[] =>
|
const handleFilterValue = (value: string): string[] =>
|
||||||
filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value];
|
filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value];
|
||||||
|
|
||||||
const clearFilter = () => handleInboxIssueFilters("label", undefined);
|
const clearFilter = () => handleInboxIssueFilters("labels", undefined);
|
||||||
|
|
||||||
if (filteredValues.length === 0) return <></>;
|
if (filteredValues.length === 0) return <></>;
|
||||||
return (
|
return (
|
||||||
@ -36,7 +36,7 @@ export const InboxIssueAppliedFiltersLabel: FC = observer(() => {
|
|||||||
<div className="text-xs truncate">{optionDetail?.name}</div>
|
<div className="text-xs truncate">{optionDetail?.name}</div>
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
||||||
onClick={() => handleInboxIssueFilters("label", handleFilterValue(value))}
|
onClick={() => handleInboxIssueFilters("labels", handleFilterValue(value))}
|
||||||
>
|
>
|
||||||
<X className={`w-3 h-3`} />
|
<X className={`w-3 h-3`} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -28,9 +28,9 @@ export const InboxIssueAppliedFilters: FC = observer(() => {
|
|||||||
{/* label */}
|
{/* label */}
|
||||||
<InboxIssueAppliedFiltersLabel />
|
<InboxIssueAppliedFiltersLabel />
|
||||||
{/* created_at */}
|
{/* created_at */}
|
||||||
<InboxIssueAppliedFiltersDate filterKey="created_at" label="Created At" />
|
<InboxIssueAppliedFiltersDate filterKey="created_at" label="Created date" />
|
||||||
{/* updated_at */}
|
{/* updated_at */}
|
||||||
<InboxIssueAppliedFiltersDate filterKey="updated_at" label="Updated At" />
|
<InboxIssueAppliedFiltersDate filterKey="updated_at" label="Updated date" />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -9,7 +9,7 @@ import { useProjectInbox } from "@/hooks/store";
|
|||||||
|
|
||||||
export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
|
export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
|
||||||
// hooks
|
// hooks
|
||||||
const { currentTab, inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||||
// derived values
|
// derived values
|
||||||
const filteredValues = inboxFilters?.status || [];
|
const filteredValues = inboxFilters?.status || [];
|
||||||
const currentOptionDetail = (status: TInboxIssueStatus) => INBOX_STATUS.find((s) => s.status === status) || undefined;
|
const currentOptionDetail = (status: TInboxIssueStatus) => INBOX_STATUS.find((s) => s.status === status) || undefined;
|
||||||
@ -17,8 +17,6 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
|
|||||||
const handleFilterValue = (value: TInboxIssueStatus): TInboxIssueStatus[] =>
|
const handleFilterValue = (value: TInboxIssueStatus): TInboxIssueStatus[] =>
|
||||||
filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value];
|
filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value];
|
||||||
|
|
||||||
const clearFilter = () => handleInboxIssueFilters("status", undefined);
|
|
||||||
|
|
||||||
if (filteredValues.length === 0) return <></>;
|
if (filteredValues.length === 0) return <></>;
|
||||||
return (
|
return (
|
||||||
<div className="relative flex flex-wrap items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1">
|
<div className="relative flex flex-wrap items-center gap-2 rounded-md border border-custom-border-200 px-2 py-1">
|
||||||
@ -32,7 +30,7 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
|
|||||||
<optionDetail.icon className={`w-3 h-3 ${optionDetail?.textColor(false)}`} />
|
<optionDetail.icon className={`w-3 h-3 ${optionDetail?.textColor(false)}`} />
|
||||||
</div>
|
</div>
|
||||||
<div className="text-xs truncate">{optionDetail?.title}</div>
|
<div className="text-xs truncate">{optionDetail?.title}</div>
|
||||||
{currentTab === "closed" && handleFilterValue(optionDetail?.status).length >= 1 && (
|
{handleFilterValue(optionDetail?.status).length >= 1 && (
|
||||||
<div
|
<div
|
||||||
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
||||||
onClick={() => handleInboxIssueFilters("status", handleFilterValue(optionDetail?.status))}
|
onClick={() => handleInboxIssueFilters("status", handleFilterValue(optionDetail?.status))}
|
||||||
@ -43,15 +41,6 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
{currentTab === "closed" && filteredValues.length > 1 && (
|
|
||||||
<div
|
|
||||||
className="w-3 h-3 flex-shrink-0 relative flex justify-center items-center overflow-hidden cursor-pointer text-custom-text-300 hover:text-custom-text-200 transition-all"
|
|
||||||
onClick={clearFilter}
|
|
||||||
>
|
|
||||||
<X className={`w-3 h-3`} />
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { FC, useState } from "react";
|
import { FC, useState } from "react";
|
||||||
import concat from "lodash/concat";
|
import concat from "lodash/concat";
|
||||||
import pull from "lodash/pull";
|
|
||||||
import uniq from "lodash/uniq";
|
import uniq from "lodash/uniq";
|
||||||
import { observer } from "mobx-react";
|
import { observer } from "mobx-react";
|
||||||
import { TInboxIssueFilterDateKeys } from "@plane/types";
|
import { TInboxIssueFilterDateKeys } from "@plane/types";
|
||||||
@ -8,7 +7,7 @@ import { TInboxIssueFilterDateKeys } from "@plane/types";
|
|||||||
import { DateFilterModal } from "@/components/core";
|
import { DateFilterModal } from "@/components/core";
|
||||||
import { FilterHeader, FilterOption } from "@/components/issues";
|
import { FilterHeader, FilterOption } from "@/components/issues";
|
||||||
// constants
|
// constants
|
||||||
import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters";
|
import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper";
|
||||||
// hooks
|
// hooks
|
||||||
import { useProjectInbox } from "@/hooks/store";
|
import { useProjectInbox } from "@/hooks/store";
|
||||||
|
|
||||||
@ -33,16 +32,15 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
// derived values
|
// derived values
|
||||||
const filterValue: string[] = inboxFilters?.[filterKey] || [];
|
const filterValue: string[] = inboxFilters?.[filterKey] || [];
|
||||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||||
const filteredOptions = DATE_BEFORE_FILTER_OPTIONS.filter((d) =>
|
const filteredOptions = PAST_DURATION_FILTER_OPTIONS.filter((d) =>
|
||||||
d.name.toLowerCase().includes(searchQuery.toLowerCase())
|
d.name.toLowerCase().includes(searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleFilterValue = (value: string): string[] =>
|
const handleFilterValue = (value: string): string[] => (filterValue?.includes(value) ? [] : uniq(concat(value)));
|
||||||
filterValue?.includes(value) ? pull(filterValue, value) : uniq(concat(filterValue, value));
|
|
||||||
|
|
||||||
const handleCustomFilterValue = (value: string[]): string[] => {
|
const handleCustomFilterValue = (value: string[]): string[] => {
|
||||||
const finalOptions: string[] = [...filterValue];
|
const finalOptions: string[] = [...filterValue];
|
||||||
value.forEach((v) => (finalOptions?.includes(v) ? pull(finalOptions, v) : finalOptions.push(v)));
|
value.forEach((v) => (finalOptions?.includes(v) ? [] : finalOptions.push(v)));
|
||||||
return uniq(finalOptions);
|
return uniq(finalOptions);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -53,10 +51,13 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const handleCustomDate = () => {
|
const handleCustomDate = () => {
|
||||||
if (isCustomDateSelected()) {
|
if (isCustomDateSelected()) {
|
||||||
const updateAppliedFilters = filterValue?.filter((f) => isDate(f.split(";")[0])) || [];
|
const updateAppliedFilters = filterValue?.filter((f) => !isDate(f.split(";")[0])) || [];
|
||||||
handleInboxIssueFilters(filterKey, handleCustomFilterValue(updateAppliedFilters));
|
handleInboxIssueFilters(filterKey, updateAppliedFilters);
|
||||||
} else setIsDateFilterModalOpen(true);
|
} else {
|
||||||
|
setIsDateFilterModalOpen(true);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{isDateFilterModalOpen && (
|
{isDateFilterModalOpen && (
|
||||||
@ -82,10 +83,15 @@ export const FilterDate: FC<Props> = observer((props) => {
|
|||||||
isChecked={filterValue?.includes(option.value) ? true : false}
|
isChecked={filterValue?.includes(option.value) ? true : false}
|
||||||
onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(option.value))}
|
onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(option.value))}
|
||||||
title={option.name}
|
title={option.name}
|
||||||
multiple
|
multiple={false}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
<FilterOption isChecked={isCustomDateSelected()} onClick={handleCustomDate} title="Custom" multiple />
|
<FilterOption
|
||||||
|
isChecked={isCustomDateSelected()}
|
||||||
|
onClick={handleCustomDate}
|
||||||
|
title="Custom"
|
||||||
|
multiple={false}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
<p className="text-xs italic text-custom-text-400">No matches found</p>
|
||||||
|
@ -75,11 +75,11 @@ export const InboxIssueFilterSelection: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
{/* Created at */}
|
{/* Created at */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterDate filterKey="created_at" label="Created at" searchQuery={filtersSearchQuery} />
|
<FilterDate filterKey="created_at" label="Created date" searchQuery={filtersSearchQuery} />
|
||||||
</div>
|
</div>
|
||||||
{/* Updated at */}
|
{/* Updated at */}
|
||||||
<div className="py-2">
|
<div className="py-2">
|
||||||
<FilterDate filterKey="updated_at" label="Updated at" searchQuery={filtersSearchQuery} />
|
<FilterDate filterKey="updated_at" label="Last updated date" searchQuery={filtersSearchQuery} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,7 +24,7 @@ export const FilterLabels: FC<Props> = observer((props) => {
|
|||||||
|
|
||||||
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
const { inboxFilters, handleInboxIssueFilters } = useProjectInbox();
|
||||||
|
|
||||||
const filterValue = inboxFilters?.label || [];
|
const filterValue = inboxFilters?.labels || [];
|
||||||
|
|
||||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||||
|
|
||||||
@ -56,7 +56,7 @@ export const FilterLabels: FC<Props> = observer((props) => {
|
|||||||
<FilterOption
|
<FilterOption
|
||||||
key={label?.id}
|
key={label?.id}
|
||||||
isChecked={filterValue?.includes(label?.id) ? true : false}
|
isChecked={filterValue?.includes(label?.id) ? true : false}
|
||||||
onClick={() => handleInboxIssueFilters("label", handleFilterValue(label.id))}
|
onClick={() => handleInboxIssueFilters("labels", handleFilterValue(label.id))}
|
||||||
icon={<LabelIcons color={label.color} />}
|
icon={<LabelIcons color={label.color} />}
|
||||||
title={label.name}
|
title={label.name}
|
||||||
/>
|
/>
|
||||||
|
@ -24,8 +24,8 @@ export const FilterStatus: FC<Props> = observer((props) => {
|
|||||||
const appliedFiltersCount = filterValue?.length ?? 0;
|
const appliedFiltersCount = filterValue?.length ?? 0;
|
||||||
const filteredOptions = INBOX_STATUS.filter(
|
const filteredOptions = INBOX_STATUS.filter(
|
||||||
(s) =>
|
(s) =>
|
||||||
((currentTab === "open" && [-2].includes(s.status)) ||
|
((currentTab === "open" && [-2, 0].includes(s.status)) ||
|
||||||
(currentTab === "closed" && [-1, 0, 1, 2].includes(s.status))) &&
|
(currentTab === "closed" && [-1, 1, 2].includes(s.status))) &&
|
||||||
s.key.includes(searchQuery.toLowerCase())
|
s.key.includes(searchQuery.toLowerCase())
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -33,10 +33,8 @@ export const FilterStatus: FC<Props> = observer((props) => {
|
|||||||
filterValue?.includes(value) ? filterValue.filter((v) => v !== value) : [...filterValue, value];
|
filterValue?.includes(value) ? filterValue.filter((v) => v !== value) : [...filterValue, value];
|
||||||
|
|
||||||
const handleStatusFilterSelect = (status: TInboxIssueStatus) => {
|
const handleStatusFilterSelect = (status: TInboxIssueStatus) => {
|
||||||
if (currentTab === "closed") {
|
const selectedStatus = handleFilterValue(status);
|
||||||
const selectedStatus = handleFilterValue(status);
|
if (selectedStatus.length >= 1) handleInboxIssueFilters("status", selectedStatus);
|
||||||
if (selectedStatus.length >= 1) handleInboxIssueFilters("status", selectedStatus);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -32,14 +32,14 @@ export const InboxIssueStatus: React.FC<Props> = observer((props) => {
|
|||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className={`flex items-center gap-1`}>
|
<div className={`flex items-center gap-1`}>
|
||||||
<inboxIssueStatusDetail.icon size={iconSize} />
|
<inboxIssueStatusDetail.icon size={iconSize} className="flex-shrink-0" />
|
||||||
<div className="font-medium text-xs">
|
<div className="font-medium text-xs whitespace-nowrap">
|
||||||
{inboxIssue?.status === 0 && inboxIssue?.snoozed_till
|
{inboxIssue?.status === 0 && inboxIssue?.snoozed_till
|
||||||
? inboxIssueStatusDetail.description(inboxIssue?.snoozed_till)
|
? inboxIssueStatusDetail.description(inboxIssue?.snoozed_till)
|
||||||
: inboxIssueStatusDetail.title}
|
: inboxIssueStatusDetail.title}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{showDescription && <div className="text-sm">{description}</div>}
|
{showDescription && <div className="text-sm whitespace-nowrap">{description}</div>}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -28,7 +28,7 @@ export const InboxIssueRoot: FC<TInboxIssueRoot> = observer((props) => {
|
|||||||
() => {
|
() => {
|
||||||
inboxAccessible && workspaceSlug && projectId && fetchInboxIssues(workspaceSlug.toString(), projectId.toString());
|
inboxAccessible && workspaceSlug && projectId && fetchInboxIssues(workspaceSlug.toString(), projectId.toString());
|
||||||
},
|
},
|
||||||
{ revalidateOnFocus: false }
|
{ revalidateOnFocus: false, revalidateIfStale: false }
|
||||||
);
|
);
|
||||||
|
|
||||||
// loader
|
// loader
|
||||||
|
@ -99,7 +99,7 @@ export const InboxSidebar: FC<IInboxSidebarProps> = observer((props) => {
|
|||||||
|
|
||||||
<InboxIssueAppliedFilters />
|
<InboxIssueAppliedFilters />
|
||||||
|
|
||||||
{isLoading && !inboxIssuePaginationInfo?.next_page_results ? (
|
{isLoading === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? (
|
||||||
<InboxSidebarLoader />
|
<InboxSidebarLoader />
|
||||||
) : (
|
) : (
|
||||||
<div
|
<div
|
||||||
|
@ -69,6 +69,7 @@ export const LabelCreate: FC<ILabelCreate> = (props) => {
|
|||||||
const labelResponse = await labelOperations.createLabel(workspaceSlug, projectId, formData);
|
const labelResponse = await labelOperations.createLabel(workspaceSlug, projectId, formData);
|
||||||
const currentLabels = [...(values || []), labelResponse.id];
|
const currentLabels = [...(values || []), labelResponse.id];
|
||||||
await labelOperations.updateIssue(workspaceSlug, projectId, issueId, { label_ids: currentLabels });
|
await labelOperations.updateIssue(workspaceSlug, projectId, issueId, { label_ids: currentLabels });
|
||||||
|
handleIsCreateToggle();
|
||||||
reset(defaultValues);
|
reset(defaultValues);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
setToast({
|
setToast({
|
||||||
|
56
web/helpers/inbox.helper.ts
Normal file
56
web/helpers/inbox.helper.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { subDays } from "date-fns";
|
||||||
|
import { renderFormattedPayloadDate } from "./date-time.helper";
|
||||||
|
|
||||||
|
export enum EPastDurationFilters {
|
||||||
|
TODAY = "today",
|
||||||
|
YESTERDAY = "yesterday",
|
||||||
|
LAST_7_DAYS = "last_7_days",
|
||||||
|
LAST_30_DAYS = "last_30_days",
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getCustomDates = (duration: EPastDurationFilters): string => {
|
||||||
|
const today = new Date();
|
||||||
|
let firstDay, lastDay;
|
||||||
|
|
||||||
|
switch (duration) {
|
||||||
|
case EPastDurationFilters.TODAY:
|
||||||
|
firstDay = renderFormattedPayloadDate(today);
|
||||||
|
lastDay = renderFormattedPayloadDate(today);
|
||||||
|
return `${firstDay};after,${lastDay};before`;
|
||||||
|
case EPastDurationFilters.YESTERDAY:
|
||||||
|
const yesterday = subDays(today, 1);
|
||||||
|
firstDay = renderFormattedPayloadDate(yesterday);
|
||||||
|
lastDay = renderFormattedPayloadDate(yesterday);
|
||||||
|
return `${firstDay};after,${lastDay};before`;
|
||||||
|
case EPastDurationFilters.LAST_7_DAYS:
|
||||||
|
firstDay = renderFormattedPayloadDate(subDays(today, 7));
|
||||||
|
lastDay = renderFormattedPayloadDate(today);
|
||||||
|
return `${firstDay};after,${lastDay};before`;
|
||||||
|
case EPastDurationFilters.LAST_30_DAYS:
|
||||||
|
firstDay = renderFormattedPayloadDate(subDays(today, 30));
|
||||||
|
lastDay = renderFormattedPayloadDate(today);
|
||||||
|
return `${firstDay};after,${lastDay};before`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const PAST_DURATION_FILTER_OPTIONS: {
|
||||||
|
name: string;
|
||||||
|
value: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
name: "Today",
|
||||||
|
value: EPastDurationFilters.TODAY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Yesterday",
|
||||||
|
value: EPastDurationFilters.YESTERDAY,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Last 7 days",
|
||||||
|
value: EPastDurationFilters.LAST_7_DAYS,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Last 30 days",
|
||||||
|
value: EPastDurationFilters.LAST_30_DAYS,
|
||||||
|
},
|
||||||
|
];
|
@ -2,7 +2,7 @@ import set from "lodash/set";
|
|||||||
import { makeObservable, observable, runInAction, action } from "mobx";
|
import { makeObservable, observable, runInAction, action } from "mobx";
|
||||||
// services
|
// services
|
||||||
// types
|
// types
|
||||||
import { TIssue, TInboxIssue, TInboxIssueStatus } from "@plane/types";
|
import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types";
|
||||||
import { InboxIssueService } from "@/services/inbox";
|
import { InboxIssueService } from "@/services/inbox";
|
||||||
|
|
||||||
export interface IInboxIssueStore {
|
export interface IInboxIssueStore {
|
||||||
@ -13,6 +13,7 @@ export interface IInboxIssueStore {
|
|||||||
snoozed_till: Date | undefined;
|
snoozed_till: Date | undefined;
|
||||||
duplicate_to: string | undefined;
|
duplicate_to: string | undefined;
|
||||||
created_by: string | undefined;
|
created_by: string | undefined;
|
||||||
|
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined;
|
||||||
// actions
|
// actions
|
||||||
updateInboxIssueStatus: (status: TInboxIssueStatus) => Promise<void>; // accept, decline
|
updateInboxIssueStatus: (status: TInboxIssueStatus) => Promise<void>; // accept, decline
|
||||||
updateInboxIssueDuplicateTo: (issueId: string) => Promise<void>; // connecting the inbox issue to the project existing issue
|
updateInboxIssueDuplicateTo: (issueId: string) => Promise<void>; // connecting the inbox issue to the project existing issue
|
||||||
@ -29,6 +30,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
snoozed_till: Date | undefined;
|
snoozed_till: Date | undefined;
|
||||||
duplicate_to: string | undefined;
|
duplicate_to: string | undefined;
|
||||||
created_by: string | undefined;
|
created_by: string | undefined;
|
||||||
|
duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined = undefined;
|
||||||
workspaceSlug: string;
|
workspaceSlug: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
// services
|
// services
|
||||||
@ -41,6 +43,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
this.snoozed_till = data?.snoozed_till ? new Date(data.snoozed_till) : undefined;
|
this.snoozed_till = data?.snoozed_till ? new Date(data.snoozed_till) : undefined;
|
||||||
this.duplicate_to = data?.duplicate_to || undefined;
|
this.duplicate_to = data?.duplicate_to || undefined;
|
||||||
this.created_by = data?.created_by || undefined;
|
this.created_by = data?.created_by || undefined;
|
||||||
|
this.duplicate_issue_detail = data?.duplicate_issue_detail || undefined;
|
||||||
this.workspaceSlug = workspaceSlug;
|
this.workspaceSlug = workspaceSlug;
|
||||||
this.projectId = projectId;
|
this.projectId = projectId;
|
||||||
// services
|
// services
|
||||||
@ -52,6 +55,7 @@ export class InboxIssueStore implements IInboxIssueStore {
|
|||||||
issue: observable,
|
issue: observable,
|
||||||
snoozed_till: observable,
|
snoozed_till: observable,
|
||||||
duplicate_to: observable,
|
duplicate_to: observable,
|
||||||
|
duplicate_issue_detail: observable,
|
||||||
created_by: observable,
|
created_by: observable,
|
||||||
// actions
|
// actions
|
||||||
updateInboxIssueStatus: action,
|
updateInboxIssueStatus: action,
|
||||||
|
@ -12,6 +12,8 @@ import {
|
|||||||
TInboxIssuePaginationInfo,
|
TInboxIssuePaginationInfo,
|
||||||
TInboxIssueSortingOrderByQueryParam,
|
TInboxIssueSortingOrderByQueryParam,
|
||||||
} from "@plane/types";
|
} from "@plane/types";
|
||||||
|
// helpers
|
||||||
|
import { EPastDurationFilters, getCustomDates } from "@/helpers/inbox.helper";
|
||||||
// services
|
// services
|
||||||
import { InboxIssueService } from "@/services/inbox";
|
import { InboxIssueService } from "@/services/inbox";
|
||||||
// root store
|
// root store
|
||||||
@ -110,7 +112,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
|
|
||||||
get inboxIssuesArray() {
|
get inboxIssuesArray() {
|
||||||
return Object.values(this.inboxIssues || {}).filter((inbox) =>
|
return Object.values(this.inboxIssues || {}).filter((inbox) =>
|
||||||
(this.currentTab === "open" ? [-2] : [-1, 0, 1, 2]).includes(inbox.status)
|
(this.currentTab === "open" ? [-2, 0] : [-1, 1, 2]).includes(inbox.status)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -126,8 +128,16 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
!isEmpty(inboxFilters) &&
|
!isEmpty(inboxFilters) &&
|
||||||
Object.keys(inboxFilters).forEach((key) => {
|
Object.keys(inboxFilters).forEach((key) => {
|
||||||
const filterKey = key as keyof TInboxIssueFilter;
|
const filterKey = key as keyof TInboxIssueFilter;
|
||||||
if (inboxFilters[filterKey] && inboxFilters[filterKey]?.length)
|
if (inboxFilters[filterKey] && inboxFilters[filterKey]?.length) {
|
||||||
filters[filterKey] = inboxFilters[filterKey]?.join(",");
|
if (["created_at", "updated_at"].includes(filterKey) && (inboxFilters[filterKey] || [])?.length > 0) {
|
||||||
|
const appliedDateFilters: string[] = [];
|
||||||
|
inboxFilters[filterKey]?.forEach((value) => {
|
||||||
|
const dateValue = value as EPastDurationFilters;
|
||||||
|
appliedDateFilters.push(getCustomDates(dateValue));
|
||||||
|
});
|
||||||
|
filters[filterKey] = appliedDateFilters?.join(",");
|
||||||
|
} else filters[filterKey] = inboxFilters[filterKey]?.join(",");
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const sorting: TInboxIssueSortingOrderByQueryParam = {
|
const sorting: TInboxIssueSortingOrderByQueryParam = {
|
||||||
@ -167,7 +177,9 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
set(this, "inboxFilters", undefined);
|
set(this, "inboxFilters", undefined);
|
||||||
set(this, ["inboxSorting", "order_by"], "issue__created_at");
|
set(this, ["inboxSorting", "order_by"], "issue__created_at");
|
||||||
set(this, ["inboxSorting", "sort_by"], "desc");
|
set(this, ["inboxSorting", "sort_by"], "desc");
|
||||||
if (tab === "closed") set(this, ["inboxFilters", "status"], [-1, 0, 1, 2]);
|
set(this, ["inboxIssues"], {});
|
||||||
|
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||||
|
if (tab === "closed") set(this, ["inboxFilters", "status"], [-1, 1, 2]);
|
||||||
else set(this, ["inboxFilters", "status"], [-2]);
|
else set(this, ["inboxFilters", "status"], [-2]);
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
||||||
@ -175,12 +187,16 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
|
|
||||||
handleInboxIssueFilters = <T extends keyof TInboxIssueFilter>(key: T, value: TInboxIssueFilter[T]) => {
|
handleInboxIssueFilters = <T extends keyof TInboxIssueFilter>(key: T, value: TInboxIssueFilter[T]) => {
|
||||||
set(this.inboxFilters, key, value);
|
set(this.inboxFilters, key, value);
|
||||||
|
set(this, ["inboxIssues"], {});
|
||||||
|
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
||||||
};
|
};
|
||||||
|
|
||||||
handleInboxIssueSorting = <T extends keyof TInboxIssueSorting>(key: T, value: TInboxIssueSorting[T]) => {
|
handleInboxIssueSorting = <T extends keyof TInboxIssueSorting>(key: T, value: TInboxIssueSorting[T]) => {
|
||||||
set(this.inboxSorting, key, value);
|
set(this.inboxSorting, key, value);
|
||||||
|
set(this, ["inboxIssues"], {});
|
||||||
|
set(this, ["inboxIssuePaginationInfo"], undefined);
|
||||||
const { workspaceSlug, projectId } = this.store.app.router;
|
const { workspaceSlug, projectId } = this.store.app.router;
|
||||||
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading");
|
||||||
};
|
};
|
||||||
@ -193,9 +209,7 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
fetchInboxIssues = async (workspaceSlug: string, projectId: string, loadingType: TLoader = undefined) => {
|
fetchInboxIssues = async (workspaceSlug: string, projectId: string, loadingType: TLoader = undefined) => {
|
||||||
try {
|
try {
|
||||||
if (loadingType) this.isLoading = loadingType;
|
if (loadingType) this.isLoading = loadingType;
|
||||||
else this.isLoading = "init-loading";
|
else if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading";
|
||||||
this.inboxIssuePaginationInfo = undefined;
|
|
||||||
this.inboxIssues = {};
|
|
||||||
|
|
||||||
const queryParams = this.inboxIssueQueryParams(
|
const queryParams = this.inboxIssueQueryParams(
|
||||||
this.inboxFilters,
|
this.inboxFilters,
|
||||||
@ -284,9 +298,9 @@ export class ProjectInboxStore implements IProjectInboxStore {
|
|||||||
// fetching reactions
|
// fetching reactions
|
||||||
await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId);
|
await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId);
|
||||||
// fetching activity
|
// fetching activity
|
||||||
await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId);
|
await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId);
|
||||||
// fetching comments
|
// fetching comments
|
||||||
await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId);
|
await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId);
|
||||||
runInAction(() => {
|
runInAction(() => {
|
||||||
set(this.inboxIssues, issueId, new InboxIssueStore(workspaceSlug, projectId, inboxIssue));
|
set(this.inboxIssues, issueId, new InboxIssueStore(workspaceSlug, projectId, inboxIssue));
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user