Merge pull request 'feat(notifications): Implement functional header notification dropdown' (#41) from company-details into develop
Reviewed-on: https://repo.ravanertebat.com/TM/tm_panel/pulls/41 Reviewed-by: Sina Nakhostin <s.nakhostin@ravanertebat.com>
This commit is contained in:
@@ -7,6 +7,7 @@ import { useGetNotificationDetails } from "@/hooks/queries/useNotificationQuerie
|
|||||||
import { BreadcrumbItem } from "@/types/shared";
|
import { BreadcrumbItem } from "@/types/shared";
|
||||||
import { unixToDate } from "@/utils/shared";
|
import { unixToDate } from "@/utils/shared";
|
||||||
import { use } from "react";
|
import { use } from "react";
|
||||||
|
import { capitalize } from "../../../../utils/shared";
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
params: Promise<{ id: string }>;
|
params: Promise<{ id: string }>;
|
||||||
@@ -36,11 +37,6 @@ const NotificationDetailsPage = ({ params }: IProps) => {
|
|||||||
<Breadcrumb items={breadcrumbItems} />
|
<Breadcrumb items={breadcrumbItems} />
|
||||||
<ShowcaseSection title={`${data?.data.title}`}>
|
<ShowcaseSection title={`${data?.data.title}`}>
|
||||||
<div className="mt-4 grid grid-cols-4 gap-15 pb-4">
|
<div className="mt-4 grid grid-cols-4 gap-15 pb-4">
|
||||||
<IsVisible condition={!!data?.data.message}>
|
|
||||||
<ShowcaseSection title="Message">
|
|
||||||
{data?.data.message}
|
|
||||||
</ShowcaseSection>
|
|
||||||
</IsVisible>
|
|
||||||
<IsVisible condition={!!data?.data.event_type}>
|
<IsVisible condition={!!data?.data.event_type}>
|
||||||
<ShowcaseSection title="Event Type">
|
<ShowcaseSection title="Event Type">
|
||||||
{data?.data.event_type}
|
{data?.data.event_type}
|
||||||
@@ -52,12 +48,14 @@ const NotificationDetailsPage = ({ params }: IProps) => {
|
|||||||
|
|
||||||
<IsVisible condition={!!data?.data.priority}>
|
<IsVisible condition={!!data?.data.priority}>
|
||||||
<ShowcaseSection title="Priority">
|
<ShowcaseSection title="Priority">
|
||||||
{data?.data.priority}
|
{capitalize(data?.data.priority ?? "")}
|
||||||
</ShowcaseSection>
|
</ShowcaseSection>
|
||||||
</IsVisible>
|
</IsVisible>
|
||||||
|
|
||||||
<IsVisible condition={!!data?.data.type}>
|
<IsVisible condition={!!data?.data.type}>
|
||||||
<ShowcaseSection title="Type">{data?.data.type}</ShowcaseSection>
|
<ShowcaseSection title="Type">
|
||||||
|
{capitalize(data?.data.type ?? "")}
|
||||||
|
</ShowcaseSection>
|
||||||
</IsVisible>
|
</IsVisible>
|
||||||
<IsVisible condition={!!data?.data.seen_at}>
|
<IsVisible condition={!!data?.data.seen_at}>
|
||||||
<ShowcaseSection title="Seen at">
|
<ShowcaseSection title="Seen at">
|
||||||
@@ -72,6 +70,12 @@ const NotificationDetailsPage = ({ params }: IProps) => {
|
|||||||
{data?.data.seen ? "Yes" : "No"}
|
{data?.data.seen ? "Yes" : "No"}
|
||||||
</ShowcaseSection>
|
</ShowcaseSection>
|
||||||
</div>
|
</div>
|
||||||
|
<hr className="py-4" />
|
||||||
|
<IsVisible condition={!!data?.data.message}>
|
||||||
|
<ShowcaseSection title="Message">
|
||||||
|
{data?.data.message}
|
||||||
|
</ShowcaseSection>
|
||||||
|
</IsVisible>
|
||||||
</ShowcaseSection>
|
</ShowcaseSection>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
+2
-1
@@ -3,10 +3,11 @@ import { cn } from "../lib/utils";
|
|||||||
interface IProps {
|
interface IProps {
|
||||||
className?: string | null;
|
className?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Loading = ({ className = "w-svw h-svh" }: IProps) => {
|
const Loading = ({ className = "w-svw h-svh" }: IProps) => {
|
||||||
return (
|
return (
|
||||||
<div className={cn("flex items-center justify-center", className)}>
|
<div className={cn("flex items-center justify-center", className)}>
|
||||||
<span className="loading loading-dots loading-xl"></span>
|
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-primary"></div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ const DatePicker = <T extends FieldValues>({
|
|||||||
onChange(date.isValid ? date.toUnix() : "");
|
onChange(date.isValid ? date.toUnix() : "");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
format={"YYYY/MM/DD"}
|
format={`YYYY/MM/DD ${timePicker ? "HH:mm" : ""}`}
|
||||||
containerClassName="w-full"
|
containerClassName="w-full"
|
||||||
inputClass={cn(
|
inputClass={cn(
|
||||||
"w-full rounded-lg border-[1.5px] border-stroke bg-transparent px-5.5 py-3 pl-12.5 text-dark outline-none transition focus:border-primary disabled:cursor-default disabled:bg-gray-2 dark:border-dark-3 dark:bg-dark-2 dark:text-white dark:focus:border-primary dark:disabled:bg-dark",
|
"w-full rounded-lg border-[1.5px] border-stroke bg-transparent px-5.5 py-3 pl-12.5 text-dark outline-none transition focus:border-primary disabled:cursor-default disabled:bg-gray-2 dark:border-dark-3 dark:bg-dark-2 dark:text-white dark:focus:border-primary dark:disabled:bg-dark",
|
||||||
|
|||||||
@@ -5,125 +5,109 @@ import {
|
|||||||
DropdownContent,
|
DropdownContent,
|
||||||
DropdownTrigger,
|
DropdownTrigger,
|
||||||
} from "@/components/ui/dropdown";
|
} from "@/components/ui/dropdown";
|
||||||
|
import Modal from "@/components/ui/modal";
|
||||||
|
import { useMarkNotificationAsSeen } from "@/hooks/queries/useNotificationQueries";
|
||||||
import { useIsMobile } from "@/hooks/use-mobile";
|
import { useIsMobile } from "@/hooks/use-mobile";
|
||||||
|
import { TNotificationDetailsResponse } from "@/lib/api/types/NotificationHistory"; // Import the type
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Image from "next/image";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { BellIcon } from "./icons";
|
import { BellIcon } from "./icons";
|
||||||
|
import NotificationList from "./notification-list";
|
||||||
const notificationList = [
|
|
||||||
{
|
|
||||||
image: "/images/user/user-15.png",
|
|
||||||
title: "Piter Joined the Team!",
|
|
||||||
subTitle: "Congratulate him",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/user/user-03.png",
|
|
||||||
title: "New message",
|
|
||||||
subTitle: "Devid sent a new message",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/user/user-26.png",
|
|
||||||
title: "New Payment received",
|
|
||||||
subTitle: "Check your earnings",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/user/user-28.png",
|
|
||||||
title: "Jolly completed tasks",
|
|
||||||
subTitle: "Assign new task",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
image: "/images/user/user-27.png",
|
|
||||||
title: "Roman Joined the Team!",
|
|
||||||
subTitle: "Congratulate him",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
export function Notification() {
|
export function Notification() {
|
||||||
const [isOpen, setIsOpen] = useState(false);
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
const [isDotVisible, setIsDotVisible] = useState(true);
|
const [isDotVisible, setIsDotVisible] = useState(true);
|
||||||
|
const [unreadNotificationCount, setUnreadNotificationCount] =
|
||||||
|
useState<number>(0);
|
||||||
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||||
|
const [currentNotification, setCurrentNotification] =
|
||||||
|
useState<TNotificationDetailsResponse | null>(null);
|
||||||
|
|
||||||
|
const { mutate } = useMarkNotificationAsSeen(
|
||||||
|
currentNotification?.id ?? "",
|
||||||
|
() => setIsModalOpen(false),
|
||||||
|
);
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
|
const handleNotificationClick = (
|
||||||
|
notification: TNotificationDetailsResponse,
|
||||||
|
) => {
|
||||||
|
setCurrentNotification(notification);
|
||||||
|
setIsModalOpen(true);
|
||||||
|
setIsOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown
|
<>
|
||||||
isOpen={isOpen}
|
<Dropdown
|
||||||
setIsOpen={(open) => {
|
isOpen={isOpen}
|
||||||
setIsOpen(open);
|
setIsOpen={(open) => {
|
||||||
|
setIsOpen(open);
|
||||||
if (setIsDotVisible) setIsDotVisible(false);
|
if (setIsDotVisible) setIsDotVisible(false);
|
||||||
}}
|
}}
|
||||||
>
|
|
||||||
<DropdownTrigger
|
|
||||||
className="grid size-12 place-items-center rounded-full border bg-gray-2 text-dark outline-none hover:text-primary focus-visible:border-primary focus-visible:text-primary dark:border-dark-4 dark:bg-dark-3 dark:text-white dark:focus-visible:border-primary"
|
|
||||||
aria-label="View Notifications"
|
|
||||||
>
|
>
|
||||||
<span className="relative">
|
<DropdownTrigger
|
||||||
<BellIcon />
|
className="grid size-12 place-items-center rounded-full border bg-gray-2 text-dark outline-none hover:text-primary focus-visible:border-primary focus-visible:text-primary dark:border-dark-4 dark:bg-dark-3 dark:text-white dark:focus-visible:border-primary"
|
||||||
|
aria-label="View Notifications"
|
||||||
{isDotVisible && (
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"absolute right-0 top-0 z-1 size-2 rounded-full bg-red-light ring-2 ring-gray-2 dark:ring-dark-3",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span className="absolute inset-0 -z-1 animate-ping rounded-full bg-red-light opacity-75" />
|
|
||||||
</span>
|
|
||||||
)}
|
|
||||||
</span>
|
|
||||||
</DropdownTrigger>
|
|
||||||
|
|
||||||
<DropdownContent
|
|
||||||
align={isMobile ? "end" : "center"}
|
|
||||||
className="border border-stroke bg-white px-3.5 py-3 shadow-md dark:border-dark-3 dark:bg-gray-dark min-[350px]:min-w-[20rem]"
|
|
||||||
>
|
|
||||||
<div className="mb-1 flex items-center justify-between px-2 py-1.5">
|
|
||||||
<span className="text-lg font-medium text-dark dark:text-white">
|
|
||||||
Notifications
|
|
||||||
</span>
|
|
||||||
<span className="rounded-md bg-primary px-[9px] py-0.5 text-xs font-medium text-white">
|
|
||||||
5 new
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<ul className="mb-3 max-h-[23rem] space-y-1.5 overflow-y-auto">
|
|
||||||
{notificationList.map((item, index) => (
|
|
||||||
<li key={index} role="menuitem">
|
|
||||||
<Link
|
|
||||||
href="#"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
className="flex items-center gap-4 rounded-lg px-2 py-1.5 outline-none hover:bg-gray-2 focus-visible:bg-gray-2 dark:hover:bg-dark-3 dark:focus-visible:bg-dark-3"
|
|
||||||
>
|
|
||||||
<Image
|
|
||||||
src={item.image}
|
|
||||||
className="size-14 rounded-full object-cover"
|
|
||||||
width={200}
|
|
||||||
height={200}
|
|
||||||
alt="User"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<strong className="block text-sm font-medium text-dark dark:text-white">
|
|
||||||
{item.title}
|
|
||||||
</strong>
|
|
||||||
|
|
||||||
<span className="truncate text-sm font-medium text-dark-5 dark:text-dark-6">
|
|
||||||
{item.subTitle}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
href="#"
|
|
||||||
onClick={() => setIsOpen(false)}
|
|
||||||
className="block rounded-lg border border-primary p-2 text-center text-sm font-medium tracking-wide text-primary outline-none transition-colors hover:bg-blue-light-5 focus:bg-blue-light-5 focus:text-primary focus-visible:border-primary dark:border-dark-3 dark:text-dark-6 dark:hover:border-dark-5 dark:hover:bg-dark-3 dark:hover:text-dark-7 dark:focus-visible:border-dark-5 dark:focus-visible:bg-dark-3 dark:focus-visible:text-dark-7"
|
|
||||||
>
|
>
|
||||||
See all notifications
|
<span className="relative">
|
||||||
</Link>
|
<BellIcon />
|
||||||
</DropdownContent>
|
{isDotVisible && (
|
||||||
</Dropdown>
|
<span
|
||||||
|
className={cn(
|
||||||
|
"bg-red-light absolute right-0 top-0 z-1 size-2 rounded-full ring-2 ring-gray-2 dark:ring-dark-3",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<span className="bg-red-light absolute inset-0 -z-1 animate-ping rounded-full opacity-75" />
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</span>
|
||||||
|
</DropdownTrigger>
|
||||||
|
|
||||||
|
<DropdownContent
|
||||||
|
align={isMobile ? "end" : "center"}
|
||||||
|
className="border border-stroke bg-white px-3.5 py-3 shadow-md dark:border-dark-3 dark:bg-gray-dark min-[350px]:min-w-[20rem]"
|
||||||
|
>
|
||||||
|
<div className="mb-1 flex items-center justify-between px-2 py-1.5">
|
||||||
|
<span className="text-lg font-medium text-dark dark:text-white">
|
||||||
|
Notifications
|
||||||
|
</span>
|
||||||
|
<span className="rounded-md bg-primary px-[9px] py-0.5 text-xs font-medium text-white">
|
||||||
|
{unreadNotificationCount} new
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<NotificationList
|
||||||
|
setUnreadNotificationCount={setUnreadNotificationCount}
|
||||||
|
onNotificationClick={handleNotificationClick}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
href="/notification-history"
|
||||||
|
onClick={() => setIsOpen(false)}
|
||||||
|
className="block rounded-lg border border-primary p-2 text-center text-sm font-medium tracking-wide text-primary outline-none transition-colors hover:bg-blue-light-5 focus:bg-blue-light-5 focus:text-primary focus-visible:border-primary dark:border-dark-3 dark:text-dark-6 dark:hover:border-dark-5 dark:hover:bg-dark-3 dark:hover:text-dark-7 dark:focus-visible:border-dark-5 dark:focus-visible:bg-dark-3 dark:focus-visible:text-dark-7"
|
||||||
|
>
|
||||||
|
See all notifications
|
||||||
|
</Link>
|
||||||
|
</DropdownContent>
|
||||||
|
</Dropdown>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
isOpen={isModalOpen}
|
||||||
|
onClose={() => {
|
||||||
|
setIsModalOpen(false);
|
||||||
|
}}
|
||||||
|
confirmText="Mark as read"
|
||||||
|
onConfirm={() => {
|
||||||
|
mutate();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className="flex flex-col gap-3 max-w-29">
|
||||||
|
<h2 className="font-lg font-bold">{currentNotification?.title}</h2>
|
||||||
|
<p>{currentNotification?.message}</p>
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import Loading from "@/app/loading";
|
||||||
|
import IsVisible from "@/components/ui/IsVisible";
|
||||||
|
import { useGetMyNotifications } from "@/hooks/queries/useNotificationQueries";
|
||||||
|
import { TNotificationDetailsResponse } from "@/lib/api/types/NotificationHistory";
|
||||||
|
import { truncateString } from "@/utils/shared";
|
||||||
|
import { Dispatch, SetStateAction, useEffect } from "react";
|
||||||
|
|
||||||
|
interface IProps {
|
||||||
|
setUnreadNotificationCount: Dispatch<SetStateAction<number>>;
|
||||||
|
onNotificationClick: (notification: TNotificationDetailsResponse) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const NotificationList = ({
|
||||||
|
setUnreadNotificationCount,
|
||||||
|
onNotificationClick,
|
||||||
|
}: IProps) => {
|
||||||
|
const { data, isLoading } = useGetMyNotifications({ seen: false });
|
||||||
|
useEffect(() => {
|
||||||
|
setUnreadNotificationCount(data?.data.length ?? 0);
|
||||||
|
}, [data, isLoading]);
|
||||||
|
|
||||||
|
if (isLoading) return <Loading className={"p-6"} />;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ul className="mb-3 max-h-[23rem] space-y-1.5 overflow-y-auto">
|
||||||
|
<IsVisible condition={data?.data.length === 0}>
|
||||||
|
<h2 className="cursor-default ps-2">No new notification</h2>
|
||||||
|
</IsVisible>
|
||||||
|
{data?.data.map((notification) => (
|
||||||
|
<li
|
||||||
|
role="menuitem"
|
||||||
|
key={notification.id}
|
||||||
|
onClick={() => onNotificationClick(notification)}
|
||||||
|
>
|
||||||
|
<div className="cursor-pointer rounded-lg px-1 py-2 hover:bg-gray-2">
|
||||||
|
<strong className="block text-lg font-medium capitalize text-dark dark:text-white">
|
||||||
|
{notification.title}
|
||||||
|
</strong>
|
||||||
|
<span className="text-sm font-medium text-dark-5 dark:text-dark-6">
|
||||||
|
{truncateString(notification.message, 50, 50)}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotificationList;
|
||||||
@@ -51,6 +51,7 @@ const CreateNotificationForm = () => {
|
|||||||
name="schedule_at"
|
name="schedule_at"
|
||||||
label="schedule at"
|
label="schedule at"
|
||||||
range={false}
|
range={false}
|
||||||
|
timePicker
|
||||||
placeholder="Select schedule date"
|
placeholder="Select schedule date"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -100,7 +101,7 @@ const CreateNotificationForm = () => {
|
|||||||
{...register("link", {
|
{...register("link", {
|
||||||
pattern: {
|
pattern: {
|
||||||
value: REGEX.URL,
|
value: REGEX.URL,
|
||||||
message: FormErrorMessages.invalidPattern,
|
message: FormErrorMessages.invalidPattern,
|
||||||
},
|
},
|
||||||
})}
|
})}
|
||||||
name="link"
|
name="link"
|
||||||
@@ -152,7 +153,13 @@ const CreateNotificationForm = () => {
|
|||||||
</div>
|
</div>
|
||||||
<div className="col-span-2 flex flex-col gap-2">
|
<div className="col-span-2 flex flex-col gap-2">
|
||||||
<TextAreaGroup
|
<TextAreaGroup
|
||||||
{...register("description")}
|
required
|
||||||
|
{...register("description", {
|
||||||
|
required: {
|
||||||
|
value: true,
|
||||||
|
message: FormErrorMessages.required,
|
||||||
|
},
|
||||||
|
})}
|
||||||
label="Description"
|
label="Description"
|
||||||
name="description"
|
name="description"
|
||||||
placeholder="Enter description"
|
placeholder="Enter description"
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ interface ModalProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onConfirm?: () => void;
|
onConfirm?: () => void;
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
|
confirmText?: string;
|
||||||
|
cancelText?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Modal: React.FC<ModalProps> = ({
|
const Modal: React.FC<ModalProps> = ({
|
||||||
@@ -15,6 +17,8 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
onClose,
|
onClose,
|
||||||
children,
|
children,
|
||||||
onConfirm,
|
onConfirm,
|
||||||
|
confirmText = "confirm",
|
||||||
|
cancelText = "close",
|
||||||
}) => {
|
}) => {
|
||||||
const [mounted, setMounted] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
const isMutating = useIsMutating();
|
const isMutating = useIsMutating();
|
||||||
@@ -55,21 +59,21 @@ const Modal: React.FC<ModalProps> = ({
|
|||||||
<div className="flex gap-5">
|
<div className="flex gap-5">
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium text-white hover:bg-opacity-90"
|
className="mt-6 flex w-fit justify-center rounded-lg bg-primary p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
|
||||||
onClick={onConfirm}
|
onClick={onConfirm}
|
||||||
>
|
>
|
||||||
{isMutating ? (
|
{isMutating ? (
|
||||||
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
<div className="h-5 w-5 animate-spin rounded-full border-b-2 border-gray-1"></div>
|
||||||
) : (
|
) : (
|
||||||
"Confirm"
|
<span>{confirmText}</span>
|
||||||
)}
|
)}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium text-white hover:bg-opacity-90"
|
className="mt-6 flex w-fit justify-center rounded-lg bg-error p-[13px] font-medium capitalize text-white hover:bg-opacity-90"
|
||||||
onClick={onClose}
|
onClick={onClose}
|
||||||
>
|
>
|
||||||
Close
|
{cancelText}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -16,6 +16,38 @@ export const useGetNotificationHistoryQuery = (
|
|||||||
queryFn: () => notificationService.getNotifications(params),
|
queryFn: () => notificationService.getNotifications(params),
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
export const useGetMyNotifications = (params?: Record<string, any>) => {
|
||||||
|
const queryKey = useMemo(
|
||||||
|
() => [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS, params],
|
||||||
|
[params],
|
||||||
|
);
|
||||||
|
return useQuery({
|
||||||
|
queryKey,
|
||||||
|
queryFn: () => notificationService.getMyNotifications(params),
|
||||||
|
});
|
||||||
|
};
|
||||||
|
export const useMarkNotificationAsSeen = (
|
||||||
|
id: string,
|
||||||
|
successCallback?: () => void,
|
||||||
|
) => {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
const mutationKey = useMemo(
|
||||||
|
() => [API_ENDPOINTS.NOTIFICATIONS.MARK_AS_SEEN(id), id],
|
||||||
|
[id],
|
||||||
|
);
|
||||||
|
return useMutation({
|
||||||
|
mutationKey,
|
||||||
|
mutationFn: () => notificationService.markNotificationAsSeen(id),
|
||||||
|
onSuccess: () => {
|
||||||
|
if (successCallback) {
|
||||||
|
successCallback();
|
||||||
|
}
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
export const useGetNotificationDetails = (id: string) => {
|
export const useGetNotificationDetails = (id: string) => {
|
||||||
const queryKey = useMemo(
|
const queryKey = useMemo(
|
||||||
() => [API_ENDPOINTS.NOTIFICATIONS.DETAILS(id), "NOTIFICATION DETAILS"],
|
() => [API_ENDPOINTS.NOTIFICATIONS.DETAILS(id), "NOTIFICATION DETAILS"],
|
||||||
@@ -40,6 +72,9 @@ export const useCreateNotificationQuery = (successCallback?: () => void) => {
|
|||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: [API_ENDPOINTS.NOTIFICATIONS.HISTORY],
|
queryKey: [API_ENDPOINTS.NOTIFICATIONS.HISTORY],
|
||||||
});
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: [API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS],
|
||||||
|
});
|
||||||
if (successCallback) {
|
if (successCallback) {
|
||||||
successCallback();
|
successCallback();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,5 +54,7 @@ export const API_ENDPOINTS = {
|
|||||||
HISTORY: "notifications",
|
HISTORY: "notifications",
|
||||||
CREATE: "notifications",
|
CREATE: "notifications",
|
||||||
DETAILS: (id: string) => `notifications/view/${id}`,
|
DETAILS: (id: string) => `notifications/view/${id}`,
|
||||||
|
MY_NOTIFICATIONS: "notifications/my",
|
||||||
|
MARK_AS_SEEN: (id: string) => `notifications/mark-seen/${id}`,
|
||||||
},
|
},
|
||||||
} as const;
|
} as const;
|
||||||
|
|||||||
@@ -8,6 +8,32 @@ import {
|
|||||||
import { ApiResponse } from "../types/shared";
|
import { ApiResponse } from "../types/shared";
|
||||||
|
|
||||||
export const notificationService = {
|
export const notificationService = {
|
||||||
|
getMyNotifications: async (
|
||||||
|
params?: Record<string, any>,
|
||||||
|
): Promise<TNotificationHistoryResponse> => {
|
||||||
|
try {
|
||||||
|
return (
|
||||||
|
await api.get(API_ENDPOINTS.NOTIFICATIONS.MY_NOTIFICATIONS, { params })
|
||||||
|
).data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"ERROR Caught in notification service => getMyNotifications",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
markNotificationAsSeen: async (id: string) => {
|
||||||
|
try {
|
||||||
|
return (await api.get(API_ENDPOINTS.NOTIFICATIONS.MARK_AS_SEEN(id))).data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(
|
||||||
|
"ERROR Caught in notification service => markNotificationAsRead",
|
||||||
|
error,
|
||||||
|
);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
getNotifications: async (
|
getNotifications: async (
|
||||||
params?: Record<string, any>,
|
params?: Record<string, any>,
|
||||||
): Promise<TNotificationHistoryResponse> => {
|
): Promise<TNotificationHistoryResponse> => {
|
||||||
|
|||||||
@@ -72,3 +72,13 @@ export const getEnumAsArray = <T extends object>(target: T): ILabelValue[] => {
|
|||||||
value,
|
value,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
export const truncateString = (
|
||||||
|
str: string,
|
||||||
|
startLength: number = 10,
|
||||||
|
endLength: number = 10,
|
||||||
|
): string => {
|
||||||
|
if (str.length <= startLength + endLength) {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
return str.slice(0, startLength) + "..." + str.slice(-endLength);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user