"use client"; import React, { useState, useEffect } from "react"; import { Bell, Mail, AlertCircle, XCircle, CheckCircle2 } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Popover, PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; import { Badge } from "@/components/ui/badge"; import { Separator } from "@/components/ui/separator"; import { getSentMailList, getReceivedMails, getMailAccounts } from "@/lib/api/mail"; import { useRouter } from "next/navigation"; interface MailNotification { id: string; type: "new_mail" | "send_failed" | "limit_warning"; title: string; message: string; timestamp: string; read: boolean; url?: string; // 이동할 URL 추가 } export default function MailNotifications() { const router = useRouter(); const [notifications, setNotifications] = useState([]); const [unreadCount, setUnreadCount] = useState(0); const [isOpen, setIsOpen] = useState(false); const [readNotificationIds, setReadNotificationIds] = useState>(new Set()); const [isInitialized, setIsInitialized] = useState(false); // localStorage에서 읽은 알림 ID 로드 (최우선) useEffect(() => { const stored = localStorage.getItem('readNotificationIds'); if (stored) { try { const ids = JSON.parse(stored); setReadNotificationIds(new Set(ids)); } catch (error) { console.error('읽은 알림 ID 로드 실패:', error); } } setIsInitialized(true); }, []); useEffect(() => { // localStorage 로드 완료 후에만 알림 로드 if (!isInitialized) return; // 알림 로드 loadNotifications(); // 5초마다 새 알림 확인 (더 빠른 실시간 업데이트) const interval = setInterval(() => { checkNewNotifications(); }, 5000); // 메일 발송/수신 이벤트 리스너 (즉시 갱신) const handleMailEvent = () => { console.log('📧 메일 이벤트 감지 - 알림 갱신'); checkNewNotifications(); }; window.addEventListener('mail-sent', handleMailEvent); window.addEventListener('mail-received', handleMailEvent); return () => { clearInterval(interval); window.removeEventListener('mail-sent', handleMailEvent); window.removeEventListener('mail-received', handleMailEvent); }; }, [isInitialized, readNotificationIds]); useEffect(() => { const count = notifications.filter((n) => !n.read).length; setUnreadCount(count); }, [notifications]); // 알림 패널이 열리면 3초 후 자동으로 읽음 처리 useEffect(() => { if (isOpen && notifications.some((n) => !n.read)) { const timer = setTimeout(() => { markAllAsRead(); }, 3000); // 3초 후 자동 읽음 처리 return () => clearTimeout(timer); } }, [isOpen, notifications]); const loadNotifications = async () => { try { const newNotifications: MailNotification[] = []; // 1. 최근 발송 실패한 메일 확인 (최근 1시간) try { const sentMails = await getSentMailList({ page: 1, limit: 20, status: 'failed', }); const oneHourAgo = new Date(Date.now() - 60 * 60 * 1000); sentMails.items?.forEach((mail) => { const sentDate = new Date(mail.sentAt); if (sentDate > oneHourAgo) { newNotifications.push({ id: `failed-${mail.id}`, type: "send_failed", title: "메일 발송 실패", message: `${mail.to.join(', ')}에게 보낸 메일이 실패했습니다.`, timestamp: mail.sentAt, read: false, url: '/admin/mail/sent', // 보낸메일함으로 이동 }); } }); } catch (error) { console.error('발송 실패 메일 확인 오류:', error); } // 2. 최근 수신 메일 확인 (최근 30분) try { const accounts = await getMailAccounts(); const activeAccounts = accounts.filter((acc) => acc.status === 'active'); for (const account of activeAccounts) { try { const receivedMails = await getReceivedMails(account.id, 10); const thirtyMinutesAgo = new Date(Date.now() - 30 * 60 * 1000); receivedMails.forEach((mail) => { const receivedDate = new Date(mail.date); if (receivedDate > thirtyMinutesAgo && !mail.isRead) { newNotifications.push({ id: `new-${mail.id}`, type: "new_mail", title: "새 메일 도착", message: `${mail.from}에서 메일이 도착했습니다: ${mail.subject}`, timestamp: mail.date, read: false, url: `/admin/mail/receive?mailId=${mail.id}&accountId=${account.id}`, // 특정 메일로 이동 }); } }); } catch (error) { console.error(`계정 ${account.id} 수신 메일 확인 오류:`, error); } } } catch (error) { console.error('수신 메일 확인 오류:', error); } // 3. 일일 발송 제한 경고 확인 try { const accounts = await getMailAccounts(); const sentMails = await getSentMailList({ page: 1, limit: 100, }); accounts.forEach((account) => { if (account.status === 'active' && account.dailyLimit) { const todaySentCount = sentMails.items?.filter((mail) => { const sentDate = new Date(mail.sentAt); const today = new Date(); return ( mail.accountId === account.id && sentDate.toDateString() === today.toDateString() ); }).length || 0; const usagePercent = (todaySentCount / account.dailyLimit) * 100; if (usagePercent >= 80) { newNotifications.push({ id: `limit-${account.id}`, type: "limit_warning", title: "일일 발송 제한 경고", message: `${account.name} 계정이 일일 제한의 ${usagePercent.toFixed(0)}%를 사용했습니다 (${todaySentCount}/${account.dailyLimit})`, timestamp: new Date().toISOString(), read: false, url: '/admin/mail/accounts', // 계정 관리로 이동 }); } } }); } catch (error) { console.error('일일 제한 확인 오류:', error); } // 최신순 정렬 newNotifications.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() ); // 읽은 알림 표시 적용 const notificationsWithReadStatus = newNotifications.map((notification) => ({ ...notification, read: readNotificationIds.has(notification.id), })); setNotifications(notificationsWithReadStatus); } catch (error) { console.error('알림 로드 실패:', error); } }; const checkNewNotifications = () => { loadNotifications(); }; const markAsRead = (id: string) => { const newReadIds = new Set(readNotificationIds); newReadIds.add(id); setReadNotificationIds(newReadIds); // localStorage에 저장 localStorage.setItem('readNotificationIds', JSON.stringify(Array.from(newReadIds))); setNotifications((prev) => prev.map((n) => (n.id === id ? { ...n, read: true } : n)) ); }; const handleNotificationClick = (notification: MailNotification) => { // 읽음 처리 markAsRead(notification.id); // URL이 있으면 해당 페이지로 이동 if (notification.url) { router.push(notification.url); setIsOpen(false); // 팝오버 닫기 } }; const markAllAsRead = () => { const allIds = new Set([...readNotificationIds, ...notifications.map((n) => n.id)]); setReadNotificationIds(allIds); // localStorage에 저장 localStorage.setItem('readNotificationIds', JSON.stringify(Array.from(allIds))); setNotifications((prev) => prev.map((n) => ({ ...n, read: true }))); }; const clearAll = () => { // 모든 알림을 읽음으로 표시하고 localStorage에 저장 const allIds = new Set([...readNotificationIds, ...notifications.map((n) => n.id)]); setReadNotificationIds(allIds); localStorage.setItem('readNotificationIds', JSON.stringify(Array.from(allIds))); setNotifications([]); }; const getIcon = (type: MailNotification["type"]) => { switch (type) { case "new_mail": return ; case "send_failed": return ; case "limit_warning": return ; default: return ; } }; const getTypeColor = (type: MailNotification["type"]) => { switch (type) { case "new_mail": return "bg-blue-50 border-blue-200"; case "send_failed": return "bg-red-50 border-red-200"; case "limit_warning": return "bg-yellow-50 border-yellow-200"; default: return "bg-muted"; } }; return (

알림

{unreadCount > 0 && ( )} {notifications.length > 0 && ( )}
{notifications.filter((n) => !n.read).length === 0 ? (

새로운 알림이 없습니다

) : (
{notifications.filter((n) => !n.read).map((notification) => (
handleNotificationClick(notification)} >
{getIcon(notification.type)}

{notification.title}

{!notification.read && (
)}

{notification.message}

{new Date(notification.timestamp).toLocaleString("ko-KR", { month: "short", day: "numeric", hour: "2-digit", minute: "2-digit", })}

))}
)}
); }