86 lines
3.3 KiB
TypeScript
86 lines
3.3 KiB
TypeScript
import { useEffect } from 'react';
|
||
import { useQuery } from '@tanstack/react-query';
|
||
import { queryClient } from '@/lib/queryClient';
|
||
import { sseManager } from '@/lib/sseManager';
|
||
import { useEvents } from './useEvents';
|
||
|
||
/**
|
||
* SSE-обработчики счётчиков регистрируются один раз на модуль:
|
||
* useUnreadBadges вызывается в нескольких компонентах (Sidebar, MobileBottomNav),
|
||
* а sseManager вызывает всех подписчиков — без guard'а инкремент бейджа
|
||
* срабатывал бы столько раз, сколько компонентов смонтировано.
|
||
*/
|
||
let sseHandlersRegistered = false;
|
||
|
||
function ensureBadgeSseHandlers() {
|
||
if (sseHandlersRegistered) return;
|
||
sseHandlersRegistered = true;
|
||
|
||
sseManager.on('conv_message_created', () => {
|
||
// Оптимистично увеличиваем бейдж непрочитанных сразу, без запроса.
|
||
queryClient.setQueryData<{ success: boolean; unreadCount: number }>(
|
||
['/api/messenger/unread-count'],
|
||
(old) => (old ? { ...old, unreadCount: (old.unreadCount ?? 0) + 1 } : old),
|
||
);
|
||
queryClient.invalidateQueries({ queryKey: ['/api/messenger/conversations'] });
|
||
});
|
||
|
||
sseManager.on('conv_read', () => {
|
||
queryClient.setQueryData<{ success: boolean; unreadCount: number }>(
|
||
['/api/messenger/unread-count'],
|
||
(old) => (old ? { ...old, unreadCount: 0 } : old),
|
||
);
|
||
queryClient.invalidateQueries({ queryKey: ['/api/messenger/conversations'] });
|
||
});
|
||
|
||
sseManager.on('conv_created', () => {
|
||
queryClient.invalidateQueries({ queryKey: ['/api/messenger/unread-count'] });
|
||
queryClient.invalidateQueries({ queryKey: ['/api/messenger/conversations'] });
|
||
});
|
||
|
||
const invalidateInboxCount = () => {
|
||
queryClient.invalidateQueries({ queryKey: ['/api/home/inbox/count'] });
|
||
queryClient.invalidateQueries({ queryKey: ['/api/notifications/unread-count'] });
|
||
};
|
||
sseManager.on('notification', invalidateInboxCount);
|
||
sseManager.on('task_updated', invalidateInboxCount);
|
||
}
|
||
|
||
/**
|
||
* Единый источник бейджей «непрочитанное»: сообщения мессенджера + inbox задач.
|
||
* Обновления приходят по SSE (инкрементально через setQueryData);
|
||
* polling раз в 30 секунд — только fallback при потерянном SSE-соединении.
|
||
*/
|
||
export function useUnreadBadges() {
|
||
const { isConnected } = useEvents();
|
||
|
||
useEffect(() => {
|
||
ensureBadgeSseHandlers();
|
||
}, []);
|
||
|
||
const { data: messengerData } = useQuery<{ success: boolean; unreadCount: number }>({
|
||
queryKey: ['/api/messenger/unread-count'],
|
||
refetchInterval: isConnected ? false : 30000,
|
||
staleTime: 10000,
|
||
});
|
||
|
||
const { data: inboxData } = useQuery<{ count: number }>({
|
||
queryKey: ['/api/home/inbox/count'],
|
||
refetchInterval: isConnected ? false : 30000,
|
||
staleTime: 10000,
|
||
});
|
||
|
||
const { data: notificationsData } = useQuery<{ count: number }>({
|
||
queryKey: ['/api/notifications/unread-count'],
|
||
refetchInterval: isConnected ? false : 30000,
|
||
staleTime: 10000,
|
||
});
|
||
|
||
return {
|
||
messengerUnread: messengerData?.unreadCount ?? 0,
|
||
inboxCount: inboxData?.count ?? 0,
|
||
notificationsUnread: notificationsData?.count ?? 0,
|
||
isConnected,
|
||
};
|
||
}
|