diff --git a/frontend/src/pages/chat/css/left-panel.module.scss b/frontend/src/pages/chat/css/left-panel.module.scss index 0184cef..0cf9e9c 100644 --- a/frontend/src/pages/chat/css/left-panel.module.scss +++ b/frontend/src/pages/chat/css/left-panel.module.scss @@ -74,45 +74,11 @@ } } - .chatTabs { - margin-top: 5px; - width: 100%; - height: calc(100% - 80px); - display: flex; - flex-direction: column; - min-height: 0; // prevent flex collapse when inner overflows - --mdui-color-surface: $color-dark-surface-container; - --mdui-color-surface-variant: transparent; - - img { - width: 45px; - height: 45px; - border-radius: 20%; - object-fit: cover; - } - - mdui-tabs { - height: 100%; - display: flex; - flex-direction: column; - min-height: 0; // enable inner panel to scroll - } - - mdui-tab-panel[active] { - flex: 1; - display: flex; - flex-direction: column; - min-height: 0; // critical to avoid collapsing - overflow: auto; - } - - mdui-list { - flex: 1; - min-height: 0; // allow scroll area to size correctly - overflow-y: auto; - padding: 0; - margin: 0; - } + .unifiedChatsList { + flex: 1; + min-height: 0; // allow scroll area to size correctly + overflow-y: auto; + margin-top: 10px; } // Search container diff --git a/frontend/src/pages/chat/ui/left/ChatTabs.tsx b/frontend/src/pages/chat/ui/left/ChatTabs.tsx deleted file mode 100644 index 6560205..0000000 --- a/frontend/src/pages/chat/ui/left/ChatTabs.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { useAppState, type ChatTabs } from "@/pages/chat/state"; -import { UnifiedChatsList } from "./UnifiedChatsList"; -import { MaterialTab, MaterialTabPanel, MaterialTabs } from "@/utils/material"; -import styles from "@/pages/chat/css/left-panel.module.scss"; - -export function ChatTabs() { - const { chat, setActiveTab } = useAppState(); - - return ( -
- setActiveTab(e.target.value as ChatTabs)}> - - Чаты - - - Каналы - - - Контакты - - - - - - Скоро будет... - Скоро будет... - -
- ); -} diff --git a/frontend/src/pages/chat/ui/left/LeftPanel.tsx b/frontend/src/pages/chat/ui/left/LeftPanel.tsx index 49cbc8e..7fdce4b 100644 --- a/frontend/src/pages/chat/ui/left/LeftPanel.tsx +++ b/frontend/src/pages/chat/ui/left/LeftPanel.tsx @@ -2,7 +2,7 @@ import { useAppState } from "@/pages/chat/state"; import { useState } from "react"; import { SettingsDialog } from "./settings/SettingsDialog"; import { UsernameSearch } from "./UsernameSearch"; -import { ChatTabs } from "./ChatTabs"; +import { UnifiedChatsList } from "./UnifiedChatsList"; import { ChatHeader } from "./ChatHeader"; import { MaterialBottomAppBar, MaterialFab, MaterialIconButton } from "@/utils/material"; import styles from "@/pages/chat/css/left-panel.module.scss"; @@ -35,7 +35,7 @@ export function LeftPanel() {
- + ); diff --git a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx index 3a4f709..fb40dad 100644 --- a/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx +++ b/frontend/src/pages/chat/ui/left/UnifiedChatsList.tsx @@ -1,4 +1,4 @@ -import { useState, useEffect, useCallback } from "react"; +import { useState, useEffect, useCallback, useMemo } from "react"; import { useAppState } from "@/pages/chat/state"; import { useDM, type DMUser } from "@/pages/chat/hooks/useDM"; import { API_BASE_URL } from "@/core/config"; @@ -36,18 +36,17 @@ interface DMConversation { type ChatItem = PublicChat | DMConversation; +const PUBLIC_CHAT: PublicChat = { + id: "general", + name: "Общий чат", + type: "public" +}; + export function UnifiedChatsList() { const { user, switchToPublicChat, switchToDM, chat } = useAppState(); const { dmUsers, isLoadingUsers, loadUsers } = useDM(); - - const [publicChats] = useState([ - { id: "general", name: "Общий чат", type: "public" }, - { id: "general2", name: "Общий чат 2", type: "public" } - ]); const [lastMessages, setLastMessages] = useState>({}); - const [allChats, setAllChats] = useState([]); - // Load public chat last messages const loadLastMessages = useCallback(async () => { if (!user.authToken) return; @@ -58,13 +57,9 @@ export function UnifiedChatsList() { if (response.ok) { const data = await response.json(); - if (data.messages && data.messages.length > 0) { + if (data.messages?.length > 0) { const lastMessage = data.messages[data.messages.length - 1]; - - setLastMessages({ - general: lastMessage, - general2: lastMessage - }); + setLastMessages({ general: lastMessage }); } } } catch (error) { @@ -72,7 +67,6 @@ export function UnifiedChatsList() { } }, [user.authToken]); - // Load DM users when chats tab is active useEffect(() => { if (chat.activeTab === "chats") { loadUsers(); @@ -80,79 +74,56 @@ export function UnifiedChatsList() { } }, [chat.activeTab, loadUsers, loadLastMessages]); - // Combine public chats and DMs into one list - useEffect(() => { - const publicChatItems: ChatItem[] = publicChats.map(chat => ({ - ...chat, - lastMessage: lastMessages[chat.id] - })); + const allChats = useMemo(() => { + return [ + ...dmUsers.map((user: DMUser) => ({ + ...user, + userId: user.id, + type: "dm" as const + })), + { + ...PUBLIC_CHAT, + lastMessage: lastMessages[PUBLIC_CHAT.id] + } + ]; + }, [lastMessages, dmUsers]); - const dmChatItems: ChatItem[] = dmUsers.map((user: DMUser) => ({ - id: user.id, - userId: user.id, // Add userId field - username: user.username, - display_name: user.display_name, - profile_picture: user.profile_picture, - online: user.online, - type: "dm" as const, - lastMessage: user.lastMessage, - unreadCount: user.unreadCount, - publicKey: user.publicKey - })); - - // Combine and sort by last message timestamp (DMs first, then public chats) - const combined = [...dmChatItems, ...publicChatItems]; - setAllChats(combined); - }, [publicChats, lastMessages, dmUsers]); - - // WebSocket listener for public chat message updates useEffect(() => { if (!websocket) return; - const handleWebSocketMessage = (e: MessageEvent) => { + function handleWebSocketMessage(e: MessageEvent) { try { const msg = JSON.parse(e.data); if (msg.type === "newMessage") { const newMessage = msg.data as Message; - // Update all public chats with the new message - setLastMessages(prev => { - const updated = { ...prev }; - publicChats.forEach(chat => { - updated[chat.id] = newMessage; - }); - return updated; - }); + setLastMessages(prev => ({ + ...prev, + [PUBLIC_CHAT.id]: newMessage + })); } else if (msg.type === "messageEdited") { const editedMessage = msg.data as Message; - // Update only if the edited message is the current last message setLastMessages(prev => { - const updated = { ...prev }; - publicChats.forEach(chat => { - if (updated[chat.id]?.id === editedMessage.id) { - updated[chat.id] = editedMessage; - } - }); - return updated; + if (prev[PUBLIC_CHAT.id]?.id === editedMessage.id) { + return { + ...prev, + [PUBLIC_CHAT.id]: editedMessage + }; + } + return prev; }); } else if (msg.type === "messageDeleted") { const deletedMessageId = msg.data?.message_id; - let needsReload = false; - setLastMessages(prev => { - const updated = { ...prev }; - publicChats.forEach(chat => { - if (updated[chat.id]?.id === deletedMessageId) { - updated[chat.id] = undefined; - needsReload = true; - } - }); - return updated; + if (prev[PUBLIC_CHAT.id]?.id === deletedMessageId) { + loadLastMessages(); + return { + ...prev, + [PUBLIC_CHAT.id]: undefined + }; + } + return prev; }); - - if (needsReload) { - loadLastMessages(); - } } } catch (error) { console.error("Failed to handle WebSocket message in UnifiedChatsList:", error); @@ -161,45 +132,33 @@ export function UnifiedChatsList() { websocket.addEventListener("message", handleWebSocketMessage); return () => websocket.removeEventListener("message", handleWebSocketMessage); - }, [publicChats, loadLastMessages]); + }, [loadLastMessages]); - // Subscribe to online status for all DM users useEffect(() => { - const dmUsers = allChats.filter(chat => chat.type === "dm") as DMConversation[]; - - // Subscribe to all DM users dmUsers.forEach(dmUser => { onlineStatusManager.subscribe(dmUser.id); }); - // Cleanup function to unsubscribe from all users return () => { dmUsers.forEach(dmUser => { onlineStatusManager.unsubscribe(dmUser.id); }); }; - }, [allChats]); + }, [dmUsers]); function formatPublicChatMessage(chatId: string): string { const lastMessage = lastMessages[chatId]; - if (!lastMessage) { - return ""; - } + if (!lastMessage) return ""; const isCurrentUser = lastMessage.user_id === user.currentUser?.id; const prefix = isCurrentUser ? "Вы: " : `${lastMessage.username}: `; - - const maxContentLength = 50 - prefix.length; - const content = lastMessage.content.length > maxContentLength - ? lastMessage.content.substring(0, maxContentLength) + "..." + const maxLength = 50 - prefix.length; + const content = lastMessage.content.length > maxLength + ? lastMessage.content.substring(0, maxLength) + "..." : lastMessage.content; return prefix + content; - } - - async function handlePublicChatClick(chatName: string) { - await switchToPublicChat(chatName); - } + }; async function handleDMClick(dmConversation: DMConversation) { if (!dmConversation.publicKey) { @@ -207,12 +166,11 @@ export function UnifiedChatsList() { if (!authToken) return; const publicKey = await fetchUserPublicKey(dmConversation.id, authToken); - if (publicKey) { - dmConversation.publicKey = publicKey; - } else { + if (!publicKey) { console.error("Failed to get public key for user:", dmConversation.id); return; } + dmConversation.publicKey = publicKey; } await switchToDM({ @@ -222,26 +180,27 @@ export function UnifiedChatsList() { profilePicture: dmConversation.profile_picture, online: dmConversation.online || false }); - } + }; if (isLoadingUsers) { return ; } return ( - + {allChats.map((chat) => { if (chat.type === "public") { + const formattedMessage = formatPublicChatMessage(chat.id); return ( handlePublicChatClick(chat.name)} + onClick={() => switchToPublicChat(chat.name)} style={{ cursor: "pointer" }} > - {formatPublicChatMessage(chat.id) && ( + {formattedMessage && ( - {formatPublicChatMessage(chat.id)} + {formattedMessage} )} ); - } else { - return ( - handleDMClick(chat)} - style={{ cursor: "pointer" }} - > -
- {chat.display_name} - -
- - {chat.lastMessage || "Нет сообщений"} - -
- {chat.display_name} { - e.target.src = defaultAvatar; - }} - /> - -
- {chat.unreadCount > 0 && ( - - {chat.unreadCount} - - )} -
- ); } + + return ( + handleDMClick(chat)} + style={{ cursor: "pointer" }} + > +
+ {chat.display_name} + +
+ + {chat.lastMessage || "Нет сообщений"} + +
+ {chat.display_name} { + e.target.src = defaultAvatar; + }} + /> + +
+ {chat.unreadCount > 0 && ( + + {chat.unreadCount} + + )} +
+ ); })}
);