Adds a notification history view accessible from the header, including a clear button and a notification count badge.
124 lines
4.0 KiB
TypeScript
124 lines
4.0 KiB
TypeScript
|
|
import React from 'react';
|
|
import { BellRing, X, Check } from 'lucide-react';
|
|
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Separator } from '@/components/ui/separator';
|
|
import { toast } from 'sonner';
|
|
|
|
// 알림 타입 정의
|
|
export interface Notification {
|
|
id: string;
|
|
title: string;
|
|
message: string;
|
|
timestamp: Date;
|
|
read: boolean;
|
|
}
|
|
|
|
interface NotificationPopoverProps {
|
|
notifications: Notification[];
|
|
onClearAll: () => void;
|
|
onReadNotification: (id: string) => void;
|
|
}
|
|
|
|
const NotificationPopover: React.FC<NotificationPopoverProps> = ({
|
|
notifications,
|
|
onClearAll,
|
|
onReadNotification
|
|
}) => {
|
|
const unreadCount = notifications.filter(notification => !notification.read).length;
|
|
|
|
const handleClearAll = () => {
|
|
onClearAll();
|
|
toast.success('모든 알림이 삭제되었습니다.');
|
|
};
|
|
|
|
const formatDate = (date: Date) => {
|
|
return new Intl.DateTimeFormat('ko-KR', {
|
|
month: 'short',
|
|
day: 'numeric',
|
|
hour: '2-digit',
|
|
minute: '2-digit'
|
|
}).format(date);
|
|
};
|
|
|
|
return (
|
|
<Popover>
|
|
<PopoverTrigger asChild>
|
|
<Button variant="ghost" size="icon" className="relative">
|
|
<BellRing size={20} className="text-gray-600" />
|
|
{unreadCount > 0 && (
|
|
<Badge
|
|
className="absolute -top-1 -right-1 px-1.5 py-0.5 min-w-5 h-5 flex items-center justify-center text-xs bg-neuro-income text-white border-2 border-neuro-background"
|
|
>
|
|
{unreadCount}
|
|
</Badge>
|
|
)}
|
|
</Button>
|
|
</PopoverTrigger>
|
|
<PopoverContent className="w-80 p-0 neuro-flat" align="end">
|
|
<div className="flex items-center justify-between p-4">
|
|
<div className="flex items-center">
|
|
<BellRing size={16} className="mr-2 text-neuro-income" />
|
|
<h3 className="font-medium">알림</h3>
|
|
{unreadCount > 0 && (
|
|
<Badge
|
|
className="ml-2 px-1.5 py-0.5 bg-neuro-income text-white"
|
|
>
|
|
{unreadCount}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
{notifications.length > 0 && (
|
|
<Button
|
|
variant="ghost"
|
|
size="sm"
|
|
onClick={handleClearAll}
|
|
className="text-xs hover:bg-red-100 hover:text-red-600"
|
|
>
|
|
모두 삭제
|
|
</Button>
|
|
)}
|
|
</div>
|
|
|
|
<Separator />
|
|
|
|
<div className="max-h-[300px] overflow-y-auto">
|
|
{notifications.length === 0 ? (
|
|
<div className="py-6 text-center text-gray-500">
|
|
알림이 없습니다.
|
|
</div>
|
|
) : (
|
|
notifications.map((notification) => (
|
|
<div key={notification.id} className={`p-4 border-b last:border-b-0 ${!notification.read ? 'bg-[#F2FCE2]' : ''}`}>
|
|
<div className="flex items-start justify-between">
|
|
<div className="flex-1">
|
|
<h4 className="text-sm font-medium">{notification.title}</h4>
|
|
<p className="text-xs text-gray-600 mt-1">{notification.message}</p>
|
|
<p className="text-xs text-gray-400 mt-1">{formatDate(notification.timestamp)}</p>
|
|
</div>
|
|
<Button
|
|
variant="ghost"
|
|
size="icon"
|
|
className="h-6 w-6 rounded-full hover:bg-gray-200"
|
|
onClick={() => onReadNotification(notification.id)}
|
|
>
|
|
{notification.read ? (
|
|
<Check size={14} className="text-gray-400" />
|
|
) : (
|
|
<X size={14} className="text-gray-400" />
|
|
)}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
))
|
|
)}
|
|
</div>
|
|
</PopoverContent>
|
|
</Popover>
|
|
);
|
|
};
|
|
|
|
export default NotificationPopover;
|