1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative" aria-label="Notifications">
<BellIcon className="h-5 w-5" />
{unreadCount > 0 && (
<Badge
variant="destructive"
className="absolute -top-1 -right-1 h-5 w-5 flex items-center justify-center p-0 text-xs"
>
{unreadCount > 99 ? '99+' : unreadCount}
</Badge>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent className="w-80" align="end">
<div className="flex items-center justify-between p-2">
<DropdownMenuLabel className="p-0">알림</DropdownMenuLabel>
{unreadCount > 0 && (
<Button
variant="ghost"
size="sm"
onClick={markAllAsRead}
className="h-auto p-1 text-xs"
>
모두 읽음
</Button>
)}
</div>
<DropdownMenuSeparator />
{notifications.length === 0 ? (
<div className="p-4 text-center text-sm text-muted-foreground">
새로운 알림이 없습니다
</div>
) : (
<ScrollArea className="h-80">
{notifications.slice(0, 10).map((notification) => (
<DropdownMenuItem
key={notification.id}
className="p-0 cursor-pointer"
onSelect={() => handleNotificationClick(notification)}
>
<div className={`w-full p-3 ${!notification.isRead ? 'bg-blue-50 dark:bg-blue-950/20' : ''}`}>
<div className="flex items-start gap-3">
<div className="text-lg">
{getNotificationIcon(notification.type)}
</div>
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between">
<p className="text-sm font-medium truncate">
{notification.title}
</p>
{!notification.isRead && (
<div className="w-2 h-2 bg-blue-500 rounded-full flex-shrink-0 ml-2" />
)}
</div>
<p className="text-xs text-muted-foreground mt-1 line-clamp-2">
{notification.message}
</p>
<p className="text-xs text-muted-foreground mt-2">
{formatDistanceToNow(new Date(notification.createdAt), {
addSuffix: true,
locale: ko
})}
</p>
</div>
</div>
</div>
</DropdownMenuItem>
))}
{notifications.length > 10 && (
<DropdownMenuItem className="p-3 text-center text-sm text-muted-foreground">
더 많은 알림 보기...
</DropdownMenuItem>
)}
</ScrollArea>
)}
</DropdownMenuContent>
</DropdownMenu>
);
}
|