"use client"; import React, { useState, useEffect } from "react"; // 간단한 SVG 아이콘 컴포넌트들 const BellIcon = () => ( ); const XIcon = () => ( ); const AlertTriangleIcon = () => ( ); const InfoIcon = () => ( ); const CheckCircleIcon = () => ( ); export enum NotificationType { INFO = "info", WARNING = "warning", ERROR = "error", SUCCESS = "success", } export interface Notification { id: string; type: NotificationType; title: string; message: string; siteId?: string; siteName?: string; timestamp: string; read: boolean; actionUrl?: string; actionLabel?: string; } interface NotificationCenterProps { className?: string; } export default function NotificationCenter({ className = "", }: NotificationCenterProps) { const [isOpen, setIsOpen] = useState(false); const [notifications, setNotifications] = useState([]); const [loading, setLoading] = useState(false); // 알림 로드 useEffect(() => { loadNotifications(); }, []); const loadNotifications = async () => { setLoading(true); try { // 실제 구현에서는 API 호출 // const response = await fetch('/api/notifications') // const data = await response.json() // 목업 데이터 const mockNotifications: Notification[] = [ { id: "1", type: NotificationType.WARNING, title: "SSL 인증서 만료 예정", message: "example.com의 SSL 인증서가 7일 후 만료됩니다.", siteId: "site1", siteName: "Example Site", timestamp: new Date(Date.now() - 2 * 60 * 60 * 1000).toISOString(), read: false, actionUrl: "/admin/sites/site1/edit", actionLabel: "인증서 갱신", }, { id: "2", type: NotificationType.ERROR, title: "사이트 접속 불가", message: "mycompany.com에 접속할 수 없습니다. 서버 상태를 확인해주세요.", siteId: "site2", siteName: "My Company", timestamp: new Date(Date.now() - 30 * 60 * 1000).toISOString(), read: false, actionUrl: "/admin/sites/site2", actionLabel: "상태 확인", }, { id: "3", type: NotificationType.SUCCESS, title: "새 사이트 생성 완료", message: "Portfolio Site가 성공적으로 생성되었습니다.", siteId: "site3", siteName: "Portfolio Site", timestamp: new Date(Date.now() - 60 * 60 * 1000).toISOString(), read: true, actionUrl: "/admin/sites/site3", actionLabel: "사이트 보기", }, { id: "4", type: NotificationType.INFO, title: "시스템 업데이트", message: "새로운 템플릿이 추가되었습니다.", timestamp: new Date(Date.now() - 3 * 60 * 60 * 1000).toISOString(), read: true, actionUrl: "/admin/templates", actionLabel: "템플릿 보기", }, ]; setNotifications(mockNotifications); } catch (error) { console.error("Failed to load notifications:", error); } finally { setLoading(false); } }; const markAsRead = async (notificationId: string) => { try { // 실제 구현에서는 API 호출 // await fetch(`/api/notifications/${notificationId}/read`, { method: 'POST' }) setNotifications((prev) => prev.map((notification) => notification.id === notificationId ? { ...notification, read: true } : notification ) ); } catch (error) { console.error("Failed to mark notification as read:", error); } }; const markAllAsRead = async () => { try { // 실제 구현에서는 API 호출 // await fetch('/api/notifications/read-all', { method: 'POST' }) setNotifications((prev) => prev.map((notification) => ({ ...notification, read: true })) ); } catch (error) { console.error("Failed to mark all notifications as read:", error); } }; const deleteNotification = async (notificationId: string) => { try { // 실제 구현에서는 API 호출 // await fetch(`/api/notifications/${notificationId}`, { method: 'DELETE' }) setNotifications((prev) => prev.filter((notification) => notification.id !== notificationId) ); } catch (error) { console.error("Failed to delete notification:", error); } }; const getNotificationIcon = (type: NotificationType) => { switch (type) { case NotificationType.WARNING: return ; case NotificationType.ERROR: return ; case NotificationType.SUCCESS: return ; case NotificationType.INFO: default: return ; } }; const getNotificationColor = (type: NotificationType) => { switch (type) { case NotificationType.WARNING: return "text-yellow-600"; case NotificationType.ERROR: return "text-red-600"; case NotificationType.SUCCESS: return "text-green-600"; case NotificationType.INFO: default: return "text-blue-600"; } }; const unreadCount = notifications.filter((n) => !n.read).length; const formatTimestamp = (timestamp: string) => { const date = new Date(timestamp); const now = new Date(); const diffInMinutes = Math.floor( (now.getTime() - date.getTime()) / (1000 * 60) ); if (diffInMinutes < 1) { return "방금 전"; } else if (diffInMinutes < 60) { return `${diffInMinutes}분 전`; } else if (diffInMinutes < 1440) { const hours = Math.floor(diffInMinutes / 60); return `${hours}시간 전`; } else { const days = Math.floor(diffInMinutes / 1440); return `${days}일 전`; } }; return (
{/* 알림 버튼 */} {/* 알림 패널 */} {isOpen && (
{/* 헤더 */}

알림

{unreadCount > 0 && ( )}
{/* 알림 목록 */}
{loading ? (

알림을 불러오는 중...

) : notifications.length === 0 ? (

새로운 알림이 없습니다.

) : (
{notifications.map((notification) => (
{getNotificationIcon(notification.type)}

{notification.title}

{notification.message}

{notification.siteName && (

사이트: {notification.siteName}

)}
{formatTimestamp(notification.timestamp)}
{!notification.read && ( )} {notification.actionUrl && ( setIsOpen(false)} > {notification.actionLabel || "자세히 보기"} )}
))}
)}
{/* 푸터 */} {notifications.length > 0 && ( )}
)}
); }