website/app/(admin)/_components/notifications.tsx

175 lines
5.4 KiB
TypeScript
Raw Normal View History

"use client";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import {
Bell,
X,
CheckCircle,
AlertCircle,
Info,
Calendar,
Clock,
} from "lucide-react";
import { cn } from "@/lib/utils";
export interface Notification {
id: string;
type: "success" | "warning" | "info" | "appointment";
title: string;
message: string;
time: string;
read: boolean;
}
interface NotificationsProps {
notifications: Notification[];
onMarkAsRead?: (id: string) => void;
onDismiss?: (id: string) => void;
onMarkAllAsRead?: () => void;
}
export function Notifications({
notifications,
onMarkAsRead,
onDismiss,
onMarkAllAsRead,
}: NotificationsProps) {
const unreadCount = notifications.filter((n) => !n.read).length;
const getIcon = (type: Notification["type"]) => {
switch (type) {
case "success":
return <CheckCircle className="w-5 h-5 text-green-600" />;
case "warning":
return <AlertCircle className="w-5 h-5 text-orange-600" />;
case "info":
return <Info className="w-5 h-5 text-blue-600" />;
case "appointment":
return <Calendar className="w-5 h-5 text-rose-600" />;
}
};
const getBgColor = (type: Notification["type"]) => {
switch (type) {
case "success":
return "bg-[#4A90A4]/10 border-[#4A90A4]/30";
case "warning":
return "bg-rose-100 border-rose-300";
case "info":
return "bg-pink-50 border-pink-200";
case "appointment":
return "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300";
}
};
return (
<div className="w-full max-w-2xl mx-auto">
{/* Header */}
<div className="flex items-center justify-between mb-6">
<div className="flex items-center gap-3">
<Bell className="w-6 h-6 text-gray-900" />
<h2 className="text-2xl font-bold text-gray-900">Notifications</h2>
{unreadCount > 0 && (
<span className="px-2.5 py-0.5 bg-linear-to-r from-rose-500 to-pink-500 text-white text-sm font-medium rounded-full">
{unreadCount}
</span>
)}
</div>
{unreadCount > 0 && onMarkAllAsRead && (
<Button variant="outline" size="sm" onClick={onMarkAllAsRead}>
Mark all as read
</Button>
)}
</div>
{/* Notifications List */}
<div className="space-y-3">
{notifications.length === 0 ? (
<div className="text-center py-12">
<Bell className="w-12 h-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-600">No notifications</p>
</div>
) : (
notifications.map((notification) => (
<div
key={notification.id}
className={cn(
"p-4 rounded-lg border-2 transition-all",
getBgColor(notification.type),
!notification.read && "ring-2 ring-offset-2 ring-rose-300"
)}
>
<div className="flex items-start gap-3">
<div className="mt-0.5">{getIcon(notification.type)}</div>
<div className="flex-1">
<div className="flex items-start justify-between gap-4">
<div className="flex-1">
<h3
className={cn(
"font-semibold mb-1",
!notification.read ? "text-gray-900" : "text-gray-700"
)}
>
{notification.title}
</h3>
<p className="text-sm text-gray-600 mb-2">{notification.message}</p>
<div className="flex items-center gap-2 text-xs text-gray-500">
<Clock className="w-3 h-3" />
{notification.time}
</div>
</div>
<div className="flex items-center gap-2">
{!notification.read && onMarkAsRead && (
<Button
variant="ghost"
size="icon-sm"
onClick={() => onMarkAsRead(notification.id)}
className="h-7 w-7"
>
<CheckCircle className="w-4 h-4" />
</Button>
)}
{onDismiss && (
<Button
variant="ghost"
size="icon-sm"
onClick={() => onDismiss(notification.id)}
className="h-7 w-7"
>
<X className="w-4 h-4" />
</Button>
)}
</div>
</div>
</div>
</div>
</div>
))
)}
</div>
</div>
);
}
// Notification Bell Component for Header
export function NotificationBell({
count,
onClick,
}: {
count: number;
onClick: () => void;
}) {
return (
<Button variant="ghost" size="icon" className="relative" onClick={onClick}>
<Bell className="w-5 h-5" />
{count > 0 && (
<span className="absolute -top-1 -right-1 w-5 h-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center font-medium">
{count > 9 ? "9+" : count}
</span>
)}
</Button>
);
}