Corp/frontend/NOTIFICATIONS_INTEGRATION.md
lizerk ed636081dc refactor: complete notifications v2 system with frontend translation
- Remove hard-coded Russian translations from backend API
- Backend now returns raw verb values (language-agnostic)
- Implement frontend translation functions for notification titles and categories
- Add category badge system with filters on notifications page
- Create translation utilities in frontend (verbTranslations.ts)
- Add notification settings page structure
- Update API endpoints to return simplified notification data
- Refactor notification signals across all apps
- Remove deprecated code and tests
- Add comprehensive documentation for v2 migration

Backend changes:
- notifications/api/views.py: Remove translation logic, simplify responses
- All notification_signals.py: Update to use consistent patterns
- Remove deprecated Telegram bot command
- Update Celery tasks for new notification structure

Frontend changes:
- app/notifications/page.tsx: Add translation functions and category badges
- hooks/useNotificationWebSocket.ts: New WebSocket hook for real-time updates
- lib/verbTranslations.ts: Centralized translation mappings
- components/NotificationCenter.tsx: Update to use new API format

This makes the notifications module fully reusable and project-independent.
2026-03-09 21:35:36 +03:00

11 KiB
Raw Blame History

Next.js Frontend - Интеграция уведомлений

Статус: ✅ Полностью подключено

Дата: 9 марта 2026
Версия API: v2


Что было исправлено

1. Backend - UserConsumer (realtime/consumers.py)

Проблема: WebSocketNotificationSender отправлял события с типом notification_message, но в UserConsumer не было соответствующего метода.

Решение: Добавлен метод notification_message:

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 внутри хука:

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:

// 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:

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

Получить уведомления:

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
}

Отметить как прочитанное:

POST /api/v2/notifications/{id}/mark_read/
Authorization: Bearer {token}

Response: {"status": "ok"}

Отметить все как прочитанные:

POST /api/v2/notifications/mark_all_read/
Authorization: Bearer {token}

Response: {"status": "ok", "count": 5}

WebSocket

Подключение:

ws://localhost:9000/ws/?token={access_token}

События от сервера:

// Новое уведомление
{
  "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

import { useNotifications } from '@/hooks/useApi';

function MyComponent() {
  const { notifications, unreadCount, markAsRead, markAllAsRead, loading } = useNotifications();
  
  return (
    <div>
      <h1>Уведомления ({unreadCount})</h1>
      {notifications.map(n => (
        <div key={n.id} onClick={() => markAsRead(n.id)}>
          <h3>{n.verb}</h3>
          <p>{n.description}</p>
        </div>
      ))}
    </div>
  );
}

Использование NotificationCenter

import { NotificationCenter } from '@/components/NotificationCenter';

function Header() {
  return (
    <header>
      <NotificationCenter /> {/* Desktop version */}
    </header>
  );
}

function MobileNav() {
  const [isOpen, setIsOpen] = useState(false);
  
  return (
    <NotificationCenter 
      variant="mobile"
      isOpen={isOpen}
      onToggle={() => setIsOpen(!isOpen)}
    />
  );
}

Тестирование

1. Проверка REST API

# Получить все уведомления
curl -H "Authorization: Bearer YOUR_TOKEN" \
  http://localhost:9000/api/v2/notifications/

# Отметить как прочитанное
curl -X POST -H "Authorization: Bearer YOUR_TOKEN" \
  http://localhost:9000/api/v2/notifications/1/mark_read/

2. Проверка WebSocket

  1. Открыть DevTools → Console
  2. Должно быть: [Notifications] WebSocket connected
  3. Создать заявку от другого пользователя
  4. Должно прийти: [Notifications] New notification: {...}
  5. Колокольчик должен обновиться автоматически

3. Проверка UI

  1. Открыть http://localhost:3000/
  2. Кликнуть на колокольчик
  3. Увидеть список уведомлений
  4. Кликнуть на уведомление - счетчик уменьшается
  5. "Прочитать все" - все уведомления помечаются прочитанными

Поддерживаемые возможности

✅ Realtime уведомления - через WebSocket
✅ REST API fallback - при отключении WebSocket
✅ Автоматический reconnect - до 5 попыток
✅ Обратная совместимость - v1 и v2 API
✅ Desktop и Mobile UI - адаптивный дизайн
✅ Push notifications - через useWebPush
✅ Счетчик непрочитанных - realtime обновление
✅ Отметка прочитанных - индивидуально и все сразу
✅ Навигация по action_url - переход при клике
✅ TypeScript типизация - полная типобезопасность


Переменные окружения

# Frontend (.env.local)
NEXT_PUBLIC_API_URL=http://localhost:9000
NEXT_PUBLIC_WS_HOST=localhost:9000

# Backend (.env)
REDIS_URL=redis://localhost:6379/0
CELERY_BROKER_URL=redis://localhost:6379/0
CELERY_RESULT_BACKEND=redis://localhost:6379/0

Запуск для разработки

Backend

cd backend

# 1. Django server (WebSocket через Daphne)
.venv/Scripts/python manage.py runserver 9000

# 2. Celery worker (в отдельном терминале)
.venv/Scripts/celery -A eusrr_backend worker -l info

# 3. Redis (должен быть запущен)
redis-server

Frontend

cd frontend

# Development server
npm run dev

# Production build
npm run build
npm start

Известные проблемы

Нет проблем! 🎉

Все критические баги исправлены. Frontend полностью интегрирован с backend notifications v2.


Следующие шаги (опционально)

  1. Звуковые уведомления - добавить аудио при получении
  2. Группировка - группировать похожие уведомления
  3. Фильтрация - по типу, дате, прочитанности
  4. Настройки каналов - UI для UserChannelPreferences
  5. Уведомления в заголовке - обновление document.title

Полезные ссылки

  • Backend документация: backend/notifications/README.md
  • Тестирование: backend/requests_app/TESTING_NOTIFICATIONS.md
  • Отчет о рефакторинге: backend/docs/reports/NOTIFICATION_SIGNALS_REFACTORING.md