'use client'; import '@/app/styles/notification-bell.scss'; import { useState, useEffect, useRef } from 'react'; import { fetchApi } from '@/lib/utils/client'; import { useNotification, NotificationItem } from '@/hooks/useNotification'; import { NotificationListResponse } from '@/types/response/notification/list'; import { BellIcon, } from "lucide-react" export default function NotificationBell() { const { unreadNotifCount, unreadNoteCount, latestNotification, markNotifRead, clearLatestNotification } = useNotification(); const [open, setOpen] = useState(false); const [notifications, setNotifications] = useState([]); const [loading, setLoading] = useState(false); const dropdownRef = useRef(null); const totalUnread = unreadNotifCount + unreadNoteCount; // toast 알림 (3초 후 사라짐) const [toast, setToast] = useState(null); useEffect(() => { if (latestNotification) { setToast(latestNotification.title); const timer = setTimeout(() => { setToast(null); clearLatestNotification(); }, 3000); return () => clearTimeout(timer); } }, [latestNotification, clearLatestNotification]); // 드롭다운 열 때 목록 로드 const handleToggle = async () => { const next = !open; setOpen(next); if (next && notifications.length === 0) { setLoading(true); try { const res = await fetchApi('/api/notification/list?page=1&perPage=10'); if (res.data?.list) { setNotifications(res.data.list); } } catch {} setLoading(false); } }; // 읽음 처리 const handleRead = async (item: NotificationItem) => { if (!item.isRead) { await markNotifRead(item.id); setNotifications(prev => prev.map(n => n.id === item.id ? { ...n, isRead: true } : n)); } if (item.actionUrl) { window.location.href = item.actionUrl; } }; // 전체 읽음 const handleReadAll = async () => { await markNotifRead(); setNotifications(prev => prev.map(n => ({ ...n, isRead: true }))); }; // 외부 클릭 시 닫기 useEffect(() => { const handler = (e: MouseEvent) => { if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) { setOpen(false); } }; document.addEventListener('mousedown', handler); return () => document.removeEventListener('mousedown', handler); }, []); const formatTime = (dateStr: string) => { const d = new Date(dateStr); const now = new Date(); const diff = Math.floor((now.getTime() - d.getTime()) / 60000); if (diff < 1) { return '방금'; } if (diff < 60) { return `${diff}분 전`; } if (diff < 1440) { return `${Math.floor(diff / 60)}시간 전`; } return `${Math.floor(diff / 1440)}일 전`; }; return (
{/* 벨 버튼 */} {/* Toast */} {toast && (
{toast}
)} {/* 드롭다운 */} {open && (
{/* 헤더 */}
알림 {unreadNotifCount > 0 && ( )}
{/* 목록 */} {loading &&
준비 중...
} {!loading && notifications.length === 0 &&
알림이 없습니다
} {notifications.map(n => (
handleRead(n)} className={`notification-bell__item ${n.isRead ? '' : 'notification-bell__item--unread'}`}> {n.imageUrl && {n.title}}
{n.title}
{n.message}
{formatTime(n.createdAt)}
{!n.isRead &&
}
))} {/* 하단 링크 */}
)}
); }