From 26040144fcccff21051753938555a1769c8f4ff1 Mon Sep 17 00:00:00 2001 From: guru_sainath Date: Tue, 2 Jul 2024 16:12:27 +0530 Subject: [PATCH] [WEB-1792] chore: integrated inbox issue in notification peek view and handled increment/decrement of unread notifications (#5008) * chore: added a boolean field in notification list * chore: notification filters changed * chore: handled inbox notification and typo on the card items * chore: handled notification count increment and decrement * chore: typos and ui updates --------- Co-authored-by: NarayanBavisetti --- .../plane/app/serializers/notification.py | 4 ++ .../plane/app/views/notification/base.py | 43 ++++--------- .../types/src/workspace-notifications.d.ts | 13 +++- .../(projects)/notifications/page.tsx | 41 ++++++++++-- .../inbox/content/inbox-issue-header.tsx | 56 ++++++++++------- web/core/components/inbox/content/root.tsx | 15 ++++- .../issues/peek-overview/loader.tsx | 2 +- .../notification-app-sidebar-option.tsx | 6 +- .../sidebar/header/root.tsx | 4 +- .../sidebar/loader.tsx | 2 +- .../sidebar/notification-card/item.tsx | 26 ++++++-- .../notification-card/options/archive.tsx | 2 +- .../workspace-notifications/sidebar/root.tsx | 4 +- web/core/store/notifications/notification.ts | 7 +++ .../workspace-notifications.store.ts | 63 +++++++++++++------ web/core/store/user/user-membership.store.ts | 11 ++++ 16 files changed, 202 insertions(+), 97 deletions(-) diff --git a/apiserver/plane/app/serializers/notification.py b/apiserver/plane/app/serializers/notification.py index c6713a354..248441bc8 100644 --- a/apiserver/plane/app/serializers/notification.py +++ b/apiserver/plane/app/serializers/notification.py @@ -3,11 +3,15 @@ from .base import BaseSerializer from .user import UserLiteSerializer from plane.db.models import Notification, UserNotificationPreference +# Third Party imports +from rest_framework import serializers + class NotificationSerializer(BaseSerializer): triggered_by_details = UserLiteSerializer( read_only=True, source="triggered_by" ) + is_inbox_issue = serializers.BooleanField(read_only=True) class Meta: model = Notification diff --git a/apiserver/plane/app/views/notification/base.py b/apiserver/plane/app/views/notification/base.py index 2ef8ab511..12997241c 100644 --- a/apiserver/plane/app/views/notification/base.py +++ b/apiserver/plane/app/views/notification/base.py @@ -47,10 +47,18 @@ class NotificationViewSet(BaseViewSet, BasePaginator): type = request.GET.get("type", "all") q_filters = Q() + inbox_issue = Issue.objects.filter( + pk=OuterRef("entity_identifier"), + issue_inbox__status__in=[0, 2, -2], + workspace__slug=self.kwargs.get("slug"), + ) + notifications = ( Notification.objects.filter( workspace__slug=slug, receiver_id=request.user.id ) + .filter(entity_name="issue") + .annotate(is_inbox_issue=Exists(inbox_issue)) .select_related("workspace", "project", "triggered_by", "receiver") .order_by("snoozed_till", "-created_at") ) @@ -202,46 +210,19 @@ class NotificationViewSet(BaseViewSet, BasePaginator): class UnreadNotificationEndpoint(BaseAPIView): def get(self, request, slug): # Watching Issues Count - subscribed_issues_count = Notification.objects.filter( + unread_notifications_count = Notification.objects.filter( workspace__slug=slug, receiver_id=request.user.id, read_at__isnull=True, archived_at__isnull=True, snoozed_till__isnull=True, - entity_identifier__in=IssueSubscriber.objects.filter( - workspace__slug=slug, subscriber_id=request.user.id - ).values_list("issue_id", flat=True), - ).count() - - # My Issues Count - my_issues_count = Notification.objects.filter( - workspace__slug=slug, - receiver_id=request.user.id, - read_at__isnull=True, - archived_at__isnull=True, - snoozed_till__isnull=True, - entity_identifier__in=IssueAssignee.objects.filter( - workspace__slug=slug, assignee_id=request.user.id - ).values_list("issue_id", flat=True), - ).count() - - # Created Issues Count - created_issues_count = Notification.objects.filter( - workspace__slug=slug, - receiver_id=request.user.id, - read_at__isnull=True, - archived_at__isnull=True, - snoozed_till__isnull=True, - entity_identifier__in=Issue.objects.filter( - workspace__slug=slug, created_by=request.user - ).values_list("pk", flat=True), ).count() return Response( { - "subscribed_issues": subscribed_issues_count, - "my_issues": my_issues_count, - "created_issues": created_issues_count, + "total_unread_notifications_count": int( + unread_notifications_count + ) }, status=status.HTTP_200_OK, ) diff --git a/packages/types/src/workspace-notifications.d.ts b/packages/types/src/workspace-notifications.d.ts index 95d93b890..0e5bb0975 100644 --- a/packages/types/src/workspace-notifications.d.ts +++ b/packages/types/src/workspace-notifications.d.ts @@ -50,6 +50,7 @@ export type TNotification = { read_at: string | undefined; archived_at: string | undefined; snoozed_till: string | undefined; + is_inbox_issue: boolean | undefined; workspace: string | undefined; project: string | undefined; created_at: string | undefined; @@ -84,7 +85,13 @@ export type TNotificationPaginatedInfo = { // notification count export type TUnreadNotificationsCount = { - created_issues: number | undefined; - my_issues: number | undefined; - subscribed_issues: number | undefined; + total_unread_notifications_count: number; +}; + +export type TCurrentSelectedNotification = { + workspace_slug: string | undefined; + project_id: string | undefined; + notification_id: string | undefined; + issue_id: string | undefined; + is_inbox_issue: boolean | undefined; }; diff --git a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx index 8a73d549f..0e29a84d1 100644 --- a/web/app/[workspaceSlug]/(projects)/notifications/page.tsx +++ b/web/app/[workspaceSlug]/(projects)/notifications/page.tsx @@ -3,19 +3,25 @@ import { observer } from "mobx-react"; import useSWR from "swr"; // components +import { LogoSpinner } from "@/components/common"; import { PageHead } from "@/components/core"; +import { InboxContentRoot } from "@/components/inbox"; import { IssuePeekOverview } from "@/components/issues"; // constants import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification"; // hooks -import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store"; +import { useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store"; const WorkspaceDashboardPage = observer(() => { // hooks const { currentWorkspace } = useWorkspace(); - const { notificationIdsByWorkspaceId, getNotifications } = useWorkspaceNotifications(); + const { currentSelectedNotification, notificationIdsByWorkspaceId, getNotifications } = useWorkspaceNotifications(); + const { + membership: { fetchUserProjectInfo }, + } = useUser(); // derived values const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Notifications` : undefined; + const { workspace_slug, project_id, issue_id, is_inbox_issue } = currentSelectedNotification; // fetch workspace notifications const notificationMutation = @@ -29,15 +35,42 @@ const WorkspaceDashboardPage = observer(() => { useSWR( currentWorkspace?.slug ? `WORKSPACE_NOTIFICATION` : null, currentWorkspace?.slug - ? async () => getNotifications(currentWorkspace?.slug, notificationMutation, notificationLoader) + ? () => getNotifications(currentWorkspace?.slug, notificationMutation, notificationLoader) : null ); + // fetching user project member info + const { isLoading: projectMemberInfoLoader } = useSWR( + workspace_slug && project_id && is_inbox_issue + ? `PROJECT_MEMBER_PERMISSION_INFO_${workspace_slug}_${project_id}` + : null, + workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null + ); + return ( <>
- + {is_inbox_issue === true && workspace_slug && project_id && issue_id ? ( + <> + {projectMemberInfoLoader ? ( +
+ +
+ ) : ( + {}} + isMobileSidebar={false} + workspaceSlug={workspace_slug} + projectId={project_id} + inboxIssueId={issue_id} + isNotificationEmbed + /> + )} + + ) : ( + + )}
); diff --git a/web/core/components/inbox/content/inbox-issue-header.tsx b/web/core/components/inbox/content/inbox-issue-header.tsx index f0c763543..9c46a3109 100644 --- a/web/core/components/inbox/content/inbox-issue-header.tsx +++ b/web/core/components/inbox/content/inbox-issue-header.tsx @@ -44,10 +44,19 @@ type TInboxIssueActionsHeader = { isSubmitting: "submitting" | "submitted" | "saved"; isMobileSidebar: boolean; setIsMobileSidebar: (value: boolean) => void; + isNotificationEmbed: boolean; }; export const InboxIssueActionsHeader: FC = observer((props) => { - const { workspaceSlug, projectId, inboxIssue, isSubmitting, isMobileSidebar, setIsMobileSidebar } = props; + const { + workspaceSlug, + projectId, + inboxIssue, + isSubmitting, + isMobileSidebar, + setIsMobileSidebar, + isNotificationEmbed = false, + } = props; // states const [isSnoozeDateModalOpen, setIsSnoozeDateModalOpen] = useState(false); const [selectDuplicateIssue, setSelectDuplicateIssue] = useState(false); @@ -58,7 +67,7 @@ export const InboxIssueActionsHeader: FC = observer((p const { currentTab, deleteInboxIssue, filteredInboxIssueIds } = useProjectInbox(); const { data: currentUser } = useUser(); const { - membership: { currentProjectRole }, + membership: { currentProjectRoleByProjectId }, } = useUser(); const router = useAppRouter(); @@ -66,6 +75,7 @@ export const InboxIssueActionsHeader: FC = observer((p const issue = inboxIssue?.issue; // derived values + const currentProjectRole = currentProjectRoleByProjectId(projectId) || undefined; const isAllowed = !!currentProjectRole && currentProjectRole >= EUserProjectRoles.MEMBER; const canMarkAsDuplicate = isAllowed && (inboxIssue?.status === 0 || inboxIssue?.status === -2); const canMarkAsAccepted = isAllowed && (inboxIssue?.status === 0 || inboxIssue?.status === -2); @@ -240,22 +250,24 @@ export const InboxIssueActionsHeader: FC = observer((p
-
- - -
+ {!isNotificationEmbed && ( +
+ + +
+ )}
{canMarkAsAccepted && ( @@ -313,12 +325,12 @@ export const InboxIssueActionsHeader: FC = observer((p {isAllowed && ( {canMarkAsAccepted && ( - +
- {inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 - ? "Un-snooze" - : "Snooze"} + {inboxIssue?.snoozed_till && numberOfDaysLeft && numberOfDaysLeft > 0 + ? "Un-snooze" + : "Snooze"}
)} diff --git a/web/core/components/inbox/content/root.tsx b/web/core/components/inbox/content/root.tsx index 97ec0d237..406568f48 100644 --- a/web/core/components/inbox/content/root.tsx +++ b/web/core/components/inbox/content/root.tsx @@ -15,10 +15,18 @@ type TInboxContentRoot = { inboxIssueId: string; isMobileSidebar: boolean; setIsMobileSidebar: (value: boolean) => void; + isNotificationEmbed?: boolean; }; export const InboxContentRoot: FC = observer((props) => { - const { workspaceSlug, projectId, inboxIssueId, isMobileSidebar, setIsMobileSidebar } = props; + const { + workspaceSlug, + projectId, + inboxIssueId, + isMobileSidebar, + setIsMobileSidebar, + isNotificationEmbed = false, + } = props; /// router const router = useAppRouter(); // states @@ -33,11 +41,11 @@ export const InboxContentRoot: FC = observer((props) => { const isIssueAvailable = getIsIssueAvailable(inboxIssueId?.toString() || ""); useEffect(() => { - if (!isIssueAvailable && inboxIssueId) { + if (!isIssueAvailable && inboxIssueId && !isNotificationEmbed) { router.replace(`/${workspaceSlug}/projects/${projectId}/inbox?currentTab=${currentTab}`); } // eslint-disable-next-line react-hooks/exhaustive-deps - }, [isIssueAvailable]); + }, [isIssueAvailable, isNotificationEmbed]); useSWR( workspaceSlug && projectId && inboxIssueId @@ -69,6 +77,7 @@ export const InboxContentRoot: FC = observer((props) => { projectId={projectId} inboxIssue={inboxIssue} isSubmitting={isSubmitting} + isNotificationEmbed={isNotificationEmbed || false} />
diff --git a/web/core/components/issues/peek-overview/loader.tsx b/web/core/components/issues/peek-overview/loader.tsx index 99a575f45..0fe64ec4c 100644 --- a/web/core/components/issues/peek-overview/loader.tsx +++ b/web/core/components/issues/peek-overview/loader.tsx @@ -16,7 +16,7 @@ export const IssuePeekOverviewLoader: FC = (props) => const { isMobile } = usePlatformOS(); return ( - +
diff --git a/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx b/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx index acde9b839..9e418df2a 100644 --- a/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx +++ b/web/core/components/workspace-notifications/notification-app-sidebar-option.tsx @@ -16,21 +16,21 @@ type TNotificationAppSidebarOption = { export const NotificationAppSidebarOption: FC = observer((props) => { const { workspaceSlug, isSidebarCollapsed } = props; // hooks - const { totalUnreadNotificationsCount, getUnreadNotificationsCount } = useWorkspaceNotifications(); + const { unreadNotificationsCount, getUnreadNotificationsCount } = useWorkspaceNotifications(); useSWR( workspaceSlug ? "WORKSPACE_UNREAD_NOTIFICATION_COUNT" : null, workspaceSlug ? () => getUnreadNotificationsCount(workspaceSlug) : null ); - if (totalUnreadNotificationsCount <= 0) return <>; + if (unreadNotificationsCount.total_unread_notifications_count <= 0) return <>; if (isSidebarCollapsed) return
; return (
- {getNumberCount(totalUnreadNotificationsCount)} + {getNumberCount(unreadNotificationsCount.total_unread_notifications_count)}
); }); diff --git a/web/core/components/workspace-notifications/sidebar/header/root.tsx b/web/core/components/workspace-notifications/sidebar/header/root.tsx index d84e61a99..e054934c0 100644 --- a/web/core/components/workspace-notifications/sidebar/header/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/header/root.tsx @@ -32,9 +32,7 @@ export const NotificationSidebarHeader: FC = observe label={
Notifications
-
- {notificationsCount} -
+
{notificationsCount}
} icon={} diff --git a/web/core/components/workspace-notifications/sidebar/loader.tsx b/web/core/components/workspace-notifications/sidebar/loader.tsx index 7485c2c4c..9889b221d 100644 --- a/web/core/components/workspace-notifications/sidebar/loader.tsx +++ b/web/core/components/workspace-notifications/sidebar/loader.tsx @@ -1,6 +1,6 @@ export const NotificationsLoader = () => (
- {[...Array(3)].map((i) => ( + {[...Array(8)].map((i) => (
diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx index 506665e13..19dc0b206 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/item.tsx @@ -3,6 +3,7 @@ import { FC, useState } from "react"; import { observer } from "mobx-react"; import { Clock } from "lucide-react"; +import { TCurrentSelectedNotification } from "@plane/types"; import { Avatar } from "@plane/ui"; // components import { NotificationOption } from "@/components/workspace-notifications"; @@ -12,7 +13,7 @@ import { calculateTimeAgo, renderFormattedDate, renderFormattedTime } from "@/he import { sanitizeCommentForNotification } from "@/helpers/notification.helper"; import { replaceUnderscoreIfSnakeCase, stripAndTruncateHTML } from "@/helpers/string.helper"; // hooks -import { useIssueDetail, useNotification } from "@/hooks/store"; +import { useIssueDetail, useNotification, useWorkspaceNotifications } from "@/hooks/store"; type TNotificationItem = { workspaceSlug: string; @@ -22,6 +23,7 @@ type TNotificationItem = { export const NotificationItem: FC = observer((props) => { const { workspaceSlug, notificationId } = props; // hooks + const { currentSelectedNotification, setCurrentSelectedNotification } = useWorkspaceNotifications(); const { asJson: notification, markNotificationAsRead } = useNotification(notificationId); const { getIsIssuePeeked, setPeekIssue } = useIssueDetail(); // states @@ -44,14 +46,28 @@ export const NotificationItem: FC = observer((props) => { !isSnoozeStateModalOpen && !customSnoozeModal ) { - setPeekIssue({ workspaceSlug, projectId, issueId }); + const currentSelectedNotificationPayload: TCurrentSelectedNotification = { + workspace_slug: workspaceSlug, + project_id: projectId, + issue_id: issueId, + notification_id: notification?.id, + is_inbox_issue: notification?.is_inbox_issue || false, + }; + setCurrentSelectedNotification(currentSelectedNotificationPayload); + // make the notification as read - if (notification.read_at === null) + if (notification.read_at === null) { try { await markNotificationAsRead(workspaceSlug); } catch (error) { console.error(error); } + } + + if (notification?.is_inbox_issue === false) { + setPeekIssue({ workspaceSlug, projectId, issueId }); + } else { + } } }; @@ -61,8 +77,10 @@ export const NotificationItem: FC = observer((props) => {
diff --git a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx index 395f0a75f..651404a2f 100644 --- a/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx +++ b/web/core/components/workspace-notifications/sidebar/notification-card/options/archive.tsx @@ -45,7 +45,7 @@ export const NotificationItemArchiveOption: FC = return ( {data.archived_at ? ( diff --git a/web/core/components/workspace-notifications/sidebar/root.tsx b/web/core/components/workspace-notifications/sidebar/root.tsx index 3fab32300..50c52c8e0 100644 --- a/web/core/components/workspace-notifications/sidebar/root.tsx +++ b/web/core/components/workspace-notifications/sidebar/root.tsx @@ -21,7 +21,7 @@ export const NotificationsSidebar: FC = observer(() => { const { workspaceSlug } = useParams(); // hooks const { getWorkspaceBySlug } = useWorkspace(); - const { paginationInfo, loader, notificationIdsByWorkspaceId } = useWorkspaceNotifications(); + const { unreadNotificationsCount, loader, notificationIdsByWorkspaceId } = useWorkspaceNotifications(); // derived values const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined; const notificationIds = workspace ? notificationIdsByWorkspaceId(workspace.id) : undefined; @@ -30,7 +30,7 @@ export const NotificationsSidebar: FC = observer(() => { const currentTabEmptyState = ENotificationTab.ALL ? EmptyStateType.NOTIFICATION_ALL_EMPTY_STATE : EmptyStateType.NOTIFICATION_MENTIONS_EMPTY_STATE; - const totalNotificationCount = paginationInfo?.total_count || 0; + const totalNotificationCount = unreadNotificationsCount.total_unread_notifications_count; if (!workspaceSlug || !workspace) return <>; return ( diff --git a/web/core/store/notifications/notification.ts b/web/core/store/notifications/notification.ts index dabb25d97..ae31c985e 100644 --- a/web/core/store/notifications/notification.ts +++ b/web/core/store/notifications/notification.ts @@ -41,6 +41,7 @@ export class Notification implements INotification { read_at: string | undefined = undefined; archived_at: string | undefined = undefined; snoozed_till: string | undefined = undefined; + is_inbox_issue: boolean | undefined = undefined; workspace: string | undefined = undefined; project: string | undefined = undefined; created_at: string | undefined = undefined; @@ -102,6 +103,7 @@ export class Notification implements INotification { this.archived_at = this.notification.archived_at; this.snoozed_till = this.notification.snoozed_till; this.workspace = this.notification.workspace; + this.is_inbox_issue = this.notification.is_inbox_issue; this.project = this.notification.project; this.created_at = this.notification.created_at; this.updated_at = this.notification.updated_at; @@ -131,6 +133,7 @@ export class Notification implements INotification { archived_at: this.archived_at, snoozed_till: this.snoozed_till, workspace: this.workspace, + is_inbox_issue: this.is_inbox_issue, project: this.project, created_at: this.created_at, updated_at: this.updated_at, @@ -187,6 +190,7 @@ export class Notification implements INotification { const payload: Partial = { read_at: new Date().toISOString(), }; + this.store.workspaceNotification.setUnreadNotificationsCount("decrement"); runInAction(() => this.mutateNotification(payload)); const notification = await workspaceNotificationService.markNotificationAsRead(workspaceSlug, this.id); if (notification) { @@ -195,6 +199,7 @@ export class Notification implements INotification { return notification; } catch (error) { runInAction(() => this.mutateNotification({ read_at: currentNotificationReadAt })); + this.store.workspaceNotification.setUnreadNotificationsCount("increment"); throw error; } }; @@ -212,6 +217,7 @@ export class Notification implements INotification { const payload: Partial = { read_at: undefined, }; + this.store.workspaceNotification.setUnreadNotificationsCount("increment"); runInAction(() => this.mutateNotification(payload)); const notification = await workspaceNotificationService.markNotificationAsUnread(workspaceSlug, this.id); if (notification) { @@ -219,6 +225,7 @@ export class Notification implements INotification { } return notification; } catch (error) { + this.store.workspaceNotification.setUnreadNotificationsCount("decrement"); runInAction(() => this.mutateNotification({ read_at: currentNotificationReadAt })); throw error; } diff --git a/web/core/store/notifications/workspace-notifications.store.ts b/web/core/store/notifications/workspace-notifications.store.ts index 1bdc30451..2cfe37990 100644 --- a/web/core/store/notifications/workspace-notifications.store.ts +++ b/web/core/store/notifications/workspace-notifications.store.ts @@ -1,8 +1,10 @@ import isEmpty from "lodash/isEmpty"; import set from "lodash/set"; -import { action, computed, makeObservable, observable, runInAction } from "mobx"; +import update from "lodash/update"; +import { action, makeObservable, observable, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; import { + TCurrentSelectedNotification, TNotification, TNotificationFilter, TNotificationPaginatedInfo, @@ -28,13 +30,13 @@ type TNotificationQueryParamType = ENotificationQueryParamType; export interface IWorkspaceNotificationStore { // observables loader: TNotificationLoader; - unreadNotificationsCount: TUnreadNotificationsCount | undefined; + unreadNotificationsCount: TUnreadNotificationsCount; notifications: Record; // notification_id -> notification currentNotificationTab: TNotificationTab; + currentSelectedNotification: TCurrentSelectedNotification; paginationInfo: Omit | undefined; filters: TNotificationFilter; // computed - totalUnreadNotificationsCount: number; // computed functions notificationIdsByWorkspaceId: (workspaceId: string) => string[] | undefined; // helper actions @@ -43,6 +45,8 @@ export interface IWorkspaceNotificationStore { updateBulkFilters: (filters: Partial) => void; // actions setCurrentNotificationTab: (tab: TNotificationTab) => void; + setCurrentSelectedNotification: (notification: TCurrentSelectedNotification) => void; + setUnreadNotificationsCount: (type: "increment" | "decrement") => void; getUnreadNotificationsCount: (workspaceSlug: string) => Promise; getNotifications: ( workspaceSlug: string, @@ -57,9 +61,18 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { paginatedCount = 30; // observables loader: TNotificationLoader = undefined; - unreadNotificationsCount: TUnreadNotificationsCount | undefined = undefined; + unreadNotificationsCount: TUnreadNotificationsCount = { + total_unread_notifications_count: 0, + }; notifications: Record = {}; currentNotificationTab: TNotificationTab = ENotificationTab.ALL; + currentSelectedNotification: TCurrentSelectedNotification = { + workspace_slug: undefined, + project_id: undefined, + notification_id: undefined, + issue_id: undefined, + is_inbox_issue: false, + }; paginationInfo: Omit | undefined = undefined; filters: TNotificationFilter = { type: { @@ -76,15 +89,17 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { makeObservable(this, { // observables loader: observable.ref, - unreadNotificationsCount: observable.ref, + unreadNotificationsCount: observable, notifications: observable, currentNotificationTab: observable.ref, + currentSelectedNotification: observable, paginationInfo: observable, filters: observable, // computed - totalUnreadNotificationsCount: computed, // helper actions setCurrentNotificationTab: action, + setCurrentSelectedNotification: action, + setUnreadNotificationsCount: action, mutateNotifications: action, updateFilters: action, updateBulkFilters: action, @@ -96,15 +111,6 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { } // computed - get totalUnreadNotificationsCount() { - let count: number = 0; - if (!this.unreadNotificationsCount) return count; - - Object.values(this.unreadNotificationsCount).forEach((value) => { - count += value || 0; - }); - return count; - } // computed functions /** @@ -151,16 +157,14 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { .map(([key]) => key) .join(",") || undefined; - const currentPage = this.paginationInfo ? Number(this.paginationInfo?.prev_cursor?.split(":")[1] || 0) + 1 : 0; - const queryCursorNext = paramType === ENotificationQueryParamType.INIT ? `${this.paginatedCount}:0:0` : paramType === ENotificationQueryParamType.CURRENT - ? `${this.paginatedCount}:${currentPage}:0` + ? `${this.paginatedCount}:${0}:0` : paramType === ENotificationQueryParamType.NEXT && this.paginationInfo ? this.paginationInfo?.next_cursor - : `${this.paginatedCount}:${currentPage}:0`; + : `${this.paginatedCount}:${0}:0`; const queryParams: TNotificationPaginatedInfoQueryParams = { type: queryParamsType, @@ -232,6 +236,27 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore { set(this, "currentNotificationTab", tab); }; + /** + * @description set current selected notification + * @param { TCurrentSelectedNotification } notification + * @returns { void } + */ + setCurrentSelectedNotification = (notification: TCurrentSelectedNotification): void => { + set(this, "currentSelectedNotification", notification); + }; + + /** + * @description set unread notifications count + * @param { "increment" | "decrement" } type + * @returns { void } + */ + setUnreadNotificationsCount = (type: "increment" | "decrement"): void => + runInAction(() => { + update(this.unreadNotificationsCount, "total_unread_notifications_count", (count: 0) => + type === "increment" ? count + 1 : count - 1 + ); + }); + /** * @description get unread notifications count * @param { string } workspaceSlug, diff --git a/web/core/store/user/user-membership.store.ts b/web/core/store/user/user-membership.store.ts index f9e9e6468..86f8c442b 100644 --- a/web/core/store/user/user-membership.store.ts +++ b/web/core/store/user/user-membership.store.ts @@ -34,6 +34,9 @@ export interface IUserMembershipStore { currentWorkspaceRole: EUserWorkspaceRoles | undefined; currentWorkspaceAllProjectsRole: IUserProjectsRole | undefined; + // computed functions + currentProjectRoleByProjectId: (projectId: string) => EUserProjectRoles | undefined; + hasPermissionToCurrentWorkspace: boolean | undefined; hasPermissionToCurrentProject: boolean | undefined; // fetch actions @@ -156,6 +159,14 @@ export class UserMembershipStore implements IUserMembershipStore { return this.hasPermissionToProject[this.router.projectId]; } + // computed functions + /** + * Returns the current project role by project id + * @param projectId + * @returns EUserProjectRoles + */ + currentProjectRoleByProjectId = (projectId: string) => this.projectMemberInfo[projectId]?.role || undefined; + /** * Fetches the current user workspace info * @param workspaceSlug