'use client'; import { useCallback, useEffect, useMemo, useRef, useState } from 'react'; import Link from 'next/link'; import { ArrowLeft, Loader2, MessageCircle, Send, X } from 'lucide-react'; import { AvatarWithPresence } from '@/components/family/avatar-with-presence'; import { useFamilyOverlay } from '@/components/family/family-overlay-provider'; import { useAuth } from '@/components/id/auth-provider'; import { useToast } from '@/components/id/toast-provider'; import { useRealtime } from '@/components/notifications/realtime-provider'; import { Button } from '@/components/ui/button'; import { Input } from '@/components/ui/input'; import { useSelectedFamily } from '@/hooks/use-primary-family'; import { FamilyGroupSelector } from '@/components/family/family-group-selector'; import { ChatMessage, ChatRoom, fetchChatMessages, fetchChatRooms, getApiErrorMessage, markChatRoomRead, sendChatMessage } from '@/lib/api'; import { findOrCreateMemberChat, roomDisplayLabel } from '@/lib/family-chat'; import { cn } from '@/lib/utils'; function formatMessageTime(value: string) { return new Intl.DateTimeFormat('ru-RU', { hour: '2-digit', minute: '2-digit' }).format(new Date(value)); } export function MiniFamilyChat() { const { user, token, isPinLocked } = useAuth(); const { showToast } = useToast(); const { subscribe } = useRealtime(); const { group, groupSummaries, hasFamily, selectedGroupId, setSelectedGroupId, presenceByUserId } = useSelectedFamily(Boolean(user && token && !isPinLocked)); const { miniChatOpen, openMiniChat, closeMiniChat, pendingRoomId, pendingMember, clearPending } = useFamilyOverlay(); const [view, setView] = useState<'picker' | 'chat'>('picker'); const [rooms, setRooms] = useState([]); const [activeRoom, setActiveRoom] = useState(null); const [messages, setMessages] = useState([]); const [draft, setDraft] = useState(''); const [loadingRooms, setLoadingRooms] = useState(false); const [loadingMessages, setLoadingMessages] = useState(false); const [sending, setSending] = useState(false); const [openingMemberId, setOpeningMemberId] = useState(null); const messagesEndRef = useRef(null); const previousGroupIdRef = useRef(null); const loadRooms = useCallback(async () => { if (!group || !token) return; setLoadingRooms(true); try { const response = await fetchChatRooms(group.id, token); setRooms(response.rooms ?? []); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось загрузить чаты') ?? 'Ошибка'); } finally { setLoadingRooms(false); } }, [group, showToast, token]); const openRoom = useCallback( async (room: ChatRoom) => { setActiveRoom(room); setView('chat'); setLoadingMessages(true); try { const response = await fetchChatMessages(room.id, token); setMessages(response.messages ?? []); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось загрузить сообщения') ?? 'Ошибка'); } finally { setLoadingMessages(false); } }, [showToast, token] ); const openMemberChat = useCallback( async (memberUserId: string, memberName: string) => { if (!group || !token || !user) return; setOpeningMemberId(memberUserId); try { const room = await findOrCreateMemberChat(group.id, memberUserId, memberName, user.id, token); await loadRooms(); await openRoom(room); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось открыть чат') ?? 'Ошибка'); } finally { setOpeningMemberId(null); } }, [group, loadRooms, openRoom, showToast, token, user] ); useEffect(() => { if (!miniChatOpen || !hasFamily) return; void loadRooms(); }, [group?.id, hasFamily, loadRooms, miniChatOpen]); useEffect(() => { if (!group?.id) return; if (previousGroupIdRef.current && previousGroupIdRef.current !== group.id && miniChatOpen) { setView('picker'); setActiveRoom(null); setMessages([]); setDraft(''); } previousGroupIdRef.current = group.id; }, [group?.id, miniChatOpen]); useEffect(() => { if (!miniChatOpen) { setView('picker'); setActiveRoom(null); setMessages([]); setDraft(''); clearPending(); return; } if (pendingRoomId && rooms.length) { const room = rooms.find((item) => item.id === pendingRoomId); if (room) { void openRoom(room); clearPending(); } } else if (pendingMember && user) { void openMemberChat(pendingMember.userId, pendingMember.name); clearPending(); } }, [clearPending, miniChatOpen, openMemberChat, openRoom, pendingMember, pendingRoomId, rooms, user]); useEffect(() => { if (!activeRoom) return; return subscribe((event) => { const payload = event.payload; if (payload?.roomId && payload.roomId !== activeRoom.id) return; if (event.type === 'chat_message' && payload?.message) { const message = payload.message as ChatMessage; setMessages((current) => { if (current.some((item) => item.id === message.id)) return current; return [...current, message]; }); } if (event.type === 'chat_message_updated' || event.type === 'chat_message_deleted') { const updated = payload?.message; if (updated) { setMessages((current) => current.map((item) => (item.id === updated.id ? updated : item))); } } }); }, [activeRoom, subscribe]); useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }); }, [messages]); useEffect(() => { if (!activeRoom || !token || !messages.length) return; const last = messages[messages.length - 1]; const timer = window.setTimeout(() => { void markChatRoomRead(activeRoom.id, last.id, token).catch(() => {}); }, 400); return () => window.clearTimeout(timer); }, [activeRoom, messages, token]); async function handleSend() { if (!token || !activeRoom || !draft.trim()) return; setSending(true); try { const message = await sendChatMessage(activeRoom.id, { type: 'TEXT', content: draft.trim() }, token); setMessages((current) => [...current, message]); setDraft(''); void loadRooms(); } catch (error) { showToast(getApiErrorMessage(error, 'Не удалось отправить сообщение') ?? 'Ошибка'); } finally { setSending(false); } } const otherMembers = useMemo( () => (group?.members ?? []).filter((member) => member.userId !== user?.id), [group?.members, user?.id] ); if (!user || !token || isPinLocked) return null; return ( <> {miniChatOpen ? (
{view === 'chat' ? ( ) : null}

{view === 'chat' && activeRoom && user ? roomDisplayLabel(activeRoom, user.id) : 'Чат семьи'}

{hasFamily ? ( ) : (

Семья не создана

)}
{!hasFamily ? (

Создайте семейную группу, чтобы общаться с близкими из любого раздела.

Перейти к созданию семьи
) : view === 'picker' ? (

Чаты

{loadingRooms ? (
Загрузка...
) : rooms.length ? (
{rooms.map((room) => ( ))}
) : (

Чаты пока не созданы

)}

Написать участнику

{otherMembers.map((member) => { const presence = presenceByUserId.get(member.userId); return ( ); })} {!otherMembers.length ? (

Пригласите участников в семью

) : null}
Открыть семью полностью
) : ( <>
{loadingMessages ? (
Загрузка...
) : messages.length ? ( messages.map((message) => { const mine = message.senderId === user.id; return (
{!mine ?

{message.senderName}

: null}

{message.isDeleted ? 'Сообщение удалено' : message.content ?? '…'}

{formatMessageTime(message.createdAt)}

); }) ) : (

Сообщений пока нет

)}
setDraft(event.target.value)} placeholder="Сообщение" className="rounded-xl" onKeyDown={(event) => { if (event.key === 'Enter' && !event.shiftKey) { event.preventDefault(); void handleSend(); } }} />
)}
) : null} ); }