[WEB-1900] chore: mentions mutation, ui fix on app sidebar notification badge, and back button inbox issue notification embed (#5083)
* chore: mention notification boolean field * chore: handled mentions and all notification mutation and UI fix on the app sidebar notification badge and Back redirection button on inbox issue resposiveness * chore: Moved everthing to chip * chore: cleaning up the selection when we unmount the page * chore: resolved build error --------- Co-authored-by: NarayanBavisetti <narayan3119@gmail.com>
This commit is contained in:
parent
988201d729
commit
f617937542
14 changed files with 85 additions and 18 deletions
|
|
@ -12,6 +12,7 @@ class NotificationSerializer(BaseSerializer):
|
|||
read_only=True, source="triggered_by"
|
||||
)
|
||||
is_inbox_issue = serializers.BooleanField(read_only=True)
|
||||
is_mentioned_notification = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Notification
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
# Django imports
|
||||
from django.db.models import Exists, OuterRef, Q
|
||||
from django.db.models import Exists, OuterRef, Q, Case, When, BooleanField
|
||||
from django.utils import timezone
|
||||
|
||||
# Third party imports
|
||||
|
|
@ -60,6 +60,13 @@ class NotificationViewSet(BaseViewSet, BasePaginator):
|
|||
)
|
||||
.filter(entity_name="issue")
|
||||
.annotate(is_inbox_issue=Exists(inbox_issue))
|
||||
.annotate(
|
||||
is_mentioned_notification=Case(
|
||||
When(sender__icontains="mentioned", then=True),
|
||||
default=False,
|
||||
output_field=BooleanField(),
|
||||
)
|
||||
)
|
||||
.select_related("workspace", "project", "triggered_by", "receiver")
|
||||
.order_by("snoozed_till", "-created_at")
|
||||
)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ export type TNotification = {
|
|||
archived_at: string | undefined;
|
||||
snoozed_till: string | undefined;
|
||||
is_inbox_issue: boolean | undefined;
|
||||
is_mentioned_notification: boolean | undefined;
|
||||
workspace: string | undefined;
|
||||
project: string | undefined;
|
||||
created_at: string | undefined;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
"use client";
|
||||
|
||||
import { useEffect } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
|
|
@ -10,7 +11,7 @@ import { IssuePeekOverview } from "@/components/issues";
|
|||
// constants
|
||||
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
|
||||
// hooks
|
||||
import { useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { useIssueDetail, useUser, useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
|
||||
const WorkspaceDashboardPage = observer(() => {
|
||||
// hooks
|
||||
|
|
@ -25,6 +26,7 @@ const WorkspaceDashboardPage = observer(() => {
|
|||
const {
|
||||
membership: { fetchUserProjectInfo },
|
||||
} = useUser();
|
||||
const { setPeekIssue } = useIssueDetail();
|
||||
// derived values
|
||||
const pageTitle = currentWorkspace?.name ? `${currentWorkspace?.name} - Notifications` : undefined;
|
||||
const { workspace_slug, project_id, issue_id, is_inbox_issue } =
|
||||
|
|
@ -54,6 +56,15 @@ const WorkspaceDashboardPage = observer(() => {
|
|||
workspace_slug && project_id && is_inbox_issue ? () => fetchUserProjectInfo(workspace_slug, project_id) : null
|
||||
);
|
||||
|
||||
// clearing up the selected notifications when unmounting the page
|
||||
useEffect(
|
||||
() => () => {
|
||||
setCurrentSelectedNotificationId(undefined);
|
||||
setPeekIssue(undefined);
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<PageHead title={pageTitle} />
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import { IIssueDisplayFilterOptions, IIssueDisplayProperties, IIssueFilterOption
|
|||
import { Breadcrumbs, Button, LayersIcon, Tooltip } from "@plane/ui";
|
||||
// components
|
||||
import { ProjectAnalyticsModal } from "@/components/analytics";
|
||||
import { BreadcrumbLink, Logo } from "@/components/common";
|
||||
import { BreadcrumbLink, CountChip, Logo } from "@/components/common";
|
||||
import { DisplayFiltersSelection, FiltersDropdown, FilterSelection, LayoutSelection } from "@/components/issues";
|
||||
// constants
|
||||
import {
|
||||
|
|
@ -161,9 +161,7 @@ export const ProjectIssuesHeader = observer(() => {
|
|||
tooltipContent={`There are ${issuesCount} ${issuesCount > 1 ? "issues" : "issue"} in this project`}
|
||||
position="bottom"
|
||||
>
|
||||
<span className="cursor-default flex items-center text-center justify-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl">
|
||||
{issuesCount}
|
||||
</span>
|
||||
<CountChip count={issuesCount} />
|
||||
</Tooltip>
|
||||
) : null}
|
||||
</div>
|
||||
|
|
|
|||
25
web/core/components/common/count-chip.tsx
Normal file
25
web/core/components/common/count-chip.tsx
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
//
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
|
||||
type TCountChip = {
|
||||
count: string | number;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
export const CountChip: FC<TCountChip> = (props) => {
|
||||
const { count, className = "" } = props;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative flex justify-center items-center px-2.5 py-0.5 flex-shrink-0 bg-custom-primary-100/20 text-custom-primary-100 text-xs font-semibold rounded-xl",
|
||||
className
|
||||
)}
|
||||
>
|
||||
{count}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -4,3 +4,4 @@ export * from "./latest-feature-block";
|
|||
export * from "./breadcrumb-link";
|
||||
export * from "./logo-spinner";
|
||||
export * from "./logo";
|
||||
export * from "./count-chip";
|
||||
|
|
|
|||
|
|
@ -387,6 +387,8 @@ export const InboxIssueActionsHeader: FC<TInboxIssueActionsHeader> = observer((p
|
|||
workspaceSlug={workspaceSlug}
|
||||
isMobileSidebar={isMobileSidebar}
|
||||
setIsMobileSidebar={setIsMobileSidebar}
|
||||
isNotificationEmbed={isNotificationEmbed}
|
||||
embedRemoveCurrentNotification={embedRemoveCurrentNotification}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
Link,
|
||||
Trash2,
|
||||
PanelLeft,
|
||||
MoveRight,
|
||||
} from "lucide-react";
|
||||
import { CustomMenu } from "@plane/ui";
|
||||
// components
|
||||
|
|
@ -44,6 +45,8 @@ type Props = {
|
|||
handleCopyIssueLink: () => void;
|
||||
isMobileSidebar: boolean;
|
||||
setIsMobileSidebar: (value: boolean) => void;
|
||||
isNotificationEmbed: boolean;
|
||||
embedRemoveCurrentNotification?: () => void;
|
||||
};
|
||||
|
||||
export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) => {
|
||||
|
|
@ -65,6 +68,8 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
|
|||
handleCopyIssueLink,
|
||||
isMobileSidebar,
|
||||
setIsMobileSidebar,
|
||||
isNotificationEmbed,
|
||||
embedRemoveCurrentNotification,
|
||||
} = props;
|
||||
const router = useAppRouter();
|
||||
const issue = inboxIssue?.issue;
|
||||
|
|
@ -76,6 +81,11 @@ export const InboxIssueActionsMobileHeader: React.FC<Props> = observer((props) =
|
|||
|
||||
return (
|
||||
<div className="h-12 relative flex border-custom-border-200 w-full items-center gap-2 px-4">
|
||||
{isNotificationEmbed && (
|
||||
<button onClick={embedRemoveCurrentNotification}>
|
||||
<MoveRight className="h-4 w-4 text-custom-text-300 hover:text-custom-text-200" />
|
||||
</button>
|
||||
)}
|
||||
<PanelLeft
|
||||
onClick={() => setIsMobileSidebar(!isMobileSidebar)}
|
||||
className={cn(
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import useSWR from "swr";
|
||||
// components
|
||||
import { CountChip } from "@/components/common";
|
||||
// helpers
|
||||
import { getNumberCount } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
|
|
@ -35,8 +37,8 @@ export const NotificationAppSidebarOption: FC<TNotificationAppSidebarOption> = o
|
|||
return <div className="absolute right-3.5 top-2 h-2 w-2 rounded-full bg-custom-primary-300" />;
|
||||
|
||||
return (
|
||||
<div className="text-[8px] ml-auto bg-custom-primary-100 text-white p-1 py-0.5 rounded-full">
|
||||
{`${isMentionsEnabled ? `@` : ``}${getNumberCount(totalNotifications)}`}
|
||||
<div className="ml-auto">
|
||||
<CountChip count={`${isMentionsEnabled ? `@` : ``}${getNumberCount(totalNotifications)}`} />
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ export const NotificationItem: FC<TNotificationItem> = observer((props) => {
|
|||
|
||||
const handleNotificationIssuePeekOverview = async () => {
|
||||
if (workspaceSlug && projectId && issueId && !isSnoozeStateModalOpen && !customSnoozeModal) {
|
||||
setPeekIssue(undefined);
|
||||
setCurrentSelectedNotificationId(notificationId);
|
||||
|
||||
// make the notification as read
|
||||
|
|
@ -51,7 +52,6 @@ export const NotificationItem: FC<TNotificationItem> = observer((props) => {
|
|||
|
||||
if (notification?.is_inbox_issue === false) {
|
||||
!getIsIssuePeeked(issueId) && setPeekIssue({ workspaceSlug, projectId, issueId });
|
||||
} else {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { FC } from "react";
|
|||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { CountChip } from "@/components/common";
|
||||
import {
|
||||
NotificationsLoader,
|
||||
NotificationEmptyState,
|
||||
|
|
@ -65,14 +66,12 @@ export const NotificationsSidebar: FC = observer(() => {
|
|||
)}
|
||||
>
|
||||
<div className="font-medium">{tab.label}</div>
|
||||
<div
|
||||
className={cn(
|
||||
`rounded-full text-xs px-1.5 py-0.5`,
|
||||
<CountChip
|
||||
count={getNumberCount(tab.count(unreadNotificationsCount))}
|
||||
className={
|
||||
currentNotificationTab === tab.value ? `bg-custom-primary-100/20` : `bg-custom-background-80/50`
|
||||
)}
|
||||
>
|
||||
{getNumberCount(tab.count(unreadNotificationsCount))}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
{currentNotificationTab === tab.value && (
|
||||
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ export class Notification implements INotification {
|
|||
archived_at: string | undefined = undefined;
|
||||
snoozed_till: string | undefined = undefined;
|
||||
is_inbox_issue: boolean | undefined = undefined;
|
||||
is_mentioned_notification: boolean | undefined = undefined;
|
||||
workspace: string | undefined = undefined;
|
||||
project: string | undefined = undefined;
|
||||
created_at: string | undefined = undefined;
|
||||
|
|
@ -70,6 +71,8 @@ export class Notification implements INotification {
|
|||
read_at: observable.ref,
|
||||
archived_at: observable.ref,
|
||||
snoozed_till: observable.ref,
|
||||
is_inbox_issue: observable.ref,
|
||||
is_mentioned_notification: observable.ref,
|
||||
workspace: observable.ref,
|
||||
project: observable.ref,
|
||||
created_at: observable.ref,
|
||||
|
|
@ -102,8 +105,9 @@ export class Notification implements INotification {
|
|||
this.read_at = this.notification.read_at;
|
||||
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.is_mentioned_notification = this.notification.is_mentioned_notification;
|
||||
this.workspace = this.notification.workspace;
|
||||
this.project = this.notification.project;
|
||||
this.created_at = this.notification.created_at;
|
||||
this.updated_at = this.notification.updated_at;
|
||||
|
|
@ -132,8 +136,9 @@ export class Notification implements INotification {
|
|||
read_at: this.read_at,
|
||||
archived_at: this.archived_at,
|
||||
snoozed_till: this.snoozed_till,
|
||||
workspace: this.workspace,
|
||||
is_inbox_issue: this.is_inbox_issue,
|
||||
is_mentioned_notification: this.is_mentioned_notification,
|
||||
workspace: this.workspace,
|
||||
project: this.project,
|
||||
created_at: this.created_at,
|
||||
updated_at: this.updated_at,
|
||||
|
|
|
|||
|
|
@ -125,6 +125,11 @@ export class WorkspaceNotificationStore implements IWorkspaceNotificationStore {
|
|||
);
|
||||
const workspaceNotificationIds = workspaceNotifications
|
||||
.filter((n) => n.workspace === workspaceId)
|
||||
.filter((n) =>
|
||||
this.currentNotificationTab === ENotificationTab.MENTIONS
|
||||
? n.is_mentioned_notification
|
||||
: !n.is_mentioned_notification
|
||||
)
|
||||
.filter((n) => {
|
||||
if (!this.filters.archived && !this.filters.snoozed) {
|
||||
if (n.archived_at) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue