168 lines
5.8 KiB
TypeScript
168 lines
5.8 KiB
TypeScript
import { Badge } from "@/components/ui/badge";
|
|
import { Button } from "@/components/ui/button";
|
|
import {
|
|
DropdownMenu,
|
|
DropdownMenuContent,
|
|
DropdownMenuItem,
|
|
DropdownMenuLabel,
|
|
DropdownMenuSeparator,
|
|
DropdownMenuTrigger,
|
|
} from "@/components/ui/dropdown-menu";
|
|
import { ScrollArea } from "@/components/ui/scroll-area";
|
|
import { Link } from "@/i18n/routing";
|
|
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
|
|
import { cn } from "@/lib/utils";
|
|
import shortImage from "@/public/images/all-img/short-image-2.png";
|
|
import { Icon } from "@/components/ui/icon";
|
|
import { useEffect, useState } from "react";
|
|
import { getNotifications } from "@/service/notifications/notifications";
|
|
import { format, formatDate } from "date-fns";
|
|
import {
|
|
CalendarCheck,
|
|
CheckCheck,
|
|
CircleAlert,
|
|
Clock7,
|
|
MessageCircle,
|
|
SquareCheck,
|
|
UploadIcon,
|
|
} from "lucide-react";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export type Notification = {
|
|
id: number;
|
|
notificationTypeId: number;
|
|
message: string;
|
|
createdAt: string;
|
|
isActive: boolean;
|
|
isPublic: boolean;
|
|
isRead: boolean;
|
|
redirectUrl: string;
|
|
userGroupIdDst: string | null;
|
|
userIdDst: string | null;
|
|
userLevelIdDst: string;
|
|
userLevelNumberDst: string | null;
|
|
userRoleIdDst: string;
|
|
};
|
|
|
|
const getNotificationIcon = (notificationTypeId: number) => {
|
|
switch (notificationTypeId) {
|
|
case 2:
|
|
return <MessageCircle className="h-8 w-8 text-success" />;
|
|
case 3:
|
|
return <UploadIcon className="h-5 w-5 text-warning" />;
|
|
case 4:
|
|
return <CheckCheck className="h-5 w-5 text-primary" />;
|
|
case 5:
|
|
return <SquareCheck className="h-5 w-5 text-warning" />;
|
|
case 6:
|
|
return <CalendarCheck className="h-5 w-5 text-danger" />;
|
|
case 7:
|
|
return <CircleAlert className="h-5 w-5 text-danger" />;
|
|
case 8:
|
|
return <Clock7 className="h-5 w-5 text-danger" />;
|
|
default:
|
|
return <SquareCheck className="h-5 w-5 text-danger" />;
|
|
}
|
|
};
|
|
|
|
const Notifications = () => {
|
|
const router = useRouter();
|
|
const [notifications, setNotifications] = useState<Notification[]>([]);
|
|
const [notificationTotal, setNotificationTotal] = useState(0);
|
|
|
|
useEffect(() => {
|
|
async function initState() {
|
|
const response = await getNotifications();
|
|
setNotifications(response.data?.data?.content);
|
|
setNotificationTotal(response.data?.data?.totalElements);
|
|
console.log("notif", response.data?.data?.content);
|
|
}
|
|
initState();
|
|
}, []);
|
|
|
|
const formatDate = (dateString: string) => {
|
|
const date = new Date(dateString);
|
|
return format(date, "dd/MM/yyyy HH:mm");
|
|
};
|
|
|
|
const handleNotificationClick = (redirectUrl: string) => {
|
|
router.push(redirectUrl);
|
|
};
|
|
|
|
return (
|
|
<DropdownMenu>
|
|
<DropdownMenuTrigger asChild>
|
|
<button
|
|
type="button"
|
|
className="relative hidden focus:ring-none focus:outline-none md:h-8 md:w-8 md:bg-secondary text-secondary-foreground rounded-full md:flex flex-col items-center justify-center"
|
|
>
|
|
<Icon
|
|
icon="heroicons-outline:bell"
|
|
className="animate-tada h-5 w-5"
|
|
/>
|
|
<Badge
|
|
className=" w-4 h-4 p-0 text-[8px] rounded-full font-semibold items-center justify-center absolute left-[calc(100%-12px)] bottom-[calc(100%-10px)]"
|
|
color="destructive"
|
|
>
|
|
{notificationTotal > 99 ? "99+" : notificationTotal}
|
|
</Badge>
|
|
</button>
|
|
</DropdownMenuTrigger>
|
|
<DropdownMenuContent
|
|
align="end"
|
|
className=" z-[999] mx-4 lg:w-[320px] p-0"
|
|
>
|
|
<DropdownMenuLabel>
|
|
<div className="flex justify-between px-4 py-3 border-b border-default-100 ">
|
|
<div className="text-sm text-default-800 font-medium ">
|
|
you have {notificationTotal > 99 ? "99+" : notificationTotal}{" "}
|
|
notifications
|
|
</div>
|
|
<div className="text-default-800 text-xs md:text-right">
|
|
<Link href="/notifications" className="underline">
|
|
View all
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</DropdownMenuLabel>
|
|
<div className="h-[300px] xl:h-[350px]">
|
|
<ScrollArea className="h-full">
|
|
{notifications.map((item: Notification, index: number) => (
|
|
<DropdownMenuItem
|
|
key={`inbox-${index}`}
|
|
className="flex gap-9 py-2 px-4 cursor-pointer group "
|
|
onClick={() => handleNotificationClick(item?.redirectUrl)}
|
|
>
|
|
<div className="flex items-start gap-2 flex-1 ">
|
|
<div className="flex-none">
|
|
{getNotificationIcon(item.notificationTypeId)}
|
|
</div>
|
|
<div className="flex-1 flex flex-col gap-0.5">
|
|
<div className="text-sm text-default-600 dark:group-hover:text-default-800 font-normal truncate whitespace-normal ">
|
|
{item?.message}
|
|
</div>
|
|
{/* <div className="text-xs text-default-600 dark:group-hover:text-default-700 font-light line-clamp-1 ">
|
|
{item?.desc}
|
|
</div> */}
|
|
<div className=" text-default-400 dark:group-hover:text-default-500 text-xs">
|
|
{" "}
|
|
{formatDate(item?.createdAt)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{/* {item?.unreadmessage && (
|
|
<div className="flex-0">
|
|
<span className="h-[10px] w-[10px] bg-destructive border border-destructive-foreground dark:border-default-400 rounded-full inline-block" />
|
|
</div>
|
|
)} */}
|
|
</DropdownMenuItem>
|
|
))}
|
|
</ScrollArea>
|
|
</div>
|
|
</DropdownMenuContent>
|
|
</DropdownMenu>
|
|
);
|
|
};
|
|
|
|
export default Notifications;
|