# Next.js Frontend - Интеграция уведомлений ## Статус: ✅ Полностью подключено Дата: 9 марта 2026 Версия API: v2 --- ## Что было исправлено ### 1. Backend - UserConsumer (realtime/consumers.py) **Проблема:** WebSocketNotificationSender отправлял события с типом `notification_message`, но в UserConsumer не было соответствующего метода. **Решение:** Добавлен метод `notification_message`: ```python async def notification_message(self, event): """Новое уведомление от WebSocketNotificationSender""" await self.send_json({ "type": "notification", "notification": event.get("message", {}) }) ``` ### 2. Frontend - useNotifications (hooks/useApi.ts) **Проблема:** Хук загружал уведомления только один раз при mount, без realtime обновлений. **Решение:** Добавлен WebSocket внутри хука: ```typescript useEffect(() => { // REST API загрузка fetchNotifications(); }, []); useEffect(() => { // WebSocket для realtime const ws = new WebSocket(wsUrl); ws.onmessage = (event) => { const data = JSON.parse(event.data); if (data.type === 'notification' && data.notification) { // Добавляем новое уведомление setNotifications(prev => [data.notification, ...prev]); if (data.notification.unread) { setUnreadCount(prev => prev + 1); } } if (data.type === 'unread_count') { setUnreadCount(data.count); } }; }, []); ``` ### 3. Frontend - NotificationCenter.tsx **Проблема:** Использовал старые поля v1 API (`title`, `message`, `is_read`, `created_at`). **Решение:** Обновлено для v2 API с fallback на v1: ```typescript // v2 API: unread вместо is_read (инвертированное) const isUnread = notification.unread ?? !notification.is_read; // v2 API: timestamp вместо created_at const timestamp = notification.timestamp || notification.created_at; // v2 API: verb + description вместо title + message const title = notification.verb || notification.title; const message = notification.description || notification.message; ``` ### 4. TypeScript типы (types/api.ts) **Проблема:** Старый интерфейс Notification не соответствовал v2 API. **Решение:** Обновлен интерфейс с поддержкой v2 и fallback на v1: ```typescript export interface Notification { // v2 fields id: number; verb: string; description: string; unread: boolean; timestamp: string; action_url?: string; actor?: { type: string; id: number; str: string; }; target?: { type: string; id: number; str: string; }; // v1 legacy fields (для обратной совместимости) title?: string; message?: string; is_read?: boolean; created_at?: string; } ``` --- ## Архитектура ### Полный поток данных ``` 1. Django Action (создание заявки) ↓ 2. Django Signal (post_save) ↓ 3. notify.send(sender, recipient, verb, description, ...) ↓ 4. Notification.save() [~1ms, синхронно] ↓ 5. post_save → channels.py ↓ 6. UserChannelPreferences проверка ↓ 7. Celery .delay(): - send_websocket_notification.delay() - send_email_notification.delay() - send_push_notification.delay() ↓ 8. Celery Worker обрабатывает WebSocketNotificationTask ↓ 9. WebSocketNotificationSender.send() ↓ 10. channel_layer.group_send(f"user_{user_id}", { type: 'notification_message', message: {...} }) ↓ 11. UserConsumer.notification_message() ↓ 12. ws.send({type: 'notification', notification: {...}}) ↓ 13. Next.js Frontend - useNotifications WebSocket ↓ 14. setNotifications([new, ...prev]) ↓ 15. NotificationCenter обновляет UI ↓ 16. Пользователь видит уведомление в реальном времени ``` ### Компоненты **Backend:** - `notifications/signals.py` - notify.send() API - `notifications/channels.py` - роутер по каналам - `notifications/tasks/websocket.py` - Celery задача - `notifications/senders/websocket.py` - отправка через Channels - `realtime/consumers.py` - UserConsumer (WebSocket) **Frontend:** - `hooks/useApi.ts` - useNotifications (REST + WebSocket) - `components/NotificationCenter.tsx` - UI (desktop + mobile) - `components/AppShell.tsx` - размещение компонента - `types/api.ts` - TypeScript типы --- ## API Endpoints ### REST API **Получить уведомления:** ```http GET /api/v2/notifications/ Authorization: Bearer {token} Response: { "notifications": [ { "id": 1, "verb": "request_created", "description": "Новая заявка от Иванов И.И.", "unread": true, "timestamp": "2026-03-09T18:00:00Z", "action_url": "/requests/123/", "actor": {"type": "employee", "id": 5, "str": "Иванов И.И."}, "target": {"type": "request", "id": 123, "str": "Заявка #123"} } ], "total": 42, "unread_count": 5 } ``` **Отметить как прочитанное:** ```http POST /api/v2/notifications/{id}/mark_read/ Authorization: Bearer {token} Response: {"status": "ok"} ``` **Отметить все как прочитанные:** ```http POST /api/v2/notifications/mark_all_read/ Authorization: Bearer {token} Response: {"status": "ok", "count": 5} ``` ### WebSocket **Подключение:** ``` ws://localhost:9000/ws/?token={access_token} ``` **События от сервера:** ```javascript // Новое уведомление { "type": "notification", "notification": { "id": 1, "verb": "request_approved", "description": "Ваша заявка одобрена", "unread": true, "timestamp": "2026-03-09T18:00:00Z", "action_url": "/requests/123/", "silent": false } } // Обновление счетчика { "type": "unread_count", "count": 3 } // Ping (keepalive) { "type": "ping", "timestamp": "2026-03-09T18:00:00Z" } ``` --- ## Использование в коде ### Использование хука useNotifications ```tsx import { useNotifications } from '@/hooks/useApi'; function MyComponent() { const { notifications, unreadCount, markAsRead, markAllAsRead, loading } = useNotifications(); return (
{n.description}