mirror of
https://github.com/makeplane/plane.git
synced 2026-07-12 21:40:18 +02:00
chore:reduced restrucured files. Refractored code in notifications store
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useCallback } from "react";
|
||||
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
@@ -19,8 +19,10 @@ import { cn } from "@/helpers/common.helper";
|
||||
import { getNumberCount } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
import { useFlag } from "@/plane-web/hooks/store";
|
||||
|
||||
import { NotificationCardListRoot } from "@/plane-web/components/workspace-notifications";
|
||||
import { NotificationCardListRoot as NotificationCardListRootCe } from "ce/components/workspace-notifications";
|
||||
import { NotificationCardListRoot as NotificationCardListRootEe } from "ee/components/workspace-notifications";
|
||||
|
||||
export const NotificationsSidebarRoot: FC = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
@@ -47,16 +49,18 @@ export const NotificationsSidebarRoot: FC = observer(() => {
|
||||
[currentNotificationTab, setCurrentNotificationTab]
|
||||
);
|
||||
|
||||
const isFeatureEnabled = useFlag(workspaceSlug.toString(), "INBOX_STACKING");
|
||||
|
||||
if (!workspaceSlug || !workspace) return <></>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative border-0 md:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all overflow-hidden",
|
||||
"relative border-0 md:border-r border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all max-md:overflow-hidden",
|
||||
currentSelectedNotificationId ? "w-0 md:w-2/6" : "w-full md:w-2/6"
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full h-full overflow-hidden flex flex-col">
|
||||
<div className="relative w-full h-full flex flex-col">
|
||||
<Row className="h-[3.75rem] border-b border-custom-border-200 flex">
|
||||
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
|
||||
</Row>
|
||||
@@ -100,7 +104,11 @@ export const NotificationsSidebarRoot: FC = observer(() => {
|
||||
<>
|
||||
{notificationIds && notificationIds.length > 0 ? (
|
||||
<ContentWrapper variant={ERowVariant.HUGGING}>
|
||||
<NotificationCardListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
|
||||
{isFeatureEnabled ? (
|
||||
<NotificationCardListRootEe workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
|
||||
) : (
|
||||
<NotificationCardListRootCe workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
|
||||
)}
|
||||
</ContentWrapper>
|
||||
) : (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
|
||||
@@ -1,2 +1 @@
|
||||
export * from "./root";
|
||||
export * from "./notification-card";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { FC, useMemo, useState, Fragment } from "react";
|
||||
import { uniq } from "lodash";
|
||||
import orderBy from "lodash/orderBy";
|
||||
@@ -27,25 +29,29 @@ export const NotificationItem: FC<INotificationItem> = observer((props) => {
|
||||
const [isSnoozeStateModalOpen, setIsSnoozeStateModalOpen] = useState(false);
|
||||
const [customSnoozeModal, setCustomSnoozeModal] = useState(false);
|
||||
//hooks
|
||||
const { groupedNotificationsByIssueId, markNotificationGroupRead, hasInboxIssue, setCurrentSelectedNotificationId } =
|
||||
useWorkspaceNotifications();
|
||||
const {
|
||||
getNotificationsGroupedByIssue,
|
||||
markBulkNotificationsAsRead,
|
||||
containsInboxIssue,
|
||||
setCurrentSelectedNotificationId,
|
||||
} = useWorkspaceNotifications();
|
||||
const { getIsIssuePeeked, setPeekIssue } = useIssueDetail();
|
||||
|
||||
//derived values
|
||||
const groupedNotifications = groupedNotificationsByIssueId(workspaceId);
|
||||
const notificationGroup = groupedNotifications[issueId];
|
||||
const issue = notificationGroup[0].data?.issue;
|
||||
const unreadCount = notificationGroup.filter((e) => !e.read_at).length;
|
||||
const projectId = notificationGroup[0].project;
|
||||
const groupedNotifications = getNotificationsGroupedByIssue(workspaceId);
|
||||
const notificationList = groupedNotifications[issueId];
|
||||
const issue = notificationList[0].data?.issue;
|
||||
const unreadCount = notificationList.filter((e) => !e.read_at).length;
|
||||
const projectId = notificationList[0].project;
|
||||
|
||||
const authorIds: string[] = uniq(
|
||||
notificationGroup.map((e) => e.triggered_by).filter((id): id is string => id != undefined && id != null)
|
||||
notificationList.map((e) => e.triggered_by).filter((id): id is string => id != undefined && id != null)
|
||||
);
|
||||
|
||||
const latestNotificationTime = useMemo(() => {
|
||||
const latestNotification = orderBy(notificationGroup, (n) => convertToEpoch(n.created_at), "desc")[0];
|
||||
const latestNotification = orderBy(notificationList, (n) => convertToEpoch(n.created_at), "desc")[0];
|
||||
if (latestNotification.created_at) return calculateTimeAgo(latestNotification.created_at);
|
||||
}, [notificationGroup]);
|
||||
}, [notificationList]);
|
||||
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "right-start",
|
||||
@@ -54,18 +60,18 @@ export const NotificationItem: FC<INotificationItem> = observer((props) => {
|
||||
const handleNotificationIssuePeekOverview = async () => {
|
||||
if (workspaceSlug && projectId && issueId && !isSnoozeStateModalOpen && !customSnoozeModal) {
|
||||
setPeekIssue(undefined);
|
||||
setCurrentSelectedNotificationId(notificationGroup[0].id);
|
||||
setCurrentSelectedNotificationId(notificationList[0].id);
|
||||
|
||||
// make the notification as read
|
||||
if (unreadCount > 0) {
|
||||
try {
|
||||
await markNotificationGroupRead(notificationGroup, workspaceSlug);
|
||||
await markBulkNotificationsAsRead(notificationList, workspaceSlug);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasInboxIssue(notificationGroup)) {
|
||||
if (!containsInboxIssue(notificationList)) {
|
||||
if (!getIsIssuePeeked(issueId)) setPeekIssue({ workspaceSlug, projectId, issueId });
|
||||
}
|
||||
}
|
||||
@@ -74,7 +80,7 @@ export const NotificationItem: FC<INotificationItem> = observer((props) => {
|
||||
// states
|
||||
const [showPreview, setShowPreview] = useState<boolean>(false);
|
||||
|
||||
if (!notificationGroup || !issue || !issue.id || !authorIds || !projectId) return <></>;
|
||||
if (!notificationList || !issue || !issue.id || !authorIds || !projectId) return <></>;
|
||||
|
||||
return (
|
||||
<Popover as="div" className={""}>
|
||||
@@ -107,7 +113,7 @@ export const NotificationItem: FC<INotificationItem> = observer((props) => {
|
||||
workspaceSlug={workspaceSlug}
|
||||
issueId={issueId}
|
||||
unreadCount={unreadCount}
|
||||
notificationGroup={notificationGroup}
|
||||
notificationList={notificationList}
|
||||
isSnoozeStateModalOpen={isSnoozeStateModalOpen}
|
||||
setIsSnoozeStateModalOpen={setIsSnoozeStateModalOpen}
|
||||
customSnoozeModal={customSnoozeModal}
|
||||
@@ -147,7 +153,7 @@ export const NotificationItem: FC<INotificationItem> = observer((props) => {
|
||||
<Popover.Panel {...attributes.popper} className={""}>
|
||||
<div ref={setPopperElement} className={"absolute z-10"} style={styles.popper}>
|
||||
<NotificationCardPreview
|
||||
notificationGroup={notificationGroup}
|
||||
notificationList={notificationList}
|
||||
workspaceSlug={workspaceSlug}
|
||||
projectId={projectId}
|
||||
issueData={issue}
|
||||
|
||||
@@ -15,25 +15,25 @@ import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
type TNotificationItemArchiveOption = {
|
||||
workspaceSlug: string;
|
||||
notificationGroup: INotification[];
|
||||
notificationList: INotification[];
|
||||
issueId: string;
|
||||
};
|
||||
|
||||
export const NotificationItemArchiveOption: FC<TNotificationItemArchiveOption> = observer((props) => {
|
||||
const { workspaceSlug, notificationGroup, issueId } = props;
|
||||
const { workspaceSlug, notificationList, issueId } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
|
||||
//derived values
|
||||
const archivedCount = notificationGroup.filter(n=>!!n.archived_at).length;
|
||||
const archivedCount = notificationList.filter((n) => !!n.archived_at).length;
|
||||
|
||||
const { archiveNotificationGroup, unArchiveNotificationGroup } = useWorkspaceNotifications()
|
||||
const { archiveNotificationList, unArchiveNotificationList } = useWorkspaceNotifications();
|
||||
|
||||
const handleNotificationUpdate = async () => {
|
||||
try {
|
||||
const request = archivedCount > 0 ? unArchiveNotificationGroup : archiveNotificationGroup;
|
||||
await request(notificationGroup,workspaceSlug);
|
||||
const request = archivedCount > 0 ? unArchiveNotificationList : archiveNotificationList;
|
||||
await request(notificationList, workspaceSlug);
|
||||
captureEvent(NOTIFICATION_ARCHIVED, {
|
||||
issue_id: issueId,
|
||||
tab: currentNotificationTab,
|
||||
|
||||
@@ -15,23 +15,23 @@ import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
type TNotificationItemReadOption = {
|
||||
workspaceSlug: string;
|
||||
notificationGroup: INotification[];
|
||||
notificationList: INotification[];
|
||||
issueId: string;
|
||||
unreadCount: number
|
||||
unreadCount: number;
|
||||
};
|
||||
|
||||
export const NotificationItemReadOption: FC<TNotificationItemReadOption> = observer((props) => {
|
||||
const { workspaceSlug, notificationGroup, issueId, unreadCount } = props;
|
||||
const { workspaceSlug, notificationList, issueId, unreadCount } = props;
|
||||
// hooks
|
||||
const { captureEvent } = useEventTracker();
|
||||
const { currentNotificationTab } = useWorkspaceNotifications();
|
||||
|
||||
const { markNotificationGroupRead, markNotificationGroupUnRead } = useWorkspaceNotifications();
|
||||
const { markBulkNotificationsAsRead, markBulkNotificationsAsUnread } = useWorkspaceNotifications();
|
||||
|
||||
const handleNotificationUpdate = async () => {
|
||||
try {
|
||||
const request = unreadCount === 0 ? markNotificationGroupUnRead : markNotificationGroupRead;
|
||||
await request(notificationGroup,workspaceSlug);
|
||||
const request = unreadCount === 0 ? markBulkNotificationsAsUnread : markBulkNotificationsAsRead;
|
||||
await request(notificationList, workspaceSlug);
|
||||
captureEvent(NOTIFICATIONS_READ, {
|
||||
issue_id: issueId,
|
||||
tab: currentNotificationTab,
|
||||
|
||||
@@ -6,7 +6,6 @@ import { observer } from "mobx-react";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
// hooks
|
||||
import { useNotification } from "@/hooks/store";
|
||||
import {
|
||||
NotificationItemReadOption,
|
||||
NotificationItemArchiveOption,
|
||||
@@ -16,7 +15,7 @@ import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
type TNotificationOption = {
|
||||
workspaceSlug: string;
|
||||
notificationGroup: INotification[];
|
||||
notificationList: INotification[];
|
||||
issueId: string;
|
||||
unreadCount: number;
|
||||
isSnoozeStateModalOpen: boolean;
|
||||
@@ -28,7 +27,7 @@ type TNotificationOption = {
|
||||
export const NotificationOption: FC<TNotificationOption> = observer((props) => {
|
||||
const {
|
||||
workspaceSlug,
|
||||
notificationGroup,
|
||||
notificationList,
|
||||
issueId,
|
||||
unreadCount,
|
||||
isSnoozeStateModalOpen,
|
||||
@@ -43,7 +42,7 @@ export const NotificationOption: FC<TNotificationOption> = observer((props) => {
|
||||
{/* read */}
|
||||
<NotificationItemReadOption
|
||||
workspaceSlug={workspaceSlug}
|
||||
notificationGroup={notificationGroup}
|
||||
notificationList={notificationList}
|
||||
unreadCount={unreadCount}
|
||||
issueId={issueId}
|
||||
/>
|
||||
@@ -51,14 +50,14 @@ export const NotificationOption: FC<TNotificationOption> = observer((props) => {
|
||||
{/* archive */}
|
||||
<NotificationItemArchiveOption
|
||||
workspaceSlug={workspaceSlug}
|
||||
notificationGroup={notificationGroup}
|
||||
notificationList={notificationList}
|
||||
issueId={issueId}
|
||||
/>
|
||||
|
||||
{/* snooze notification */}
|
||||
<NotificationItemSnoozeOption
|
||||
workspaceSlug={workspaceSlug}
|
||||
notificationGroup={notificationGroup}
|
||||
notificationList={notificationList}
|
||||
setIsSnoozeStateModalOpen={setIsSnoozeStateModalOpen}
|
||||
customSnoozeModal={customSnoozeModal}
|
||||
setCustomSnoozeModal={setCustomSnoozeModal}
|
||||
|
||||
@@ -18,25 +18,24 @@ import { INotification } from "@/store/notifications/notification";
|
||||
|
||||
type TNotificationItemSnoozeOption = {
|
||||
workspaceSlug: string;
|
||||
notificationGroup: INotification[];
|
||||
notificationList: INotification[];
|
||||
setIsSnoozeStateModalOpen: Dispatch<SetStateAction<boolean>>;
|
||||
customSnoozeModal: boolean;
|
||||
setCustomSnoozeModal: Dispatch<SetStateAction<boolean>>;
|
||||
};
|
||||
|
||||
export const NotificationItemSnoozeOption: FC<TNotificationItemSnoozeOption> = observer((props) => {
|
||||
const { workspaceSlug, notificationGroup, setIsSnoozeStateModalOpen, customSnoozeModal, setCustomSnoozeModal } =
|
||||
props;
|
||||
const { workspaceSlug, notificationList, setIsSnoozeStateModalOpen, customSnoozeModal, setCustomSnoozeModal } = props;
|
||||
// hooks
|
||||
const { isMobile } = usePlatformOS();
|
||||
const { snoozeNotificationGroup, unSnoozeNotificationGroup } = useWorkspaceNotifications();
|
||||
const { snoozeNotificationList, unSnoozeNotificationList } = useWorkspaceNotifications();
|
||||
|
||||
const snoozedCount = notificationGroup.filter((n) => !!n.snoozed_till).length;
|
||||
const snoozedCount = notificationList.filter((n) => !!n.snoozed_till).length;
|
||||
|
||||
const handleNotificationSnoozeDate = async (snoozeTill: Date | undefined) => {
|
||||
if (snoozedCount === 0 && snoozeTill) {
|
||||
try {
|
||||
await snoozeNotificationGroup(notificationGroup, workspaceSlug, snoozeTill);
|
||||
await snoozeNotificationList(notificationList, workspaceSlug, snoozeTill);
|
||||
setToast({
|
||||
title: "Success!",
|
||||
message: "Notification(s) snoozed successfully",
|
||||
@@ -47,7 +46,7 @@ export const NotificationItemSnoozeOption: FC<TNotificationItemSnoozeOption> = o
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await unSnoozeNotificationGroup(notificationGroup, workspaceSlug);
|
||||
await unSnoozeNotificationList(notificationList, workspaceSlug);
|
||||
setToast({
|
||||
title: "Success!",
|
||||
message: "Notification(s) un snoozed successfully",
|
||||
|
||||
@@ -30,28 +30,29 @@ type TIssueActivityBlock = {
|
||||
triggeredBy: IUserLite | undefined;
|
||||
};
|
||||
|
||||
export const acitvityIconsMap: Record<string, ReactElement> = {
|
||||
state: <LayersIcon width={14} height={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
name: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
description: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
assignees: <Users className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-200" />,
|
||||
priority: <Signal size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
estimate_point: <Triangle size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
parent: <LayoutPanelTop size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
start_date: <CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
target_date: <CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
cycles: <ContrastIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
modules: <DiceIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
labels: <Tag size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
link: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
attachment: <Paperclip size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
archived_at: <ArchiveIcon className="h-3.5 w-3.5 text-custom-text-200" aria-hidden="true" />,
|
||||
inbox: <Intake className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
type: <ArrowRightLeft className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-200" />,
|
||||
};
|
||||
|
||||
export const IssueActivityBlock: FC<TIssueActivityBlock> = (props) => {
|
||||
const { ends, children, createdAt, notificationField, triggeredBy } = props;
|
||||
|
||||
const acitvityIconsMap: Record<string, ReactElement> = {
|
||||
state: <LayersIcon width={14} height={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
name: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
description: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
assignees: <Users className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-200" />,
|
||||
priority: <Signal size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
estimate_point: <Triangle size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
parent: <LayoutPanelTop size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
start_date: <CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
target_date: <CalendarDays size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
cycles: <ContrastIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
modules: <DiceIcon className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
labels: <Tag size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
link: <MessageSquare size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
attachment: <Paperclip size={14} className="text-custom-text-200" aria-hidden="true" />,
|
||||
archived_at: <ArchiveIcon className="h-3.5 w-3.5 text-custom-text-200" aria-hidden="true" />,
|
||||
inbox: <Intake className="h-4 w-4 flex-shrink-0 text-custom-text-200" />,
|
||||
type: <ArrowRightLeft className="h-3.5 w-3.5 flex-shrink-0 text-custom-text-200" />,
|
||||
};
|
||||
const { isMobile } = usePlatformOS();
|
||||
return (
|
||||
<div
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { TNotification } from "@plane/types";
|
||||
import { LiteTextReadOnlyEditor } from "@/components/editor";
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import orderBy from "lodash/orderBy";
|
||||
import { TNotification, TNotificationIssueLite } from "@plane/types";
|
||||
@@ -6,14 +8,14 @@ import { IssueTypeLogo } from "@/plane-web/components/issue-types";
|
||||
import { NotificationPreviewActivity } from "@/plane-web/components/workspace-notifications";
|
||||
import { useIssueType } from "@/plane-web/hooks/store";
|
||||
export type TNotificationCardPreview = {
|
||||
notificationGroup: TNotification[];
|
||||
notificationList: TNotification[];
|
||||
workspaceSlug: string;
|
||||
projectId: string;
|
||||
issueData: TNotificationIssueLite;
|
||||
};
|
||||
export const NotificationCardPreview: FC<TNotificationCardPreview> = (props) => {
|
||||
const { notificationGroup, workspaceSlug, projectId, issueData } = props;
|
||||
const unreadCount = notificationGroup.filter((e) => !e.read_at).length;
|
||||
const { notificationList, workspaceSlug, projectId, issueData } = props;
|
||||
const unreadCount = notificationList.filter((e) => !e.read_at).length;
|
||||
|
||||
const issueType = useIssueType(issueData.id);
|
||||
|
||||
@@ -36,7 +38,7 @@ export const NotificationCardPreview: FC<TNotificationCardPreview> = (props) =>
|
||||
<p className="font-medium">{issueData.name}</p>
|
||||
</div>
|
||||
<div className="max-h-60 overflow-y-scroll vertical-scrollbar scrollbar-sm">
|
||||
{orderBy(notificationGroup, (n) => convertToEpoch(n.created_at), "desc")
|
||||
{orderBy(notificationList, (n) => convertToEpoch(n.created_at), "desc")
|
||||
.filter((n) => !!n)
|
||||
.map((notification, index, { length }) => (
|
||||
<NotificationPreviewActivity
|
||||
|
||||
@@ -17,8 +17,9 @@ type TNotificationCardListRoot = {
|
||||
export const NotificationCardListRoot: FC<TNotificationCardListRoot> = observer((props) => {
|
||||
const { workspaceSlug, workspaceId } = props;
|
||||
// hooks
|
||||
const { loader, paginationInfo, getNotifications, notificationIssueIdsByWorkspaceId } = useWorkspaceNotifications();
|
||||
const notificationIssueIds = notificationIssueIdsByWorkspaceId(workspaceId);
|
||||
const { loader, paginationInfo, getNotifications, getIssueIdsSortedByLatestNotification } =
|
||||
useWorkspaceNotifications();
|
||||
const notificationIssueIds = getIssueIdsSortedByLatestNotification(workspaceId);
|
||||
|
||||
const getNextNotifications = async () => {
|
||||
try {
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// export * from "./sidebar";
|
||||
|
||||
import React, { useMemo } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import { useParams } from "next/navigation";
|
||||
import { useFlag } from "@/plane-web/hooks/store";
|
||||
|
||||
export const NotificationsSidebarRoot = () => {
|
||||
const { workspaceSlug } = useParams();
|
||||
const isFeatureEnabled = useFlag(workspaceSlug.toString(), "INBOX_STACKING");
|
||||
const NotificationsSideBarRoot = useMemo(
|
||||
() =>
|
||||
dynamic(
|
||||
() =>
|
||||
isFeatureEnabled
|
||||
? import(`ee/components/workspace-notifications/sidebar`).then((module) => ({
|
||||
default: module["NotificationsSidebar"],
|
||||
}))
|
||||
: import("ce/components/workspace-notifications/root").then((module) => ({
|
||||
default: module["NotificationsSidebarRoot"],
|
||||
})),
|
||||
{
|
||||
// TODO: Add loading component
|
||||
loading: () => <></>,
|
||||
}
|
||||
),
|
||||
[isFeatureEnabled]
|
||||
);
|
||||
|
||||
return <NotificationsSideBarRoot />;
|
||||
};
|
||||
@@ -1,106 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import { FC } from "react";
|
||||
import { observer } from "mobx-react";
|
||||
import { useParams } from "next/navigation";
|
||||
// components
|
||||
import { Header, Row, ERowVariant, EHeaderVariant, ContentWrapper } from "@plane/ui";
|
||||
import { CountChip } from "@/components/common";
|
||||
import {
|
||||
NotificationsLoader,
|
||||
NotificationEmptyState,
|
||||
NotificationSidebarHeader,
|
||||
AppliedFilters,
|
||||
} from "@/components/workspace-notifications";
|
||||
// constants
|
||||
import { NOTIFICATION_TABS } from "@/constants/notification";
|
||||
// helpers
|
||||
import { cn } from "@/helpers/common.helper";
|
||||
import { getNumberCount } from "@/helpers/string.helper";
|
||||
// hooks
|
||||
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
|
||||
// plane web components
|
||||
import { NotificationCardListRoot } from "@/plane-web/components/workspace-notifications";
|
||||
|
||||
export const NotificationsSidebar: FC = observer(() => {
|
||||
const { workspaceSlug } = useParams();
|
||||
// hooks
|
||||
const { getWorkspaceBySlug } = useWorkspace();
|
||||
const {
|
||||
currentSelectedNotificationId,
|
||||
unreadNotificationsCount,
|
||||
loader,
|
||||
notificationIssueIdsByWorkspaceId,
|
||||
currentNotificationTab,
|
||||
setCurrentNotificationTab,
|
||||
} = useWorkspaceNotifications();
|
||||
// derived values
|
||||
const workspace = workspaceSlug ? getWorkspaceBySlug(workspaceSlug.toString()) : undefined;
|
||||
const groupedNotificationIssueIds = workspace ? notificationIssueIdsByWorkspaceId(workspace.id) : undefined;
|
||||
|
||||
if (!workspaceSlug || !workspace) return <></>;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"relative border-0 md:border-r max-md:overflow-hidden border-custom-border-200 z-[10] flex-shrink-0 bg-custom-background-100 h-full transition-all",
|
||||
currentSelectedNotificationId ? "w-0 md:w-2/6" : "w-full md:w-2/6"
|
||||
)}
|
||||
>
|
||||
<div className="relative w-full h-full flex flex-col">
|
||||
<Row className="h-[3.75rem] border-b border-custom-border-200 flex">
|
||||
<NotificationSidebarHeader workspaceSlug={workspaceSlug.toString()} />
|
||||
</Row>
|
||||
|
||||
<Header variant={EHeaderVariant.SECONDARY} className="justify-start">
|
||||
{NOTIFICATION_TABS.map((tab) => (
|
||||
<div
|
||||
key={tab.value}
|
||||
className="h-full px-3 relative cursor-pointer"
|
||||
onClick={() => currentNotificationTab != tab.value && setCurrentNotificationTab(tab.value)}
|
||||
>
|
||||
<div
|
||||
className={cn(
|
||||
`relative h-full flex justify-center items-center gap-1 text-sm transition-all`,
|
||||
currentNotificationTab === tab.value
|
||||
? "text-custom-primary-100"
|
||||
: "text-custom-text-100 hover:text-custom-text-200"
|
||||
)}
|
||||
>
|
||||
<div className="font-medium">{tab.label}</div>
|
||||
{tab.count(unreadNotificationsCount) > 0 && (
|
||||
<CountChip count={getNumberCount(tab.count(unreadNotificationsCount))} />
|
||||
)}
|
||||
</div>
|
||||
{currentNotificationTab === tab.value && (
|
||||
<div className="border absolute bottom-0 right-0 left-0 rounded-t-md border-custom-primary-100" />
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</Header>
|
||||
|
||||
{/* applied filters */}
|
||||
<AppliedFilters workspaceSlug={workspaceSlug.toString()} />
|
||||
|
||||
{/* rendering notifications */}
|
||||
{loader === "init-loader" ? (
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
<NotificationsLoader />
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{groupedNotificationIssueIds && groupedNotificationIssueIds.length > 0 ? (
|
||||
<ContentWrapper variant={ERowVariant.HUGGING}>
|
||||
<NotificationCardListRoot workspaceSlug={workspaceSlug.toString()} workspaceId={workspace?.id} />
|
||||
</ContentWrapper>
|
||||
) : (
|
||||
<div className="relative w-full h-full flex justify-center items-center">
|
||||
<NotificationEmptyState />
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
@@ -1,23 +1,23 @@
|
||||
import { WorkspaceNotificationService } from "@/services/workspace-notification.service";
|
||||
|
||||
export class InboxService extends WorkspaceNotificationService {
|
||||
async markNotificationGroupRead(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
async markBulkNotificationsAsRead(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
await this.post(`/api/workspaces/${workspaceSlug}/inbox/read/`, payload);
|
||||
}
|
||||
|
||||
async markNotificationGroupUnRead(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
async markBulkNotificationsAsUnread(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
await this.delete(`/api/workspaces/${workspaceSlug}/inbox/read/`, payload);
|
||||
}
|
||||
|
||||
async archiveNotificationGroup(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
async archiveNotificationList(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
await this.post(`/api/workspaces/${workspaceSlug}/inbox/archive/`, payload);
|
||||
}
|
||||
|
||||
async unArchiveNotificationGroup(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
async unArchiveNotificationList(workspaceSlug: string, payload: { notification_ids: string[] }) {
|
||||
await this.delete(`/api/workspaces/${workspaceSlug}/inbox/archive/`, payload);
|
||||
}
|
||||
|
||||
async updateNotficationGroup(
|
||||
async updateNotficationList(
|
||||
workspaceSlug: string,
|
||||
payload: { notification_ids: string[]; snoozed_till: string | undefined }
|
||||
) {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import orderBy from "lodash/orderBy";
|
||||
import uniqBy from "lodash/uniqBy";
|
||||
import { runInAction } from "mobx";
|
||||
import { computedFn } from "mobx-utils";
|
||||
import { TNotification } from "@plane/types";
|
||||
@@ -19,39 +18,44 @@ import {
|
||||
export type TGroupedNotifications = Record<string, TNotification[]>;
|
||||
|
||||
export interface IWorkspaceNotificationStore extends IWorkspaceNotificationStoreCore {
|
||||
groupedNotificationsByIssueId: (workspaceId: string) => Record<string, INotification[]>;
|
||||
notificationIssueIdsByWorkspaceId: (workspaceId: string) => string[] | undefined;
|
||||
markNotificationGroupRead: (notificationGroup: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
markNotificationGroupUnRead: (notificationGroup: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
archiveNotificationGroup: (notificationGroup: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
unArchiveNotificationGroup: (notificationGroup: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
snoozeNotificationGroup: (
|
||||
notificationGroup: INotification[],
|
||||
getNotificationsGroupedByIssue: (workspaceId: string) => Record<string, INotification[]>;
|
||||
getIssueIdsSortedByLatestNotification: (workspaceId: string) => string[] | undefined;
|
||||
markBulkNotificationsAsRead: (notitificationList: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
markBulkNotificationsAsUnread: (notitificationList: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
archiveNotificationList: (notitificationList: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
unArchiveNotificationList: (notitificationList: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
snoozeNotificationList: (
|
||||
notitificationList: INotification[],
|
||||
workspaceSlug: string,
|
||||
snoozeTill: Date
|
||||
snoozeUntil: Date
|
||||
) => Promise<void>;
|
||||
unSnoozeNotificationGroup: (notificationGroup: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
hasInboxIssue: (notificationGroup: INotification[]) => boolean;
|
||||
unSnoozeNotificationList: (notitificationList: INotification[], workspaceSlug: string) => Promise<void>;
|
||||
containsInboxIssue: (notitificationList: INotification[]) => boolean;
|
||||
}
|
||||
|
||||
export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore implements IWorkspaceNotificationStore {
|
||||
constructor(protected store: RootStore) {
|
||||
super(store);
|
||||
}
|
||||
groupedNotificationsByIssueId = computedFn((workspaceId: string) => {
|
||||
const filteredNotificationsIds = this.notificationIdsByWorkspaceId(workspaceId);
|
||||
if (!filteredNotificationsIds) return {};
|
||||
|
||||
const groupedNotifications: Record<string, INotification[]> = {};
|
||||
getNotificationsGroupedByIssue = computedFn((workspaceId: string) => {
|
||||
const notificationIds = this.notificationIdsByWorkspaceId(workspaceId);
|
||||
if (!notificationIds) return {};
|
||||
|
||||
filteredNotificationsIds.forEach((id) => {
|
||||
const entityId = this.notifications[id].entity_identifier;
|
||||
if (!entityId) return;
|
||||
const existingNotifications = groupedNotifications[entityId] || [];
|
||||
groupedNotifications[entityId] = uniqBy([...existingNotifications, this.notifications[id]], "id");
|
||||
});
|
||||
return notificationIds.reduce<Record<string, INotification[]>>((groupedNotifications, id) => {
|
||||
const notification = this.notifications[id];
|
||||
const entityId = notification.entity_identifier;
|
||||
|
||||
return groupedNotifications;
|
||||
if (!entityId) return groupedNotifications;
|
||||
|
||||
if (!groupedNotifications[entityId]) {
|
||||
groupedNotifications[entityId] = [];
|
||||
}
|
||||
|
||||
groupedNotifications[entityId].push(notification);
|
||||
|
||||
return groupedNotifications;
|
||||
}, {});
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -59,8 +63,8 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
* @param workspaceId: string
|
||||
* @returns string[]
|
||||
*/
|
||||
notificationIssueIdsByWorkspaceId = computedFn((workspaceId: string) => {
|
||||
const groupedNotifications: Record<string, INotification[]> = this.groupedNotificationsByIssueId(workspaceId);
|
||||
getIssueIdsSortedByLatestNotification = computedFn((workspaceId: string) => {
|
||||
const groupedNotifications: Record<string, INotification[]> = this.getNotificationsGroupedByIssue(workspaceId);
|
||||
|
||||
const groupedNotificationIssueIds = orderBy(
|
||||
Object.keys(groupedNotifications),
|
||||
@@ -75,21 +79,15 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
return groupedNotificationIssueIds;
|
||||
});
|
||||
|
||||
/**
|
||||
* @description Marks a group of notificaitons read
|
||||
* @param notificationGroup : INotification[]
|
||||
*/
|
||||
markNotificationGroupRead = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
markBulkNotificationsAsRead = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
const unreadNotificationGroup = notificationGroup.filter((n) => !n.read_at);
|
||||
if (unreadNotificationGroup.length === 0) return;
|
||||
try {
|
||||
this.loader = ENotificationLoader.MUTATION_LOADER;
|
||||
|
||||
const notification_ids: string[] = unreadNotificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = unreadNotificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.markNotificationGroupRead(workspaceSlug, {
|
||||
await inboxService.markBulkNotificationsAsRead(workspaceSlug, {
|
||||
notification_ids,
|
||||
});
|
||||
|
||||
@@ -107,22 +105,15 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Marks group of notifications unread
|
||||
* @param notificationGroup : INotification[]
|
||||
* @param workspaceSlug : string
|
||||
*/
|
||||
markNotificationGroupUnRead = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
markBulkNotificationsAsUnread = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
const readNotificationGroup = notificationGroup.filter((n) => n.read_at);
|
||||
if (readNotificationGroup.length === 0) return;
|
||||
try {
|
||||
this.loader = ENotificationLoader.MUTATION_LOADER;
|
||||
|
||||
const notification_ids: string[] = readNotificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = readNotificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.markNotificationGroupUnRead(workspaceSlug, {
|
||||
await inboxService.markBulkNotificationsAsUnread(workspaceSlug, {
|
||||
notification_ids,
|
||||
});
|
||||
|
||||
@@ -140,18 +131,11 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Archives group of notifications
|
||||
* @param notificationGroup : INotification[]
|
||||
* @param workspaceSlug : string
|
||||
*/
|
||||
archiveNotificationGroup = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
archiveNotificationList = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
try {
|
||||
const notification_ids: string[] = notificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = notificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.archiveNotificationGroup(workspaceSlug, { notification_ids });
|
||||
await inboxService.archiveNotificationList(workspaceSlug, { notification_ids });
|
||||
|
||||
Object.values(notificationGroup).forEach((notification) => {
|
||||
notification.mutateNotification({ archived_at: new Date().toISOString() });
|
||||
@@ -162,18 +146,11 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Un archives group of notifications
|
||||
* @param notificationGroup: INotification[]
|
||||
* @param workspaceSlug: string
|
||||
*/
|
||||
unArchiveNotificationGroup = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
unArchiveNotificationList = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
try {
|
||||
const notification_ids: string[] = notificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = notificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.unArchiveNotificationGroup(workspaceSlug, { notification_ids });
|
||||
await inboxService.unArchiveNotificationList(workspaceSlug, { notification_ids });
|
||||
|
||||
Object.values(notificationGroup).forEach((notification) => {
|
||||
notification.mutateNotification({ archived_at: undefined });
|
||||
@@ -184,26 +161,18 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Snoozes group of notifications unitl the provided date and time
|
||||
* @param notificationGroup : INotification[]
|
||||
* @param workspaceSlug : string
|
||||
* @param snoozed_till : Date
|
||||
*/
|
||||
snoozeNotificationGroup = async (notificationGroup: INotification[], workspaceSlug: string, snoozeTill: Date) => {
|
||||
snoozeNotificationList = async (notificationGroup: INotification[], workspaceSlug: string, snoozeUntil: Date) => {
|
||||
try {
|
||||
const notification_ids: string[] = notificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = notificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.updateNotficationGroup(workspaceSlug, {
|
||||
await inboxService.updateNotficationList(workspaceSlug, {
|
||||
notification_ids,
|
||||
snoozed_till: snoozeTill.toISOString(),
|
||||
snoozed_till: snoozeUntil.toISOString(),
|
||||
});
|
||||
|
||||
runInAction(() => {
|
||||
Object.values(notificationGroup).forEach((notification) => {
|
||||
notification.mutateNotification({ snoozed_till: snoozeTill.toISOString() });
|
||||
notification.mutateNotification({ snoozed_till: snoozeUntil.toISOString() });
|
||||
});
|
||||
});
|
||||
} catch (error) {
|
||||
@@ -212,19 +181,11 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Un Snoozes group of notifications unitl the provided date and time
|
||||
* @param notificationGroup : INotification[]
|
||||
* @param workspaceSlug : string
|
||||
* @param snoozed_till : Date
|
||||
*/
|
||||
unSnoozeNotificationGroup = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
unSnoozeNotificationList = async (notificationGroup: INotification[], workspaceSlug: string) => {
|
||||
try {
|
||||
const notification_ids: string[] = notificationGroup
|
||||
.filter((n): n is INotification & { id: string } => !!n.id)
|
||||
.map((n) => n.id);
|
||||
const notification_ids: string[] = notificationGroup.map((n) => n.id);
|
||||
|
||||
await inboxService.updateNotficationGroup(workspaceSlug, {
|
||||
await inboxService.updateNotficationList(workspaceSlug, {
|
||||
notification_ids,
|
||||
snoozed_till: undefined,
|
||||
});
|
||||
@@ -240,12 +201,7 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if notification group has an inbox issue
|
||||
* @param notificationGroup : INotification[]
|
||||
* @returns boolean
|
||||
*/
|
||||
hasInboxIssue = (notificationGroup: INotification[]) => {
|
||||
containsInboxIssue = (notificationGroup: INotification[]) => {
|
||||
const inbox_issue = notificationGroup.find((notification) => notification.is_inbox_issue);
|
||||
if (inbox_issue) return true;
|
||||
return false;
|
||||
|
||||
Reference in New Issue
Block a user