diff --git a/frontend/src/app/notifications/page.tsx b/frontend/src/app/notifications/page.tsx new file mode 100644 index 00000000..f67eeef2 --- /dev/null +++ b/frontend/src/app/notifications/page.tsx @@ -0,0 +1,244 @@ +'use client'; + +import { AppShell } from '@/components/AppShell'; +import { useNotifications } from '@/hooks/useApi'; +import { Bell, Check, CheckCheck, Filter, Search, Trash2 } from 'lucide-react'; +import { useState, useMemo } from 'react'; +import { formatDistanceToNow } from 'date-fns'; +import { ru } from 'date-fns/locale/ru'; +import Link from 'next/link'; + +export default function NotificationsPage() { + const { notifications: notificationsData, loading, markAsRead, markAllAsRead } = useNotifications(); + const notifications = Array.isArray(notificationsData) ? notificationsData : []; + + const [search, setSearch] = useState(''); + const [filterRead, setFilterRead] = useState<'all' | 'unread' | 'read'>('all'); + const [filterCategory, setFilterCategory] = useState(''); + const [filtersOpen, setFiltersOpen] = useState(false); + + // Уникальные категории + const categories = useMemo(() => { + const cats = new Set(); + notifications.forEach((n: any) => { + if (n.category_name) cats.add(n.category_name); + }); + return Array.from(cats).sort(); + }, [notifications]); + + // Фильтрация + const filteredNotifications = useMemo(() => { + return notifications.filter((n: any) => { + // Фильтр по прочитанности + if (filterRead === 'unread' && n.is_read) return false; + if (filterRead === 'read' && !n.is_read) return false; + + // Фильтр по категории + if (filterCategory && n.category_name !== filterCategory) return false; + + // Поиск + if (search) { + const q = search.toLowerCase(); + const title = (n.title || '').toLowerCase(); + const message = (n.message || '').toLowerCase(); + if (!title.includes(q) && !message.includes(q)) return false; + } + + return true; + }); + }, [notifications, search, filterRead, filterCategory]); + + const unreadCount = notifications.filter((n: any) => !n.is_read).length; + + const handleNotificationClick = async (notification: any) => { + if (!notification.is_read) { + await markAsRead(notification.id); + } + if (notification.action_url) { + window.location.href = notification.action_url; + } + }; + + const getCategoryIcon = (icon?: string) => { + // Можно добавить маппинг иконок по необходимости + return icon || '📋'; + }; + + return ( + +
+ {/* Заголовок */} +
+
+

Уведомления

+

+ {unreadCount > 0 ? `${unreadCount} непрочитанных` : 'Все прочитано'} +

+
+ {unreadCount > 0 && ( + + )} +
+ + {/* Поиск и фильтры */} +
+
+
+ + setSearch(e.target.value)} + placeholder="Поиск по уведомлениям..." + className="w-full rounded-lg border border-gray-200 bg-gray-50 py-2.5 pl-9 pr-3 text-sm text-gray-800 transition focus:border-sky-500 focus:bg-white focus:outline-none focus:ring-2 focus:ring-sky-100" + /> +
+ +
+ + {filtersOpen && ( +
+ + + + + {(filterRead !== 'all' || filterCategory) && ( + + )} +
+ )} +
+ + {/* Список уведомлений */} + {loading ? ( +
+
+

Загрузка уведомлений...

+
+ ) : filteredNotifications.length === 0 ? ( +
+ +

+ {search || filterRead !== 'all' || filterCategory + ? 'Нет уведомлений по заданным критериям' + : 'Нет уведомлений'} +

+

+ {search || filterRead !== 'all' || filterCategory + ? 'Попробуйте изменить фильтры' + : 'Все новые уведомления появятся здесь'} +

+
+ ) : ( +
+ {filteredNotifications.map((notification: any) => ( +
handleNotificationClick(notification)} + className={`group cursor-pointer rounded-xl border transition hover:shadow-md ${ + notification.is_read + ? 'border-gray-200 bg-white' + : 'border-sky-200 bg-sky-50/30' + }`} + > +
+ {/* Иконка */} +
+ {getCategoryIcon(notification.icon)} +
+ + {/* Содержимое */} +
+
+

+ {notification.title} +

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

+ {notification.short_message || notification.message} +

+ +
+ {notification.category_name && ( + + {notification.category_name} + + )} + + {formatDistanceToNow(new Date(notification.created_at), { + addSuffix: true, + locale: ru, + })} + +
+
+
+
+ ))} +
+ )} +
+
+ ); +}