UNPKG

@restnfeel/agentc-starter-kit

Version:

한국어 기업용 CMS 모듈 - Task Master AI와 함께 빠르게 웹사이트를 구현할 수 있는 재사용 가능한 컴포넌트 시스템

175 lines (172 loc) 12 kB
"use client"; import { jsxs, jsx } from 'react/jsx-runtime'; import { useState, useEffect } from 'react'; // 간단한 SVG 아이콘 컴포넌트들 const BellIcon = () => (jsx("svg", { className: "h-6 w-6", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 17h5l-5 5v-5zM10.5 3.75a6 6 0 0 1 6 6v2.25l2.25 2.25v2.25h-15V14.25L6 12V9.75a6 6 0 0 1 4.5-6z" }) })); const XIcon = () => (jsx("svg", { className: "h-4 w-4", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })); const AlertTriangleIcon = () => (jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L4.082 16.5c-.77.833.192 2.5 1.732 2.5z" }) })); const InfoIcon = () => (jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" }) })); const CheckCircleIcon = () => (jsx("svg", { className: "h-5 w-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" }) })); var NotificationType; (function (NotificationType) { NotificationType["INFO"] = "info"; NotificationType["WARNING"] = "warning"; NotificationType["ERROR"] = "error"; NotificationType["SUCCESS"] = "success"; })(NotificationType || (NotificationType = {})); function NotificationCenter({ className = "", }) { 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 = [ { 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) => { 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) => { 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) => { switch (type) { case NotificationType.WARNING: return jsx(AlertTriangleIcon, {}); case NotificationType.ERROR: return jsx(AlertTriangleIcon, {}); case NotificationType.SUCCESS: return jsx(CheckCircleIcon, {}); case NotificationType.INFO: default: return jsx(InfoIcon, {}); } }; const getNotificationColor = (type) => { 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) => { 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 (jsxs("div", { className: `relative ${className}`, children: [jsxs("button", { onClick: () => setIsOpen(!isOpen), className: "relative p-2 text-gray-400 hover:text-gray-600 focus:outline-none focus:ring-2 focus:ring-blue-500 rounded-md", children: [jsx(BellIcon, {}), unreadCount > 0 && (jsx("span", { className: "absolute -top-1 -right-1 h-5 w-5 bg-red-500 text-white text-xs rounded-full flex items-center justify-center", children: unreadCount > 9 ? "9+" : unreadCount }))] }), isOpen && (jsxs("div", { className: "absolute right-0 mt-2 w-96 bg-white rounded-lg shadow-lg border border-gray-200 z-50", children: [jsxs("div", { className: "flex items-center justify-between p-4 border-b border-gray-200", children: [jsx("h3", { className: "text-lg font-medium text-gray-900", children: "\uC54C\uB9BC" }), jsxs("div", { className: "flex items-center space-x-2", children: [unreadCount > 0 && (jsx("button", { onClick: markAllAsRead, className: "text-sm text-blue-600 hover:text-blue-800", children: "\uBAA8\uB450 \uC77D\uC74C" })), jsx("button", { onClick: () => setIsOpen(false), className: "text-gray-400 hover:text-gray-600", children: jsx(XIcon, {}) })] })] }), jsx("div", { className: "max-h-96 overflow-y-auto", children: loading ? (jsxs("div", { className: "p-4 text-center text-gray-500", children: [jsx("div", { className: "inline-block animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" }), jsx("p", { className: "mt-2", children: "\uC54C\uB9BC\uC744 \uBD88\uB7EC\uC624\uB294 \uC911..." })] })) : notifications.length === 0 ? (jsxs("div", { className: "p-4 text-center text-gray-500", children: [jsx(BellIcon, {}), jsx("p", { className: "mt-2", children: "\uC0C8\uB85C\uC6B4 \uC54C\uB9BC\uC774 \uC5C6\uC2B5\uB2C8\uB2E4." })] })) : (jsx("div", { className: "divide-y divide-gray-200", children: notifications.map((notification) => (jsx("div", { className: `p-4 hover:bg-gray-50 ${!notification.read ? "bg-blue-50" : ""}`, children: jsxs("div", { className: "flex items-start space-x-3", children: [jsx("div", { className: `flex-shrink-0 ${getNotificationColor(notification.type)}`, children: getNotificationIcon(notification.type) }), jsxs("div", { className: "flex-1 min-w-0", children: [jsxs("div", { className: "flex items-center justify-between", children: [jsx("p", { className: `text-sm font-medium ${!notification.read ? "text-gray-900" : "text-gray-700"}`, children: notification.title }), jsx("button", { onClick: () => deleteNotification(notification.id), className: "text-gray-400 hover:text-gray-600", children: jsx(XIcon, {}) })] }), jsx("p", { className: "text-sm text-gray-600 mt-1", children: notification.message }), notification.siteName && (jsxs("p", { className: "text-xs text-gray-500 mt-1", children: ["\uC0AC\uC774\uD2B8: ", notification.siteName] })), jsxs("div", { className: "flex items-center justify-between mt-2", children: [jsx("span", { className: "text-xs text-gray-500", children: formatTimestamp(notification.timestamp) }), jsxs("div", { className: "flex items-center space-x-2", children: [!notification.read && (jsx("button", { onClick: () => markAsRead(notification.id), className: "text-xs text-blue-600 hover:text-blue-800", children: "\uC77D\uC74C \uD45C\uC2DC" })), notification.actionUrl && (jsx("a", { href: notification.actionUrl, className: "text-xs text-blue-600 hover:text-blue-800", onClick: () => setIsOpen(false), children: notification.actionLabel || "자세히 보기" }))] })] })] })] }) }, notification.id))) })) }), notifications.length > 0 && (jsx("div", { className: "p-3 border-t border-gray-200 bg-gray-50", children: jsx("a", { href: "/admin/notifications", className: "block text-center text-sm text-blue-600 hover:text-blue-800", onClick: () => setIsOpen(false), children: "\uBAA8\uB4E0 \uC54C\uB9BC \uBCF4\uAE30" }) }))] }))] })); } export { NotificationType, NotificationCenter as default }; //# sourceMappingURL=NotificationCenter.js.map