import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'react-router'; import type { Message } from '@/api/messages'; import { ChatView } from '@/components/chat/ChatView'; import { PinsPanel } from '@/components/chat/PinsPanel'; import { SearchPanel } from '@/components/chat/SearchPanel'; import { useAuthorDirectory } from '@/components/chat/useAuthorDirectory'; import { Card } from '@/components/ui/primitives'; import { canManageMessages, canSendMessages } from '@/lib/identity'; import { useMessagesStore } from '@/stores/messages'; import { useSessionStore } from '@/stores/session'; /** * Область контента комнаты: для текстовых комнат — чат (лента, поиск, пины), * для голосовых сохраняется заглушка до следующих фаз. */ export default function GuildView() { const { t } = useTranslation(); const params = useParams<{ guildId?: string; channelId?: string }>(); const guild = useSessionStore((state) => params.guildId === undefined ? null : (state.guilds.find((item) => item.id === params.guildId) ?? null), ); const sessionUser = useSessionStore((state) => state.user); const channel = guild?.channels.find((item) => item.id === params.channelId) ?? null; const channelId = channel?.id ?? null; const [searchOpen, setSearchOpen] = useState(false); const [pinsOpen, setPinsOpen] = useState(false); const [jump, setJump] = useState<{ id: string; token: number } | null>(null); const messages = useMessagesStore((state) => channelId === null ? undefined : state.channels[channelId]?.messages, ); const pins = useMessagesStore((state) => channelId === null ? undefined : state.channels[channelId]?.pins, ); const pinsLoaded = useMessagesStore( (state) => channelId !== null && state.channels[channelId]?.pinsLoaded === true, ); // Справочник авторов для поиска и пинов: id берём из загруженной ленты. const authorIds = useMemo( () => (messages ?? []) .map((message: Message) => message.author_id) .filter((id): id is string => id !== undefined), [messages], ); const authors = useAuthorDirectory(guild?.id ?? null, authorIds); const onJumpHandled = useCallback(() => setJump(null), []); const goToMessage = useCallback((id: string) => { setJump({ id, token: Date.now() }); }, []); if (params.channelId === undefined || channel === null) { return ( {t('channels.noSelection.title')} {t('channels.noSelection.body')} ); } const isVoice = channel.type === 'voice'; const permissions = guild?.my_permissions ?? []; const isAdmin = sessionUser?.is_instance_admin === true; const canPin = canManageMessages(permissions, isAdmin) && canSendMessages(permissions, isAdmin); // Счётчик пинов: пока панель не открывали, ориентируемся на загруженную ленту. const pinCount = pinsLoaded ? (pins?.length ?? 0) : (messages ?? []).filter((message: Message) => message.pinned).length; return ( {isVoice ? '🔊' : '#'} {channel.name} {channel.slowmode_seconds === undefined || channel.slowmode_seconds === 0 ? null : ( {t('channels.slowmode', { seconds: channel.slowmode_seconds })} )} {isVoice ? null : ( setSearchOpen((open) => !open)} > 🔍 setPinsOpen(true)} > 📌 {pinCount === 0 ? null : {pinCount}} )} {isVoice ? ( {t('channels.voicePlaceholder.title')} {t('channels.voicePlaceholder.body', { name: channel.name })} ) : ( <> setSearchOpen(false)} authors={authors} onJump={goToMessage} /> setPinsOpen(false)} authors={authors} canManageMessages={canPin} onJump={(id) => { setPinsOpen(false); goToMessage(id); }} /> > )} ); }
{t('channels.noSelection.body')}
{t('channels.voicePlaceholder.body', { name: channel.name })}