diff --git a/src/components/chat/ChatWidget.tsx b/src/components/chat/ChatWidget.tsx index 5806c55..2041951 100644 --- a/src/components/chat/ChatWidget.tsx +++ b/src/components/chat/ChatWidget.tsx @@ -146,6 +146,12 @@ export function ChatWidget() { // Toast notifications const [toasts, setToasts] = useState([]) + // In-room search + const [roomSearch, setRoomSearch] = useState('') + const [roomSearchOpen, setRoomSearchOpen] = useState(false) + const roomSearchInputRef = useRef(null) + + const messagesContainerRef = useRef(null) const messagesEndRef = useRef(null) const searchInputRef = useRef(null) const fileInputRef = useRef(null) @@ -154,6 +160,7 @@ export function ChatWidget() { const prevMsgCountRef = useRef(0) const typingTimerRef = useRef | null>(null) const isTypingRef = useRef(false) + const instantScrollRef = useRef(false) const totalUnread = rooms.reduce((s, r) => s + (Number(r.unreadCount) || 0), 0) @@ -223,8 +230,17 @@ export function ChatWidget() { return () => clearInterval(t) }, [open, slug]) - // Auto-scroll - useEffect(() => { messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) }, [messages]) + // Auto-scroll — instant on initial load, smooth on new messages + useEffect(() => { + const el = messagesContainerRef.current + if (!el) return + if (instantScrollRef.current) { + el.scrollTop = el.scrollHeight + instantScrollRef.current = false + } else { + messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' }) + } + }, [messages]) // Poll messages + typing useEffect(() => { @@ -313,10 +329,12 @@ export function ChatWidget() { const openRoom = async (room: ChatRoom) => { setActiveRoom(room); setView('messages'); setLoadingMsgs(true) + setRoomSearch(''); setRoomSearchOpen(false) prevMsgCountRef.current = 0; isTypingRef.current = false try { const msgs = await api.chat.getMessages(slug, room.id) prevMsgCountRef.current = msgs.length + instantScrollRef.current = true setMessages(msgs) await api.chat.markRead(slug, room.id) setRooms(prev => prev.map(r => r.id === room.id ? { ...r, unreadCount: 0 } : r)) @@ -406,6 +424,7 @@ export function ChatWidget() { const goBack = () => { if (view === 'messages') { sendTyping(false) + setRoomSearch(''); setRoomSearchOpen(false) setView('rooms'); setActiveRoom(null); setEditingMsgId(null); setTypingNames([]) } else if (view === 'search') { setView('rooms'); setSearchQuery('') } @@ -480,6 +499,12 @@ export function ChatWidget() { )} + {view === 'messages' && activeRoom?.type !== 'notifications' && ( + + )} {/* ── Rooms ── */} @@ -591,15 +616,33 @@ export function ChatWidget() { {/* ── Messages ── */} {view === 'messages' && ( <> -
+ {/* In-room search bar */} + {roomSearchOpen && ( +
+
+ + setRoomSearch(e.target.value)} + placeholder="Поиск в чате..." + className="flex-1 bg-transparent text-sm text-slate-800 dark:text-slate-200 placeholder-slate-400 outline-none" /> + {roomSearch && } +
+
+ )} +
{loadingMsgs ? (
) : messages.length === 0 ? (
{activeRoom?.type === 'notifications' ? 'Уведомлений пока нет' : 'Нет сообщений. Напишите первым!'}
- ) : ( - messages.map(msg => ( + ) : (() => { + const filtered = roomSearch.trim() + ? messages.filter(m => !m.deletedAt && m.text.toLowerCase().includes(roomSearch.toLowerCase())) + : messages + if (filtered.length === 0) return ( +
Ничего не найдено
+ ) + return filtered.map(msg => ( toggleReaction(msg, emoji)} /> )) - )} + })()} {/* Typing indicator */} {typingNames.length > 0 && (