From 48a2255bfc45ffcfb0b39ffefdd57cbacf8b36df Mon Sep 17 00:00:00 2001 From: dujinkim Date: Fri, 18 Jul 2025 07:52:02 +0000 Subject: (대표님) 파일관리변경, 클라IP추적, 실시간알림, 미들웨어변경, 알림API MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/layout/Header.tsx | 6 +- components/layout/NotificationDropdown.tsx | 146 +++++++++++++++++++++++++++++ components/layout/providers.tsx | 5 +- 3 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 components/layout/NotificationDropdown.tsx (limited to 'components') diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 45768a57..a686da7a 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -43,6 +43,7 @@ import { CommandMenu } from "./command-menu"; import { useSession, signOut } from "next-auth/react"; import GroupedMenuRenderer from "./GroupedMenuRender"; import { useActiveMenus, filterActiveMenus, filterActiveAdditionalMenus } from "@/hooks/use-active-menus"; +import { NotificationDropdown } from "./NotificationDropdown"; export function Header() { const params = useParams(); @@ -235,10 +236,7 @@ export function Header() { {/* 알림 버튼 */} - + {/* 사용자 메뉴 */} diff --git a/components/layout/NotificationDropdown.tsx b/components/layout/NotificationDropdown.tsx new file mode 100644 index 00000000..1030bbc0 --- /dev/null +++ b/components/layout/NotificationDropdown.tsx @@ -0,0 +1,146 @@ +"use client"; + +import * as React from "react"; +import { BellIcon, CheckIcon, MoreHorizontal } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { Badge } from "@/components/ui/badge"; +import { ScrollArea } from "@/components/ui/scroll-area"; +import { formatDistanceToNow } from "date-fns"; +import { ko } from "date-fns/locale"; +import { useNotifications } from "@/lib/notification/NotificationContext"; + +export function NotificationDropdown() { + const { notifications, unreadCount, markAsRead, markAllAsRead } = useNotifications(); + + const handleNotificationClick = (notification: any) => { + if (!notification.isRead) { + markAsRead(notification.id); + } + + // 관련 레코드로 이동 + if (notification.relatedRecordId && notification.relatedRecordType) { + const url = getNotificationUrl(notification.relatedRecordType, notification.relatedRecordId); + window.location.href = url; + } + }; + + const getNotificationUrl = (type: string, id: string) => { + const baseUrl = window.location.pathname.split('/').slice(0, 3).join('/'); + + switch (type) { + case 'project': + return `${baseUrl}/projects/${id}`; + case 'task': + return `${baseUrl}/tasks/${id}`; + case 'order': + return `${baseUrl}/orders/${id}`; + default: + return baseUrl; + } + }; + + const getNotificationIcon = (type: string) => { + switch (type) { + case 'assignment': + return '📝'; + case 'update': + return '🔄'; + case 'reminder': + return '⏰'; + case 'approval': + return '✅'; + default: + return '🔔'; + } + }; + + return ( + + + + + +
+ 알림 + {unreadCount > 0 && ( + + )} +
+ + + {notifications.length === 0 ? ( +
+ 새로운 알림이 없습니다 +
+ ) : ( + + {notifications.slice(0, 10).map((notification) => ( + handleNotificationClick(notification)} + > +
+
+
+ {getNotificationIcon(notification.type)} +
+
+
+

+ {notification.title} +

+ {!notification.isRead && ( +
+ )} +
+

+ {notification.message} +

+

+ {formatDistanceToNow(new Date(notification.createdAt), { + addSuffix: true, + locale: ko + })} +

+
+
+
+ + ))} + {notifications.length > 10 && ( + + 더 많은 알림 보기... + + )} + + )} + + + ); +} \ No newline at end of file diff --git a/components/layout/providers.tsx b/components/layout/providers.tsx index ca6f5c21..11ca23f0 100644 --- a/components/layout/providers.tsx +++ b/components/layout/providers.tsx @@ -11,6 +11,7 @@ import { TooltipProvider } from "@/components/ui/tooltip" import { SessionManager } from "@/components/layout/SessionManager" // ✅ SessionManager 추가 import createEmotionCache from './createEmotionCashe' import { PageVisitTracker } from "../tracking/page-visit-tracker" +import { NotificationProvider } from "@/lib/notification/NotificationContext" const cache = createEmotionCache() @@ -66,7 +67,9 @@ export function ThemeProvider({ {/* ✅ 간소화된 SWR 설정 적용 */} - {children} + + {children} + {/* ✅ SessionManager 추가 - 모든 프로바이더 내부에 위치 */} -- cgit v1.2.3