diff --git a/apiserver/plane/app/serializers/inbox.py b/apiserver/plane/app/serializers/inbox.py index 25b3c8cb3..e0c18b3d1 100644 --- a/apiserver/plane/app/serializers/inbox.py +++ b/apiserver/plane/app/serializers/inbox.py @@ -55,6 +55,9 @@ class InboxIssueSerializer(BaseSerializer): class InboxIssueDetailSerializer(BaseSerializer): issue = IssueDetailSerializer(read_only=True) + duplicate_issue_detail = IssueInboxSerializer( + read_only=True, source="duplicate_to" + ) class Meta: model = InboxIssue @@ -63,6 +66,7 @@ class InboxIssueDetailSerializer(BaseSerializer): "status", "duplicate_to", "snoozed_till", + "duplicate_issue_detail", "source", "issue", ] diff --git a/apiserver/plane/app/views/inbox/base.py b/apiserver/plane/app/views/inbox/base.py index e486052a3..0ca36a699 100644 --- a/apiserver/plane/app/views/inbox/base.py +++ b/apiserver/plane/app/views/inbox/base.py @@ -168,9 +168,8 @@ class InboxIssueViewSet(BaseViewSet): ).distinct() def list(self, request, slug, project_id): - workspace = Workspace.objects.filter(slug=slug).first() inbox_id = Inbox.objects.filter( - workspace_id=workspace.id, project_id=project_id + workspace__slug=slug, project_id=project_id ).first() filters = issue_filters(request.GET, "GET", "issue__") inbox_issue = ( @@ -264,9 +263,8 @@ class InboxIssueViewSet(BaseViewSet): notification=True, origin=request.META.get("HTTP_ORIGIN"), ) - workspace = Workspace.objects.filter(slug=slug).first() inbox_id = Inbox.objects.filter( - workspace_id=workspace.id, project_id=project_id + workspace__slug=slug, project_id=project_id ).first() # create an inbox issue inbox_issue = InboxIssue.objects.create( @@ -279,9 +277,8 @@ class InboxIssueViewSet(BaseViewSet): return Response(serializer.data, status=status.HTTP_200_OK) def partial_update(self, request, slug, project_id, issue_id): - workspace = Workspace.objects.filter(slug=slug).first() inbox_id = Inbox.objects.filter( - workspace_id=workspace.id, project_id=project_id + workspace__slug=slug, project_id=project_id ).first() inbox_issue = InboxIssue.objects.get( issue_id=issue_id, @@ -307,9 +304,12 @@ class InboxIssueViewSet(BaseViewSet): # Get issue data issue_data = request.data.pop("issue", False) - 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 if project_member.role <= 10: # viewers and guests since only viewers and guests @@ -406,9 +406,8 @@ class InboxIssueViewSet(BaseViewSet): return Response(serializer, status=status.HTTP_200_OK) def retrieve(self, request, slug, project_id, issue_id): - workspace = Workspace.objects.filter(slug=slug).first() inbox_id = Inbox.objects.filter( - workspace_id=workspace.id, project_id=project_id + workspace__slug=slug, project_id=project_id ).first() inbox_issue = ( InboxIssue.objects.select_related("issue") @@ -445,9 +444,8 @@ class InboxIssueViewSet(BaseViewSet): ) def destroy(self, request, slug, project_id, issue_id): - workspace = Workspace.objects.filter(slug=slug).first() inbox_id = Inbox.objects.filter( - workspace_id=workspace.id, project_id=project_id + workspace__slug=slug, project_id=project_id ).first() inbox_issue = InboxIssue.objects.get( issue_id=issue_id, diff --git a/apiserver/plane/utils/issue_filters.py b/apiserver/plane/utils/issue_filters.py index 18ef51937..531ef93ec 100644 --- a/apiserver/plane/utils/issue_filters.py +++ b/apiserver/plane/utils/issue_filters.py @@ -52,9 +52,9 @@ def string_date_filter( filter[f"{date_filter}__gte"] = now - timedelta(weeks=duration) else: if offset == "fromnow": - filter[f"{date_filter}__lte"] = now + timedelta(days=duration) + filter[f"{date_filter}__lte"] = now + timedelta(weeks=duration) 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): diff --git a/packages/types/src/inbox.d.ts b/packages/types/src/inbox.d.ts index 09c5353fd..cab9a2f29 100644 --- a/packages/types/src/inbox.d.ts +++ b/packages/types/src/inbox.d.ts @@ -18,7 +18,7 @@ export type TInboxIssueFilter = { } & { status: TInboxIssueStatus[] | undefined; priority: TIssuePriorities[] | undefined; - label: string[] | undefined; + labels: string[] | undefined; }; // sorting filters @@ -50,21 +50,29 @@ export type TInboxIssueSortingOrderByQueryParam = { }; export type TInboxIssuesQueryParams = { - [key in TInboxIssueFilter]: string; + [key in keyof TInboxIssueFilter]: string; } & TInboxIssueSortingOrderByQueryParam & { per_page: number; cursor: string; }; // inbox issue types + +export type TInboxDuplicateIssueDetails = { + id: string; + sequence_id: string; + name: string; +}; + export type TInboxIssue = { id: string; status: TInboxIssueStatus; - snoozed_till: Date | null; - duplicate_to: string | null; + snoozed_till: Date | undefined; + duplicate_to: string | undefined; source: string; issue: TIssue; created_by: string; + duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined; }; export type TInboxIssuePaginationInfo = TPaginationInfo & { diff --git a/web/components/inbox/content/inbox-issue-header.tsx b/web/components/inbox/content/inbox-issue-header.tsx index 4b1f7a114..1e0aaf16f 100644 --- a/web/components/inbox/content/inbox-issue-header.tsx +++ b/web/components/inbox/content/inbox-issue-header.tsx @@ -178,7 +178,7 @@ export const InboxIssueActionsHeader: FC = observer((p {getProjectById(issue.project_id)?.identifier}-{issue.sequence_id} )} - +
diff --git a/web/components/inbox/content/issue-properties.tsx b/web/components/inbox/content/issue-properties.tsx index 038c73476..e80ec9fd2 100644 --- a/web/components/inbox/content/issue-properties.tsx +++ b/web/components/inbox/content/issue-properties.tsx @@ -1,13 +1,16 @@ import React from "react"; import { observer } from "mobx-react"; -import { CalendarCheck2, Signal, Tag } from "lucide-react"; -import { TIssue } from "@plane/types"; -import { DoubleCircleIcon, UserGroupIcon } from "@plane/ui"; +import { useRouter } from "next/router"; +import { CalendarCheck2, CopyPlus, Signal, Tag } from "lucide-react"; +import { TInboxDuplicateIssueDetails, TIssue } from "@plane/types"; +import { ControlLink, DoubleCircleIcon, Tooltip, UserGroupIcon } from "@plane/ui"; // components import { DateDropdown, PriorityDropdown, MemberDropdown, StateDropdown } from "@/components/dropdowns"; import { IssueLabel, TIssueOperations } from "@/components/issues"; // helper import { getDate, renderFormattedPayloadDate } from "@/helpers/date-time.helper"; +// hooks +import { useProject } from "@/hooks/store"; type Props = { workspaceSlug: string; @@ -15,14 +18,18 @@ type Props = { issue: Partial; issueOperations: TIssueOperations; is_editable: boolean; + duplicateIssueDetails: TInboxDuplicateIssueDetails | undefined; }; export const InboxIssueProperties: React.FC = 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; minDate?.setDate(minDate.getDate()); - if (!issue || !issue?.id) return <>; return (
@@ -149,6 +156,29 @@ export const InboxIssueProperties: React.FC = observer((props) => { )}
+ + {/* duplicate to*/} + {duplicateIssueDetails && ( +
+
+ + Duplicate of +
+ + { + router.push(`/${workspaceSlug}/projects/${projectId}/issues/${duplicateIssueDetails?.id}`); + }} + > + + + {`${currentProjectDetails?.identifier}-${duplicateIssueDetails?.sequence_id}`} + + + +
+ )} diff --git a/web/components/inbox/content/issue-root.tsx b/web/components/inbox/content/issue-root.tsx index b493b573b..7a55195f1 100644 --- a/web/components/inbox/content/issue-root.tsx +++ b/web/components/inbox/content/issue-root.tsx @@ -164,6 +164,7 @@ export const InboxIssueMainContent: React.FC = observer((props) => { issue={issue} issueOperations={issueOperations} is_editable={is_editable} + duplicateIssueDetails={inboxIssue?.duplicate_issue_detail} />
diff --git a/web/components/inbox/content/root.tsx b/web/components/inbox/content/root.tsx index 12cfb836d..c15bf0863 100644 --- a/web/components/inbox/content/root.tsx +++ b/web/components/inbox/content/root.tsx @@ -35,6 +35,9 @@ export const InboxContentRoot: FC = observer((props) => { const is_editable = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; if (!inboxIssue) return <>; + + const isIssueAcceptedOrDeclined = [-1, 1].includes(inboxIssue.status); + return ( <>
@@ -51,7 +54,7 @@ export const InboxContentRoot: FC = observer((props) => { workspaceSlug={workspaceSlug} projectId={projectId} inboxIssue={inboxIssue} - is_editable={is_editable} + is_editable={is_editable && !isIssueAcceptedOrDeclined} isSubmitting={isSubmitting} setIsSubmitting={setIsSubmitting} /> diff --git a/web/components/inbox/inbox-filter/applied-filters/date.tsx b/web/components/inbox/inbox-filter/applied-filters/date.tsx index bce7a0487..dcac855b4 100644 --- a/web/components/inbox/inbox-filter/applied-filters/date.tsx +++ b/web/components/inbox/inbox-filter/applied-filters/date.tsx @@ -2,10 +2,10 @@ import { FC } from "react"; import { observer } from "mobx-react"; import { X } from "lucide-react"; import { TInboxIssueFilterDateKeys } from "@plane/types"; -// constants -import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters"; // helpers import { renderFormattedDate } from "@/helpers/date-time.helper"; +// constants +import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper"; // hooks import { useProjectInbox } from "@/hooks/store"; @@ -21,7 +21,7 @@ export const InboxIssueAppliedFiltersDate: FC = ob // derived values const filteredValues = inboxFilters?.[filterKey] || []; 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; const dateSplit = date.split(";"); return { diff --git a/web/components/inbox/inbox-filter/applied-filters/label.tsx b/web/components/inbox/inbox-filter/applied-filters/label.tsx index b028a1773..af121718c 100644 --- a/web/components/inbox/inbox-filter/applied-filters/label.tsx +++ b/web/components/inbox/inbox-filter/applied-filters/label.tsx @@ -13,13 +13,13 @@ export const InboxIssueAppliedFiltersLabel: FC = observer(() => { const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); const { getLabelById } = useLabel(); // derived values - const filteredValues = inboxFilters?.label || []; + const filteredValues = inboxFilters?.labels || []; const currentOptionDetail = (labelId: string) => getLabelById(labelId) || undefined; const handleFilterValue = (value: string): string[] => 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 <>; return ( @@ -36,7 +36,7 @@ export const InboxIssueAppliedFiltersLabel: FC = observer(() => {
{optionDetail?.name}
handleInboxIssueFilters("label", handleFilterValue(value))} + onClick={() => handleInboxIssueFilters("labels", handleFilterValue(value))} >
diff --git a/web/components/inbox/inbox-filter/applied-filters/root.tsx b/web/components/inbox/inbox-filter/applied-filters/root.tsx index 8baf86df2..7aa50b0eb 100644 --- a/web/components/inbox/inbox-filter/applied-filters/root.tsx +++ b/web/components/inbox/inbox-filter/applied-filters/root.tsx @@ -28,9 +28,9 @@ export const InboxIssueAppliedFilters: FC = observer(() => { {/* label */} {/* created_at */} - + {/* updated_at */} - +
); }); diff --git a/web/components/inbox/inbox-filter/applied-filters/status.tsx b/web/components/inbox/inbox-filter/applied-filters/status.tsx index a4ec1a37e..23351352d 100644 --- a/web/components/inbox/inbox-filter/applied-filters/status.tsx +++ b/web/components/inbox/inbox-filter/applied-filters/status.tsx @@ -9,7 +9,7 @@ import { useProjectInbox } from "@/hooks/store"; export const InboxIssueAppliedFiltersStatus: FC = observer(() => { // hooks - const { currentTab, inboxFilters, handleInboxIssueFilters } = useProjectInbox(); + const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); // derived values const filteredValues = inboxFilters?.status || []; 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[] => filteredValues?.includes(value) ? filteredValues.filter((v) => v !== value) : [...filteredValues, value]; - const clearFilter = () => handleInboxIssueFilters("status", undefined); - if (filteredValues.length === 0) return <>; return (
@@ -32,7 +30,7 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
{optionDetail?.title}
- {currentTab === "closed" && handleFilterValue(optionDetail?.status).length >= 1 && ( + {handleFilterValue(optionDetail?.status).length >= 1 && (
handleInboxIssueFilters("status", handleFilterValue(optionDetail?.status))} @@ -43,15 +41,6 @@ export const InboxIssueAppliedFiltersStatus: FC = observer(() => {
); })} - - {currentTab === "closed" && filteredValues.length > 1 && ( -
- -
- )}
); }); diff --git a/web/components/inbox/inbox-filter/filters/date.tsx b/web/components/inbox/inbox-filter/filters/date.tsx index 12aba01ef..50e9b71b0 100644 --- a/web/components/inbox/inbox-filter/filters/date.tsx +++ b/web/components/inbox/inbox-filter/filters/date.tsx @@ -1,6 +1,5 @@ import { FC, useState } from "react"; import concat from "lodash/concat"; -import pull from "lodash/pull"; import uniq from "lodash/uniq"; import { observer } from "mobx-react"; import { TInboxIssueFilterDateKeys } from "@plane/types"; @@ -8,7 +7,7 @@ import { TInboxIssueFilterDateKeys } from "@plane/types"; import { DateFilterModal } from "@/components/core"; import { FilterHeader, FilterOption } from "@/components/issues"; // constants -import { DATE_BEFORE_FILTER_OPTIONS } from "@/constants/filters"; +import { PAST_DURATION_FILTER_OPTIONS } from "@/helpers/inbox.helper"; // hooks import { useProjectInbox } from "@/hooks/store"; @@ -33,16 +32,15 @@ export const FilterDate: FC = observer((props) => { // derived values const filterValue: string[] = inboxFilters?.[filterKey] || []; 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()) ); - const handleFilterValue = (value: string): string[] => - filterValue?.includes(value) ? pull(filterValue, value) : uniq(concat(filterValue, value)); + 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) ? pull(finalOptions, v) : finalOptions.push(v))); + value.forEach((v) => (finalOptions?.includes(v) ? [] : finalOptions.push(v))); return uniq(finalOptions); }; @@ -53,10 +51,13 @@ export const FilterDate: FC = observer((props) => { const handleCustomDate = () => { if (isCustomDateSelected()) { - const updateAppliedFilters = filterValue?.filter((f) => isDate(f.split(";")[0])) || []; - handleInboxIssueFilters(filterKey, handleCustomFilterValue(updateAppliedFilters)); - } else setIsDateFilterModalOpen(true); + const updateAppliedFilters = filterValue?.filter((f) => !isDate(f.split(";")[0])) || []; + handleInboxIssueFilters(filterKey, updateAppliedFilters); + } else { + setIsDateFilterModalOpen(true); + } }; + return ( <> {isDateFilterModalOpen && ( @@ -82,10 +83,15 @@ export const FilterDate: FC = observer((props) => { isChecked={filterValue?.includes(option.value) ? true : false} onClick={() => handleInboxIssueFilters(filterKey, handleFilterValue(option.value))} title={option.name} - multiple + multiple={false} /> ))} - + ) : (

No matches found

diff --git a/web/components/inbox/inbox-filter/filters/filter-selection.tsx b/web/components/inbox/inbox-filter/filters/filter-selection.tsx index 46f959a2d..d7bebdc11 100644 --- a/web/components/inbox/inbox-filter/filters/filter-selection.tsx +++ b/web/components/inbox/inbox-filter/filters/filter-selection.tsx @@ -75,11 +75,11 @@ export const InboxIssueFilterSelection: FC = observer(() => { {/* Created at */}
- +
{/* Updated at */}
- +
diff --git a/web/components/inbox/inbox-filter/filters/labels.tsx b/web/components/inbox/inbox-filter/filters/labels.tsx index cf55623c8..8f6b765b5 100644 --- a/web/components/inbox/inbox-filter/filters/labels.tsx +++ b/web/components/inbox/inbox-filter/filters/labels.tsx @@ -24,7 +24,7 @@ export const FilterLabels: FC = observer((props) => { const { inboxFilters, handleInboxIssueFilters } = useProjectInbox(); - const filterValue = inboxFilters?.label || []; + const filterValue = inboxFilters?.labels || []; const appliedFiltersCount = filterValue?.length ?? 0; @@ -56,7 +56,7 @@ export const FilterLabels: FC = observer((props) => { handleInboxIssueFilters("label", handleFilterValue(label.id))} + onClick={() => handleInboxIssueFilters("labels", handleFilterValue(label.id))} icon={} title={label.name} /> diff --git a/web/components/inbox/inbox-filter/filters/status.tsx b/web/components/inbox/inbox-filter/filters/status.tsx index ca79d882f..b27f360da 100644 --- a/web/components/inbox/inbox-filter/filters/status.tsx +++ b/web/components/inbox/inbox-filter/filters/status.tsx @@ -24,8 +24,8 @@ export const FilterStatus: FC = observer((props) => { const appliedFiltersCount = filterValue?.length ?? 0; const filteredOptions = INBOX_STATUS.filter( (s) => - ((currentTab === "open" && [-2].includes(s.status)) || - (currentTab === "closed" && [-1, 0, 1, 2].includes(s.status))) && + ((currentTab === "open" && [-2, 0].includes(s.status)) || + (currentTab === "closed" && [-1, 1, 2].includes(s.status))) && s.key.includes(searchQuery.toLowerCase()) ); @@ -33,10 +33,8 @@ export const FilterStatus: FC = observer((props) => { filterValue?.includes(value) ? filterValue.filter((v) => v !== value) : [...filterValue, value]; const handleStatusFilterSelect = (status: TInboxIssueStatus) => { - if (currentTab === "closed") { - const selectedStatus = handleFilterValue(status); - if (selectedStatus.length >= 1) handleInboxIssueFilters("status", selectedStatus); - } + const selectedStatus = handleFilterValue(status); + if (selectedStatus.length >= 1) handleInboxIssueFilters("status", selectedStatus); }; return ( diff --git a/web/components/inbox/inbox-issue-status.tsx b/web/components/inbox/inbox-issue-status.tsx index e230b02d5..4077ba24c 100644 --- a/web/components/inbox/inbox-issue-status.tsx +++ b/web/components/inbox/inbox-issue-status.tsx @@ -32,14 +32,14 @@ export const InboxIssueStatus: React.FC = observer((props) => { )} >
- -
+ +
{inboxIssue?.status === 0 && inboxIssue?.snoozed_till ? inboxIssueStatusDetail.description(inboxIssue?.snoozed_till) : inboxIssueStatusDetail.title}
- {showDescription &&
{description}
} + {showDescription &&
{description}
}
); }); diff --git a/web/components/inbox/root.tsx b/web/components/inbox/root.tsx index f74fd5780..505729b2e 100644 --- a/web/components/inbox/root.tsx +++ b/web/components/inbox/root.tsx @@ -28,7 +28,7 @@ export const InboxIssueRoot: FC = observer((props) => { () => { inboxAccessible && workspaceSlug && projectId && fetchInboxIssues(workspaceSlug.toString(), projectId.toString()); }, - { revalidateOnFocus: false } + { revalidateOnFocus: false, revalidateIfStale: false } ); // loader diff --git a/web/components/inbox/sidebar/root.tsx b/web/components/inbox/sidebar/root.tsx index d1d08f017..109241bb0 100644 --- a/web/components/inbox/sidebar/root.tsx +++ b/web/components/inbox/sidebar/root.tsx @@ -99,7 +99,7 @@ export const InboxSidebar: FC = observer((props) => { - {isLoading && !inboxIssuePaginationInfo?.next_page_results ? ( + {isLoading === "filter-loading" && !inboxIssuePaginationInfo?.next_page_results ? ( ) : (
= (props) => { const labelResponse = await labelOperations.createLabel(workspaceSlug, projectId, formData); const currentLabels = [...(values || []), labelResponse.id]; await labelOperations.updateIssue(workspaceSlug, projectId, issueId, { label_ids: currentLabels }); + handleIsCreateToggle(); reset(defaultValues); } catch (error) { setToast({ diff --git a/web/helpers/inbox.helper.ts b/web/helpers/inbox.helper.ts new file mode 100644 index 000000000..9b2ac28e7 --- /dev/null +++ b/web/helpers/inbox.helper.ts @@ -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, + }, +]; diff --git a/web/store/inbox/inbox-issue.store.ts b/web/store/inbox/inbox-issue.store.ts index 1807645db..2e9a95266 100644 --- a/web/store/inbox/inbox-issue.store.ts +++ b/web/store/inbox/inbox-issue.store.ts @@ -2,7 +2,7 @@ import set from "lodash/set"; import { makeObservable, observable, runInAction, action } from "mobx"; // services // types -import { TIssue, TInboxIssue, TInboxIssueStatus } from "@plane/types"; +import { TIssue, TInboxIssue, TInboxIssueStatus, TInboxDuplicateIssueDetails } from "@plane/types"; import { InboxIssueService } from "@/services/inbox"; export interface IInboxIssueStore { @@ -13,6 +13,7 @@ export interface IInboxIssueStore { snoozed_till: Date | undefined; duplicate_to: string | undefined; created_by: string | undefined; + duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined; // actions updateInboxIssueStatus: (status: TInboxIssueStatus) => Promise; // accept, decline updateInboxIssueDuplicateTo: (issueId: string) => Promise; // connecting the inbox issue to the project existing issue @@ -29,6 +30,7 @@ export class InboxIssueStore implements IInboxIssueStore { snoozed_till: Date | undefined; duplicate_to: string | undefined; created_by: string | undefined; + duplicate_issue_detail: TInboxDuplicateIssueDetails | undefined = undefined; workspaceSlug: string; projectId: string; // services @@ -41,6 +43,7 @@ export class InboxIssueStore implements IInboxIssueStore { this.snoozed_till = data?.snoozed_till ? new Date(data.snoozed_till) : undefined; this.duplicate_to = data?.duplicate_to || undefined; this.created_by = data?.created_by || undefined; + this.duplicate_issue_detail = data?.duplicate_issue_detail || undefined; this.workspaceSlug = workspaceSlug; this.projectId = projectId; // services @@ -52,6 +55,7 @@ export class InboxIssueStore implements IInboxIssueStore { issue: observable, snoozed_till: observable, duplicate_to: observable, + duplicate_issue_detail: observable, created_by: observable, // actions updateInboxIssueStatus: action, diff --git a/web/store/inbox/project-inbox.store.ts b/web/store/inbox/project-inbox.store.ts index b871ccb98..43a1a229a 100644 --- a/web/store/inbox/project-inbox.store.ts +++ b/web/store/inbox/project-inbox.store.ts @@ -12,6 +12,8 @@ import { TInboxIssuePaginationInfo, TInboxIssueSortingOrderByQueryParam, } from "@plane/types"; +// helpers +import { EPastDurationFilters, getCustomDates } from "@/helpers/inbox.helper"; // services import { InboxIssueService } from "@/services/inbox"; // root store @@ -110,7 +112,7 @@ export class ProjectInboxStore implements IProjectInboxStore { get inboxIssuesArray() { 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) && Object.keys(inboxFilters).forEach((key) => { const filterKey = key as keyof TInboxIssueFilter; - if (inboxFilters[filterKey] && inboxFilters[filterKey]?.length) - filters[filterKey] = inboxFilters[filterKey]?.join(","); + if (inboxFilters[filterKey] && inboxFilters[filterKey]?.length) { + 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 = { @@ -167,7 +177,9 @@ export class ProjectInboxStore implements IProjectInboxStore { set(this, "inboxFilters", undefined); set(this, ["inboxSorting", "order_by"], "issue__created_at"); 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]); const { workspaceSlug, projectId } = this.store.app.router; if (workspaceSlug && projectId) this.fetchInboxIssues(workspaceSlug, projectId, "filter-loading"); @@ -175,12 +187,16 @@ export class ProjectInboxStore implements IProjectInboxStore { handleInboxIssueFilters = (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 = (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"); }; @@ -193,9 +209,7 @@ export class ProjectInboxStore implements IProjectInboxStore { fetchInboxIssues = async (workspaceSlug: string, projectId: string, loadingType: TLoader = undefined) => { try { if (loadingType) this.isLoading = loadingType; - else this.isLoading = "init-loading"; - this.inboxIssuePaginationInfo = undefined; - this.inboxIssues = {}; + else if (Object.keys(this.inboxIssues).length === 0) this.isLoading = "init-loading"; const queryParams = this.inboxIssueQueryParams( this.inboxFilters, @@ -284,9 +298,9 @@ export class ProjectInboxStore implements IProjectInboxStore { // fetching reactions await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId); // fetching activity - await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId); + await this.store.issue.issueDetail.fetchActivities(workspaceSlug, projectId, issueId); // fetching comments - await this.store.issue.issueDetail.fetchReactions(workspaceSlug, projectId, issueId); + await this.store.issue.issueDetail.fetchComments(workspaceSlug, projectId, issueId); runInAction(() => { set(this.inboxIssues, issueId, new InboxIssueStore(workspaceSlug, projectId, inboxIssue)); });