added pop over

This commit is contained in:
vamsi krishna
2024-12-03 12:43:50 +05:30
parent 038fbc7227
commit fe5ce937bd
9 changed files with 200 additions and 44 deletions

View File

@@ -5,8 +5,8 @@ import { NotificationsSidebarRoot } from "@/components/workspace-notifications";
export default function ProjectInboxIssuesLayout({ children }: { children: React.ReactNode }) {
return (
<div className="relative w-full h-full overflow-hidden flex items-center">
<NotificationsSidebarRoot />
<div className="relative w-full h-full flex items-center">
<NotificationsSidebar />
<div className="w-full h-full overflow-hidden overflow-y-auto">{children}</div>
</div>
);

View File

@@ -1,2 +1,3 @@
export * from './root';
export * from './item'
export * from "./root";
export * from "./item";
export * from "./preview";

View File

@@ -1,25 +1,90 @@
import { FC } from 'react'
import { FC, useMemo, useState } from "react";
import orderBy from "lodash/orderBy";
import { observer } from "mobx-react";
import { Row } from '@plane/ui';
import { usePopper } from "react-popper";
import { Popover } from "@headlessui/react";
import { Row } from "@plane/ui";
import { MemberDropdown } from "@/components/dropdowns";
import { calculateTimeAgo, convertToEpoch } from "@/helpers/date-time.helper";
//store
import { useIssueDetail } from "@/hooks/store"
import { useWorkspaceNotifications } from "@/hooks/store";
//components
import { IssueIdentifier } from '@/plane-web/components/issues';
import { NotificationCardPreview } from "@/plane-web/components/workspace-notifications";
export interface INotificationItem {
issueId: string;
notificationsCount: number;
issueId: string;
}
export const NotificationItem: FC<INotificationItem> = observer((props)=>{
const { issueId, notificationsCount } = props;
const { issue: { getIssueById }} = useIssueDetail();
const issueDetail = getIssueById(issueId);
export const NotificationItem: FC<INotificationItem> = observer((props) => {
const [referenceElement, setReferenceElement] = useState<HTMLButtonElement | null>(null);
const [popperElement, setPopperElement] = useState<HTMLDivElement | null>(null);
if(!issueDetail) return null
return (
<div className="border-b">
<Row>
<IssueIdentifier issueId={issueDetail.id} projectId={issueDetail.project_id || ""} textContainerClassName="text-xs" />
</Row>
const { issueId } = props;
const { groupedNotifications } = useWorkspaceNotifications();
const notificationGroup = groupedNotifications[issueId];
const issue = notificationGroup[0].data?.issue;
const unreadCount = notificationGroup.filter((e) => !e.read_at).length;
const authorIds = notificationGroup
.map((e) => e.triggered_by)
.filter((id) => id != undefined && id != null) as string[];
if (!notificationGroup || !issue || !authorIds) return <></>;
const latestNotificationTime = useMemo(() => {
const latestNotification = orderBy(notificationGroup, (n) => convertToEpoch(n.created_at), "desc")[0];
if (latestNotification.created_at) return calculateTimeAgo(latestNotification.created_at);
}, [notificationGroup]);
const { styles, attributes } = usePopper(referenceElement, popperElement, {
placement: "right-end",
});
return (
<Popover as="div" className={""}>
<Popover.Button className="w-full">
<div
className="border-b transition-all py-4 border-custom-border-200 cursor-pointer group w-full"
ref={setReferenceElement}
>
{/* Issue card header */}
<Row className="flex items-center gap-1">
<span className="text-sm font-semibold break-words">
{issue.sequence_id}-{issue.identifier}
</span>
<div className="flex-1 flex gap-2 justify-between items-center">
<span className="overflow-hidden whitespace-normal break-all truncate line-clamp-1 text-sm text-custom-text-100">
{issue.name}
</span>
{unreadCount > 0 && (
<span className="text-xs px-2 font-medium py-1 text-white bg-custom-primary-300 rounded-lg">
{unreadCount}
</span>
)}
</div>
</Row>
<Row className="flex items-center justify-between">
{/* Assignesss avatars / author avatars */}
<MemberDropdown
// projectId={issue?.project}
value={authorIds}
onChange={() => {}}
disabled
multiple
buttonVariant={authorIds?.length > 0 ? "transparent-without-text" : "border-without-text"}
buttonClassName={authorIds?.length > 0 ? "hover:bg-transparent px-0" : ""}
showTooltip={authorIds?.length === 0}
placeholder="Assignees"
optionsClassName="z-10"
tooltipContent=""
/>
<div />
<span className="text-xs text-custom-text-100">{latestNotificationTime}</span>
</Row>
</div>
)
})
</Popover.Button>
<Popover.Panel {...attributes.popper} className={""}>
<div ref={setPopperElement} className={"absolute z-10 right-0 translate-x-[100%]"}>
<NotificationCardPreview notificationGroup={notificationGroup} />
</div>
</Popover.Panel>
</Popover>
);
});

View File

@@ -0,0 +1,57 @@
"use client";
import { FC, Dispatch, SetStateAction } from "react";
import { observer } from "mobx-react";
// components
import {
NotificationItemReadOption,
NotificationItemArchiveOption,
NotificationItemSnoozeOption,
} from "@/components/workspace-notifications";
// helpers
import { cn } from "@/helpers/common.helper";
// hooks
import { useNotification } from "@/hooks/store";
type TNotificationOption = {
workspaceSlug: string;
notificationId: string;
isSnoozeStateModalOpen: boolean;
setIsSnoozeStateModalOpen: Dispatch<SetStateAction<boolean>>;
customSnoozeModal: boolean;
setCustomSnoozeModal: Dispatch<SetStateAction<boolean>>;
};
export const NotificationOption: FC<TNotificationOption> = observer((props) => {
const {
workspaceSlug,
notificationId,
isSnoozeStateModalOpen,
setIsSnoozeStateModalOpen,
customSnoozeModal,
setCustomSnoozeModal,
} = props;
// hooks
const notification = useNotification(notificationId);
return (
<div className={cn("flex-shrink-0 hidden group-hover:block text-sm", isSnoozeStateModalOpen ? `!block` : ``)}>
<div className="relative flex justify-center items-center gap-2">
{/* read */}
<NotificationItemReadOption workspaceSlug={workspaceSlug} notification={notification} />
{/* archive */}
<NotificationItemArchiveOption workspaceSlug={workspaceSlug} notification={notification} />
{/* snooze notification */}
<NotificationItemSnoozeOption
workspaceSlug={workspaceSlug}
notification={notification}
setIsSnoozeStateModalOpen={setIsSnoozeStateModalOpen}
customSnoozeModal={customSnoozeModal}
setCustomSnoozeModal={setCustomSnoozeModal}
/>
</div>
</div>
);
});

View File

@@ -0,0 +1 @@
export * from "./root";

View File

@@ -0,0 +1,30 @@
import { FC } from "react";
import { TNotification } from "@plane/types";
import { Row } from "@plane/ui";
export type TNotificationCardPreview = {
notificationGroup: TNotification[];
};
export const NotificationCardPreview: FC<TNotificationCardPreview> = (props) => {
const { notificationGroup } = props;
const issue = notificationGroup[0].data?.issue;
const unreadCount = notificationGroup.filter((e) => !e.read_at).length;
if (!issue) return;
return (
<div className="p-3 border rounded-md shadow-md border-custom-border-100">
<div className="flex justify-between gap-4">
<p className="text-sm after:-100">
{issue.identifier}-{issue.sequence_id}
</p>
{unreadCount > 0 && (
<span className="text-xs first-letter:font-medium py-1 px-2 text-white bg-custom-primary-300 rounded-lg">
{unreadCount} new updates
</span>
)}
</div>
<p className="text-sm font-medium">{issue.name}</p>
<div>{/* Lastest notifications */}</div>
</div>
);
};

View File

@@ -17,7 +17,7 @@ type TNotificationCardListRoot = {
export const NotificationCardListRoot: FC<TNotificationCardListRoot> = observer((props) => {
const { workspaceSlug, workspaceId } = props;
// hooks
const { loader, paginationInfo, getNotifications, notificationIssueIdsByWorkspaceId, groupedNotifications } = useWorkspaceNotifications();
const { loader, paginationInfo, getNotifications, notificationIssueIdsByWorkspaceId } = useWorkspaceNotifications();
const notificationIssueIds = notificationIssueIdsByWorkspaceId(workspaceId);
const getNextNotifications = async () => {
@@ -30,10 +30,10 @@ export const NotificationCardListRoot: FC<TNotificationCardListRoot> = observer(
if (!workspaceSlug || !workspaceId || !notificationIssueIds) return <></>;
return (
<div>
<>
{notificationIssueIds.map((issueId: string) => (
// <NotificationItem key={notificationId} workspaceSlug={workspaceSlug} notificationId={notificationId} />
<NotificationItem issueId={issueId} key={issueId} notificationsCount={groupedNotifications[issueId].length}/>
<NotificationItem issueId={issueId} key={issueId} />
))}
{/* fetch next page notifications */}
@@ -52,6 +52,6 @@ export const NotificationCardListRoot: FC<TNotificationCardListRoot> = observer(
)}
</>
)}
</div>
</>
);
});

View File

@@ -20,7 +20,7 @@ import { getNumberCount } from "@/helpers/string.helper";
// hooks
import { useWorkspace, useWorkspaceNotifications } from "@/hooks/store";
// plane web components
import { NotificationCardListRoot } from '@/plane-web/components/workspace-notifications'
import { NotificationCardListRoot } from "@/plane-web/components/workspace-notifications";
export const NotificationsSidebar: FC = observer(() => {
const { workspaceSlug } = useParams();
@@ -43,11 +43,11 @@ export const NotificationsSidebar: FC = observer(() => {
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",
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>

View File

@@ -5,11 +5,16 @@ import { computedFn } from "mobx-utils";
import { TNotification, TNotificationPaginatedInfo } from "@plane/types";
import { ENotificationLoader, ENotificationQueryParamType } from "@/constants/notification";
import { convertToEpoch } from "@/helpers/date-time.helper";
import {RootStore} from '@/plane-web/store/root.store'
import { RootStore } from "@/plane-web/store/root.store";
import workspaceNotificationService from "@/services/workspace-notification.service";
import { IWorkspaceNotificationStore as IWorkspaceNotificationStoreCore , TNotificationLoader, TNotificationQueryParamType, WorkspaceNotificationStore as WorkspaceNotificationStoreCore } from "@/store/notifications/workspace-notifications.store";
import {
IWorkspaceNotificationStore as IWorkspaceNotificationStoreCore,
TNotificationLoader,
TNotificationQueryParamType,
WorkspaceNotificationStore as WorkspaceNotificationStoreCore,
} from "@/store/notifications/workspace-notifications.store";
export type TGroupedNotifications = Record<string,TNotification[]>
export type TGroupedNotifications = Record<string, TNotification[]>;
export interface IWorkspaceNotificationStore extends IWorkspaceNotificationStoreCore {
// observales
@@ -37,25 +42,24 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
* @param { INotification[] } notifications
*/
groupNotificationsById = action((notifications: TNotification[]) => {
this.groupedNotifications = groupBy(notifications, (n) => n.data?.issue?.id);
this.groupedNotifications = groupBy(notifications, (n) => n.entity_identifier);
});
notificationIssueIdsByWorkspaceId = computedFn((workspaceId: string)=>{
if(!workspaceId || Object.keys(this.groupedNotifications).length === 0) return undefined;
notificationIssueIdsByWorkspaceId = computedFn((workspaceId: string) => {
if (!workspaceId || Object.keys(this.groupedNotifications).length === 0) return undefined;
const groupedNotificationIssueIds = orderBy(
Object.keys(this.groupedNotifications),
(issueId) => {
const notifications = this.groupedNotifications[issueId];
const latestNotification = orderBy(notifications,(n)=>convertToEpoch(n.created_at),'desc')[0];
return convertToEpoch(latestNotification.created_at)
const latestNotification = orderBy(notifications, (n) => convertToEpoch(n.created_at), "desc")[0];
return convertToEpoch(latestNotification.created_at);
},
'desc'
)
"desc"
);
return groupedNotificationIssueIds;
})
});
/**
* @description get all workspace notification
@@ -77,7 +81,7 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
const { results, ...paginationInfo } = notificationResponse;
runInAction(() => {
if (results) {
this.groupNotificationsById(results)
this.groupNotificationsById(results);
}
set(this, "paginationInfo", paginationInfo);
});
@@ -91,5 +95,3 @@ export class WorkspaceNotificationStore extends WorkspaceNotificationStoreCore i
}
};
}