2025-11-06 12:34:29 +00:00
|
|
|
"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";
|
2025-11-13 11:42:56 +00:00
|
|
|
import { useAppTheme } from "@/components/ThemeProvider";
|
2025-11-06 12:34:29 +00:00
|
|
|
|
|
|
|
|
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;
|
2025-11-13 11:42:56 +00:00
|
|
|
const { theme } = useAppTheme();
|
|
|
|
|
const isDark = theme === "dark";
|
2025-11-06 12:34:29 +00:00
|
|
|
|
|
|
|
|
const getIcon = (type: Notification["type"]) => {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "success":
|
2025-11-13 11:42:56 +00:00
|
|
|
return <CheckCircle className={`w-5 h-5 ${isDark ? "text-green-300" : "text-green-600"}`} />;
|
2025-11-06 12:34:29 +00:00
|
|
|
case "warning":
|
2025-11-13 11:42:56 +00:00
|
|
|
return <AlertCircle className={`w-5 h-5 ${isDark ? "text-orange-300" : "text-orange-600"}`} />;
|
2025-11-06 12:34:29 +00:00
|
|
|
case "info":
|
2025-11-13 11:42:56 +00:00
|
|
|
return <Info className={`w-5 h-5 ${isDark ? "text-blue-300" : "text-blue-600"}`} />;
|
2025-11-06 12:34:29 +00:00
|
|
|
case "appointment":
|
2025-11-13 11:42:56 +00:00
|
|
|
return <Calendar className={`w-5 h-5 ${isDark ? "text-rose-300" : "text-rose-600"}`} />;
|
2025-11-06 12:34:29 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const getBgColor = (type: Notification["type"]) => {
|
|
|
|
|
switch (type) {
|
|
|
|
|
case "success":
|
2025-11-13 11:42:56 +00:00
|
|
|
return isDark ? "bg-green-500/10 border-green-500/30" : "bg-[#4A90A4]/10 border-[#4A90A4]/30";
|
2025-11-06 12:34:29 +00:00
|
|
|
case "warning":
|
2025-11-13 11:42:56 +00:00
|
|
|
return isDark ? "bg-rose-500/10 border-rose-400/40" : "bg-rose-100 border-rose-300";
|
2025-11-06 12:34:29 +00:00
|
|
|
case "info":
|
2025-11-13 11:42:56 +00:00
|
|
|
return isDark ? "bg-pink-500/10 border-pink-400/40" : "bg-pink-50 border-pink-200";
|
2025-11-06 12:34:29 +00:00
|
|
|
case "appointment":
|
2025-11-13 11:42:56 +00:00
|
|
|
return isDark ? "bg-rose-500/10 border-rose-400/40" : "bg-gradient-to-br from-rose-50 to-pink-50 border-rose-300";
|
2025-11-06 12:34:29 +00:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
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">
|
2025-11-13 11:42:56 +00:00
|
|
|
<Bell className={`w-6 h-6 ${isDark ? "text-white" : "text-gray-900"}`} />
|
|
|
|
|
<h2 className={`text-2xl font-bold ${isDark ? "text-white" : "text-gray-900"}`}>Notifications</h2>
|
2025-11-06 12:34:29 +00:00
|
|
|
{unreadCount > 0 && (
|
2025-11-07 13:45:14 +00:00
|
|
|
<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">
|
2025-11-06 12:34:29 +00:00
|
|
|
{unreadCount}
|
|
|
|
|
</span>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
{unreadCount > 0 && onMarkAllAsRead && (
|
2025-11-13 11:42:56 +00:00
|
|
|
<Button
|
|
|
|
|
variant="outline"
|
|
|
|
|
size="sm"
|
|
|
|
|
onClick={onMarkAllAsRead}
|
|
|
|
|
className={isDark ? "border-gray-700 text-gray-200 hover:bg-gray-800" : ""}
|
|
|
|
|
>
|
2025-11-06 12:34:29 +00:00
|
|
|
Mark all as read
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Notifications List */}
|
|
|
|
|
<div className="space-y-3">
|
|
|
|
|
{notifications.length === 0 ? (
|
|
|
|
|
<div className="text-center py-12">
|
2025-11-13 11:42:56 +00:00
|
|
|
<Bell className={`w-12 h-12 mx-auto mb-4 ${isDark ? "text-gray-600" : "text-gray-400"}`} />
|
|
|
|
|
<p className={isDark ? "text-gray-400" : "text-gray-600"}>No notifications</p>
|
2025-11-06 12:34:29 +00:00
|
|
|
</div>
|
|
|
|
|
) : (
|
|
|
|
|
notifications.map((notification) => (
|
|
|
|
|
<div
|
|
|
|
|
key={notification.id}
|
|
|
|
|
className={cn(
|
|
|
|
|
"p-4 rounded-lg border-2 transition-all",
|
|
|
|
|
getBgColor(notification.type),
|
2025-11-13 11:42:56 +00:00
|
|
|
!notification.read && (isDark ? "ring-2 ring-offset-2 ring-rose-400 ring-offset-gray-900" : "ring-2 ring-offset-2 ring-rose-300")
|
2025-11-06 12:34:29 +00:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
<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",
|
2025-11-13 11:42:56 +00:00
|
|
|
!notification.read
|
|
|
|
|
? isDark ? "text-white" : "text-gray-900"
|
|
|
|
|
: isDark ? "text-gray-300" : "text-gray-700"
|
2025-11-06 12:34:29 +00:00
|
|
|
)}
|
|
|
|
|
>
|
|
|
|
|
{notification.title}
|
|
|
|
|
</h3>
|
2025-11-13 11:42:56 +00:00
|
|
|
<p className={`text-sm mb-2 ${isDark ? "text-gray-400" : "text-gray-600"}`}>{notification.message}</p>
|
|
|
|
|
<div className={`flex items-center gap-2 text-xs ${isDark ? "text-gray-500" : "text-gray-500"}`}>
|
2025-11-06 12:34:29 +00:00
|
|
|
<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)}
|
2025-11-13 11:42:56 +00:00
|
|
|
className={`h-7 w-7 ${isDark ? "text-gray-300 hover:bg-gray-800" : ""}`}
|
2025-11-06 12:34:29 +00:00
|
|
|
>
|
|
|
|
|
<CheckCircle className="w-4 h-4" />
|
|
|
|
|
</Button>
|
|
|
|
|
)}
|
|
|
|
|
{onDismiss && (
|
|
|
|
|
<Button
|
|
|
|
|
variant="ghost"
|
|
|
|
|
size="icon-sm"
|
|
|
|
|
onClick={() => onDismiss(notification.id)}
|
2025-11-13 11:42:56 +00:00
|
|
|
className={`h-7 w-7 ${isDark ? "text-gray-300 hover:bg-gray-800" : ""}`}
|
2025-11-06 12:34:29 +00:00
|
|
|
>
|
|
|
|
|
<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>
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|