Merge remote-tracking branch 'origin/lev' into lev
This commit is contained in:
commit
eed6d1f54e
607
web/src/Components/Pages/ChatPage/ChatPage.jsx
Normal file
607
web/src/Components/Pages/ChatPage/ChatPage.jsx
Normal file
@ -0,0 +1,607 @@
|
||||
import React, { useRef, useEffect } from "react";
|
||||
import {
|
||||
Card,
|
||||
Input,
|
||||
Avatar,
|
||||
Badge,
|
||||
Typography,
|
||||
Space,
|
||||
Button,
|
||||
Tag,
|
||||
Empty,
|
||||
Upload,
|
||||
Drawer,
|
||||
Row,
|
||||
Col,
|
||||
Collapse,
|
||||
List,
|
||||
Select,
|
||||
Divider,
|
||||
} from "antd";
|
||||
import {
|
||||
SendOutlined,
|
||||
PaperClipOutlined,
|
||||
SearchOutlined,
|
||||
TeamOutlined,
|
||||
UserOutlined,
|
||||
MessageOutlined,
|
||||
InfoCircleOutlined,
|
||||
FileOutlined,
|
||||
FilterOutlined,
|
||||
} from "@ant-design/icons";
|
||||
import useChatPage from "./useChatPage.js";
|
||||
|
||||
const { TextArea } = Input;
|
||||
const { Title, Text } = Typography;
|
||||
|
||||
const ChatPage = () => {
|
||||
const {
|
||||
selectedChat,
|
||||
messages,
|
||||
messageText,
|
||||
setMessageText,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
selectedCourse,
|
||||
setSelectedCourse,
|
||||
selectedGroup,
|
||||
setSelectedGroup,
|
||||
selectedTopic,
|
||||
setSelectedTopic,
|
||||
courses,
|
||||
groups,
|
||||
topics,
|
||||
currentUser,
|
||||
handleSelectChat,
|
||||
handleSendMessage,
|
||||
handleFileUpload,
|
||||
filteredChatList,
|
||||
infoDrawerVisible,
|
||||
setInfoDrawerVisible,
|
||||
chatParticipants,
|
||||
} = useChatPage();
|
||||
|
||||
const messagesEndRef = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
|
||||
}, [messages]);
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24, backgroundColor: "#ffffff", minHeight: "100vh" }}>
|
||||
{/* Заголовок */}
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Title level={2} style={{ margin: 0, color: "#262626" }}>
|
||||
<MessageOutlined style={{ marginRight: 12 }} />
|
||||
Мессенджер
|
||||
</Title>
|
||||
</div>
|
||||
|
||||
<Row gutter={0}>
|
||||
{/* Левая колонка — список чатов */}
|
||||
<Col xs={24} sm={24} md={8} lg={8} xl={8}>
|
||||
<Card
|
||||
style={{
|
||||
height: "calc(100vh - 180px)",
|
||||
borderRadius: 12,
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Поиск и фильтры */}
|
||||
<div style={{ padding: "16px 20px", borderBottom: "1px solid #f0f0f0", flexShrink: 0 }}>
|
||||
<Input
|
||||
placeholder="Поиск чатов..."
|
||||
prefix={<SearchOutlined />}
|
||||
value={searchQuery}
|
||||
onChange={(e) => setSearchQuery(e.target.value)}
|
||||
allowClear
|
||||
size="large"
|
||||
style={{ marginBottom: 12 }}
|
||||
/>
|
||||
|
||||
<Collapse
|
||||
bordered={false}
|
||||
ghost
|
||||
items={[
|
||||
{
|
||||
key: "1",
|
||||
label: (
|
||||
<Space size={6}>
|
||||
<FilterOutlined />
|
||||
<Text strong>Фильтры</Text>
|
||||
{(selectedCourse || selectedGroup || selectedTopic) && (
|
||||
<Badge count="•" style={{ backgroundColor: "#1890ff" }} />
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
children: (
|
||||
<Space direction="vertical" style={{ width: "100%" }} size="middle">
|
||||
<Select
|
||||
placeholder="Курс"
|
||||
value={selectedCourse}
|
||||
onChange={setSelectedCourse}
|
||||
allowClear
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{courses.map((c) => (
|
||||
<Select.Option key={c.id} value={c.id}>
|
||||
{c.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
placeholder="Группа"
|
||||
value={selectedGroup}
|
||||
onChange={setSelectedGroup}
|
||||
allowClear
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{groups.map((g) => (
|
||||
<Select.Option key={g.id} value={g.id}>
|
||||
{g.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
<Select
|
||||
placeholder="Тема"
|
||||
value={selectedTopic}
|
||||
onChange={setSelectedTopic}
|
||||
allowClear
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{topics.map((t) => (
|
||||
<Select.Option key={t.id} value={t.id}>
|
||||
{t.name}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
|
||||
{(selectedCourse || selectedGroup || selectedTopic) && (
|
||||
<Button
|
||||
type="link"
|
||||
size="small"
|
||||
onClick={() => {
|
||||
setSelectedCourse(null);
|
||||
setSelectedGroup(null);
|
||||
setSelectedTopic(null);
|
||||
}}
|
||||
>
|
||||
Сбросить фильтры
|
||||
</Button>
|
||||
)}
|
||||
</Space>
|
||||
),
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Список чатов */}
|
||||
<div style={{ flex: 1, overflowY: "auto", overflowX: "hidden" }}>
|
||||
{filteredChatList.length === 0 ? (
|
||||
<Empty
|
||||
description="Чаты не найдены"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
style={{ marginTop: 60 }}
|
||||
/>
|
||||
) : (
|
||||
filteredChatList.map((chat) => (
|
||||
<div
|
||||
key={chat.id}
|
||||
onClick={() => handleSelectChat(chat.id)}
|
||||
style={{
|
||||
padding: "14px 20px",
|
||||
borderBottom: "1px solid #f0f0f0",
|
||||
background: selectedChat?.id === chat.id ? "#e6f7ff" : "transparent",
|
||||
cursor: "pointer",
|
||||
transition: "all 0.2s",
|
||||
}}
|
||||
onMouseEnter={(e) => {
|
||||
if (selectedChat?.id !== chat.id) e.currentTarget.style.background = "#f9f9f9";
|
||||
}}
|
||||
onMouseLeave={(e) => {
|
||||
if (selectedChat?.id !== chat.id) e.currentTarget.style.background = "transparent";
|
||||
}}
|
||||
>
|
||||
<div style={{ display: "flex", alignItems: "flex-start", gap: 12 }}>
|
||||
<Badge count={chat.unreadCount} size="small">
|
||||
<Avatar
|
||||
size={44}
|
||||
icon={chat.type === "group" ? <TeamOutlined /> : <UserOutlined />}
|
||||
style={{
|
||||
backgroundColor: chat.type === "group" ? "#52c41a" : "#1890ff",
|
||||
flexShrink: 0
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
<div style={{ flex: 1, minWidth: 0 }}>
|
||||
<div style={{
|
||||
fontWeight: 500,
|
||||
fontSize: 15,
|
||||
marginBottom: 4,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}>
|
||||
{chat.name}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
fontSize: 13,
|
||||
color: "#8c8c8c",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
marginBottom: 6
|
||||
}}
|
||||
>
|
||||
{chat.lastMessage || "Нет сообщений"}
|
||||
</div>
|
||||
<Space size={4} wrap style={{ marginTop: 6 }}>
|
||||
{chat.course && <Tag color="blue" style={{ fontSize: 11, margin: 0 }}>{chat.course}</Tag>}
|
||||
{chat.group && <Tag color="green" style={{ fontSize: 11, margin: 0 }}>{chat.group}</Tag>}
|
||||
{chat.topic && <Tag color="orange" style={{ fontSize: 11, margin: 0 }}>{chat.topic}</Tag>}
|
||||
</Space>
|
||||
</div>
|
||||
<div style={{
|
||||
fontSize: 11,
|
||||
color: "#8c8c8c",
|
||||
flexShrink: 0,
|
||||
marginLeft: 8
|
||||
}}>
|
||||
{new Date(chat.lastActivity).toLocaleTimeString("ru-RU", {
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
</Col>
|
||||
|
||||
{/* Правая колонка — чат */}
|
||||
<Col xs={24} sm={24} md={16} lg={16} xl={16}>
|
||||
{selectedChat ? (
|
||||
<Card
|
||||
style={{
|
||||
height: "calc(100vh - 180px)",
|
||||
borderRadius: 12,
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||||
}}
|
||||
styles={{
|
||||
body: {
|
||||
padding: 0,
|
||||
height: "100%",
|
||||
display: "flex",
|
||||
flexDirection: "column"
|
||||
}
|
||||
}}
|
||||
>
|
||||
{/* Шапка чата */}
|
||||
<div style={{
|
||||
padding: "16px 24px",
|
||||
borderBottom: "1px solid #f0f0f0",
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{
|
||||
display: "flex",
|
||||
justifyContent: "space-between",
|
||||
alignItems: "center",
|
||||
gap: 16,
|
||||
flexWrap: "wrap"
|
||||
}}>
|
||||
<div style={{ display: "flex", alignItems: "center", gap: 12, minWidth: 0, flex: 1 }}>
|
||||
<Avatar
|
||||
size={48}
|
||||
icon={selectedChat.type === "group" ? <TeamOutlined /> : <UserOutlined />}
|
||||
style={{
|
||||
backgroundColor: selectedChat.type === "group" ? "#52c41a" : "#1890ff",
|
||||
flexShrink: 0
|
||||
}}
|
||||
/>
|
||||
<div style={{ minWidth: 0, flex: 1 }}>
|
||||
<Title level={4} style={{
|
||||
margin: 0,
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap"
|
||||
}}>
|
||||
{selectedChat.name}
|
||||
</Title>
|
||||
<Space size={6} wrap style={{ marginTop: 4 }}>
|
||||
{selectedChat.course && <Tag color="blue" style={{ fontSize: 11 }}>{selectedChat.course}</Tag>}
|
||||
{selectedChat.group && <Tag color="green" style={{ fontSize: 11 }}>{selectedChat.group}</Tag>}
|
||||
{selectedChat.topic && <Tag color="orange" style={{ fontSize: 11 }}>{selectedChat.topic}</Tag>}
|
||||
</Space>
|
||||
</div>
|
||||
</div>
|
||||
<Button
|
||||
icon={<InfoCircleOutlined />}
|
||||
onClick={() => setInfoDrawerVisible(true)}
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Инфо
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Сообщения */}
|
||||
<div style={{
|
||||
flex: 1,
|
||||
overflowY: "auto",
|
||||
overflowX: "hidden",
|
||||
padding: "24px",
|
||||
background: "#f8f9fa"
|
||||
}}>
|
||||
{messages.length === 0 ? (
|
||||
<Empty
|
||||
description="Нет сообщений"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
) : (
|
||||
messages.map((msg) => {
|
||||
const isOwn = msg.senderId === currentUser.id;
|
||||
return (
|
||||
<div
|
||||
key={msg.id}
|
||||
style={{
|
||||
marginBottom: 20,
|
||||
display: "flex",
|
||||
justifyContent: isOwn ? "flex-end" : "flex-start",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{
|
||||
maxWidth: "70%",
|
||||
display: "flex",
|
||||
gap: 12,
|
||||
flexDirection: isOwn ? "row-reverse" : "row",
|
||||
}}
|
||||
>
|
||||
{!isOwn && (
|
||||
<Avatar
|
||||
style={{
|
||||
backgroundColor: "#1890ff",
|
||||
flexShrink: 0
|
||||
}}
|
||||
>
|
||||
{msg.senderName[0].toUpperCase()}
|
||||
</Avatar>
|
||||
)}
|
||||
<div style={{ minWidth: 0 }}>
|
||||
{!isOwn && (
|
||||
<Text
|
||||
strong
|
||||
style={{
|
||||
fontSize: 13,
|
||||
display: "block",
|
||||
marginBottom: 4
|
||||
}}
|
||||
>
|
||||
{msg.senderName}
|
||||
</Text>
|
||||
)}
|
||||
<div
|
||||
style={{
|
||||
background: isOwn ? "#1890ff" : "#fff",
|
||||
color: isOwn ? "#fff" : "#000",
|
||||
padding: "10px 16px",
|
||||
borderRadius: 18,
|
||||
boxShadow: "0 1px 3px rgba(0,0,0,0.12)",
|
||||
border: isOwn ? "none" : "1px solid #f0f0f0",
|
||||
wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{msg.text}
|
||||
{msg.attachment && (
|
||||
<div style={{ marginTop: 8 }}>
|
||||
<Tag
|
||||
icon={<FileOutlined />}
|
||||
color={isOwn ? "default" : "blue"}
|
||||
style={{
|
||||
background: isOwn ? "rgba(255,255,255,0.2)" : undefined,
|
||||
borderColor: isOwn ? "rgba(255,255,255,0.3)" : undefined,
|
||||
color: isOwn ? "#fff" : undefined
|
||||
}}
|
||||
>
|
||||
{msg.attachment}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: 11,
|
||||
marginTop: 4,
|
||||
display: "block",
|
||||
textAlign: isOwn ? "right" : "left"
|
||||
}}
|
||||
>
|
||||
{msg.timestamp}
|
||||
</Text>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
)}
|
||||
<div ref={messagesEndRef} />
|
||||
</div>
|
||||
|
||||
{/* Поле ввода */}
|
||||
<div style={{
|
||||
padding: "16px 24px",
|
||||
borderTop: "1px solid #f0f0f0",
|
||||
flexShrink: 0
|
||||
}}>
|
||||
<div style={{ display: "flex", gap: 8, alignItems: "flex-end" }}>
|
||||
<Upload
|
||||
beforeUpload={handleFileUpload}
|
||||
showUploadList={false}
|
||||
>
|
||||
<Button
|
||||
icon={<PaperClipOutlined />}
|
||||
size="large"
|
||||
style={{ flexShrink: 0 }}
|
||||
/>
|
||||
</Upload>
|
||||
<TextArea
|
||||
value={messageText}
|
||||
onChange={(e) => setMessageText(e.target.value)}
|
||||
placeholder="Напишите сообщение..."
|
||||
autoSize={{ minRows: 1, maxRows: 5 }}
|
||||
onPressEnter={(e) => {
|
||||
if (!e.shiftKey) {
|
||||
e.preventDefault();
|
||||
handleSendMessage();
|
||||
}
|
||||
}}
|
||||
style={{
|
||||
resize: "none",
|
||||
flex: 1
|
||||
}}
|
||||
/>
|
||||
<Button
|
||||
type="primary"
|
||||
icon={<SendOutlined />}
|
||||
onClick={handleSendMessage}
|
||||
disabled={!messageText.trim()}
|
||||
size="large"
|
||||
style={{ flexShrink: 0 }}
|
||||
>
|
||||
Отправить
|
||||
</Button>
|
||||
</div>
|
||||
<Text
|
||||
type="secondary"
|
||||
style={{
|
||||
fontSize: 12,
|
||||
marginTop: 8,
|
||||
display: "block"
|
||||
}}
|
||||
>
|
||||
Enter — отправить • Shift+Enter — новая строка
|
||||
</Text>
|
||||
</div>
|
||||
</Card>
|
||||
) : (
|
||||
<Card
|
||||
style={{
|
||||
height: "calc(100vh - 180px)",
|
||||
borderRadius: 12,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
background: "#fafafa",
|
||||
boxShadow: "0 2px 8px rgba(0,0,0,0.1)",
|
||||
}}
|
||||
>
|
||||
<Empty
|
||||
description="Выберите чат, чтобы начать общение"
|
||||
image={Empty.PRESENTED_IMAGE_SIMPLE}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
{/* Drawer — информация о чате */}
|
||||
<Drawer
|
||||
title="Информация о чате"
|
||||
placement="right"
|
||||
open={infoDrawerVisible}
|
||||
onClose={() => setInfoDrawerVisible(false)}
|
||||
width={400}
|
||||
>
|
||||
{selectedChat && (
|
||||
<Space direction="vertical" size="large" style={{ width: "100%" }}>
|
||||
<div>
|
||||
<Text type="secondary">Название</Text>
|
||||
<Title level={5} style={{ margin: "4px 0 0" }}>
|
||||
{selectedChat.name}
|
||||
</Title>
|
||||
</div>
|
||||
<div>
|
||||
<Text type="secondary">Тип</Text>
|
||||
<br />
|
||||
<Tag
|
||||
color={selectedChat.type === "group" ? "green" : "blue"}
|
||||
style={{ marginTop: 4 }}
|
||||
>
|
||||
{selectedChat.type === "group" ? "Групповой" : "Личный"}
|
||||
</Tag>
|
||||
</div>
|
||||
{selectedChat.course && (
|
||||
<div>
|
||||
<Text type="secondary">Курс</Text>
|
||||
<br />
|
||||
<Tag color="blue" style={{ marginTop: 4 }}>
|
||||
{selectedChat.course}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
{selectedChat.group && (
|
||||
<div>
|
||||
<Text type="secondary">Группа</Text>
|
||||
<br />
|
||||
<Tag color="green" style={{ marginTop: 4 }}>
|
||||
{selectedChat.group}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
{selectedChat.topic && (
|
||||
<div>
|
||||
<Text type="secondary">Тема</Text>
|
||||
<br />
|
||||
<Tag color="orange" style={{ marginTop: 4 }}>
|
||||
{selectedChat.topic}
|
||||
</Tag>
|
||||
</div>
|
||||
)}
|
||||
<Divider />
|
||||
<div>
|
||||
<Text strong>Участники ({chatParticipants.length})</Text>
|
||||
<List
|
||||
style={{ marginTop: 12 }}
|
||||
dataSource={chatParticipants}
|
||||
renderItem={(p) => (
|
||||
<List.Item style={{ padding: "12px 0" }}>
|
||||
<List.Item.Meta
|
||||
avatar={
|
||||
<Avatar style={{ backgroundColor: "#1890ff" }}>
|
||||
{p.name[0]}
|
||||
</Avatar>
|
||||
}
|
||||
title={p.name}
|
||||
description={
|
||||
<Tag color={p.role === "teacher" ? "purple" : "default"}>
|
||||
{p.role === "teacher" ? "Преподаватель" : "Студент"}
|
||||
</Tag>
|
||||
}
|
||||
/>
|
||||
</List.Item>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
)}
|
||||
</Drawer>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ChatPage;
|
||||
281
web/src/Components/Pages/ChatPage/useChatPage.js
Normal file
281
web/src/Components/Pages/ChatPage/useChatPage.js
Normal file
@ -0,0 +1,281 @@
|
||||
import { useState, useMemo, useEffect } from "react";
|
||||
import { message } from "antd";
|
||||
|
||||
const useChatPage = () => {
|
||||
// Состояния
|
||||
const [selectedChat, setSelectedChat] = useState(null);
|
||||
const [messageText, setMessageText] = useState("");
|
||||
const [selectedCourse, setSelectedCourse] = useState(null);
|
||||
const [selectedGroup, setSelectedGroup] = useState(null);
|
||||
const [selectedTopic, setSelectedTopic] = useState(null);
|
||||
const [searchQuery, setSearchQuery] = useState("");
|
||||
const [infoDrawerVisible, setInfoDrawerVisible] = useState(false);
|
||||
|
||||
// Текущий пользователь (заглушка)
|
||||
const currentUser = {
|
||||
id: 1,
|
||||
name: "Иван Иванов",
|
||||
role: "teacher"
|
||||
};
|
||||
|
||||
// Заглушка - курсы
|
||||
const courses = [
|
||||
{id: 1, name: "Основы программирования"},
|
||||
{id: 2, name: "Веб-разработка"},
|
||||
{id: 3, name: "Базы данных"},
|
||||
{id: 4, name: "Алгоритмы и структуры данных"},
|
||||
];
|
||||
|
||||
// Заглушка - группы
|
||||
const groups = [
|
||||
{id: 1, name: "ПИ-201"},
|
||||
{id: 2, name: "ПИ-202"},
|
||||
{id: 3, name: "ПИ-203"},
|
||||
{id: 4, name: "ПИ-301"},
|
||||
];
|
||||
|
||||
// Заглушка - темы
|
||||
const topics = [
|
||||
{id: 1, name: "Домашние задания"},
|
||||
{id: 2, name: "Лекции"},
|
||||
{id: 3, name: "Проекты"},
|
||||
{id: 4, name: "Вопросы по курсу"},
|
||||
{id: 5, name: "Организационные вопросы"},
|
||||
];
|
||||
|
||||
// Заглушка - список чатов
|
||||
const chatList = [
|
||||
{
|
||||
id: 1,
|
||||
name: "ПИ-201 - Основы программирования",
|
||||
type: "group",
|
||||
course: "Основы программирования",
|
||||
courseId: 1,
|
||||
group: "ПИ-201",
|
||||
groupId: 1,
|
||||
topic: "Домашние задания",
|
||||
topicId: 1,
|
||||
lastMessage: "Не забудьте сдать ДЗ до конца недели",
|
||||
unreadCount: 3,
|
||||
lastActivity: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Веб-разработка - Вопросы по курсу",
|
||||
type: "group",
|
||||
course: "Веб-разработка",
|
||||
courseId: 2,
|
||||
group: "ПИ-202",
|
||||
groupId: 2,
|
||||
topic: "Вопросы по курсу",
|
||||
topicId: 4,
|
||||
lastMessage: "Как правильно настроить webpack?",
|
||||
unreadCount: 0,
|
||||
lastActivity: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Мария Петрова",
|
||||
type: "personal",
|
||||
course: null,
|
||||
courseId: null,
|
||||
group: null,
|
||||
groupId: null,
|
||||
topic: null,
|
||||
topicId: null,
|
||||
lastMessage: "Здравствуйте, у меня вопрос по заданию...",
|
||||
unreadCount: 1,
|
||||
lastActivity: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "ПИ-203 - Базы данных",
|
||||
type: "group",
|
||||
course: "Базы данных",
|
||||
courseId: 3,
|
||||
group: "ПИ-203",
|
||||
groupId: 3,
|
||||
topic: "Лекции",
|
||||
topicId: 2,
|
||||
lastMessage: "Материалы по нормализации загружены",
|
||||
unreadCount: 0,
|
||||
lastActivity: new Date().toISOString()
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "Проектная работа - ПИ-201",
|
||||
type: "group",
|
||||
course: "Веб-разработка",
|
||||
courseId: 2,
|
||||
group: "ПИ-201",
|
||||
groupId: 1,
|
||||
topic: "Проекты",
|
||||
topicId: 3,
|
||||
lastMessage: "Когда защита проектов?",
|
||||
unreadCount: 5,
|
||||
lastActivity: new Date().toISOString()
|
||||
},
|
||||
];
|
||||
|
||||
// Заглушка - сообщения выбранного чата
|
||||
const [messages, setMessages] = useState([
|
||||
{
|
||||
id: 1,
|
||||
senderId: 2,
|
||||
senderName: "Мария Петрова",
|
||||
text: "Здравствуйте! У меня вопрос по второму домашнему заданию.",
|
||||
timestamp: "10:30",
|
||||
attachment: null
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
senderId: 1,
|
||||
senderName: "Иван Иванов",
|
||||
text: "Здравствуйте, Мария! Конечно, задавайте вопрос.",
|
||||
timestamp: "10:32",
|
||||
attachment: null
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
senderId: 2,
|
||||
senderName: "Мария Петрова",
|
||||
text: "Не могу понять, как правильно реализовать рекурсию в третьей задаче.",
|
||||
timestamp: "10:33",
|
||||
attachment: null
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
senderId: 1,
|
||||
senderName: "Иван Иванов",
|
||||
text: "Давайте разберем по шагам. Сначала определите базовый случай, когда рекурсия должна остановиться.",
|
||||
timestamp: "10:35",
|
||||
attachment: null
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
senderId: 2,
|
||||
senderName: "Мария Петрова",
|
||||
text: "Вот мой код, подскажите, что не так?",
|
||||
timestamp: "10:37",
|
||||
attachment: "code.py"
|
||||
},
|
||||
]);
|
||||
|
||||
// Заглушка - участники чата
|
||||
const chatParticipants = [
|
||||
{
|
||||
id: 1,
|
||||
name: "Иван Иванов",
|
||||
role: "teacher"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "Мария Петрова",
|
||||
role: "student"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "Алексей Сидоров",
|
||||
role: "student"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "Екатерина Смирнова",
|
||||
role: "student"
|
||||
},
|
||||
];
|
||||
|
||||
// Фильтрация списка чатов
|
||||
const filteredChatList = useMemo(() => {
|
||||
return chatList.filter(chat => {
|
||||
if (searchQuery && !chat.name.toLowerCase().includes(searchQuery.toLowerCase())) {
|
||||
return false;
|
||||
}
|
||||
if (selectedCourse && chat.courseId !== selectedCourse) {
|
||||
return false;
|
||||
}
|
||||
if (selectedGroup && chat.groupId !== selectedGroup) {
|
||||
return false;
|
||||
}
|
||||
if (selectedTopic && chat.topicId !== selectedTopic) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}, [searchQuery, selectedCourse, selectedGroup, selectedTopic, chatList]);
|
||||
|
||||
// Выбор чата
|
||||
const handleSelectChat = (chatId) => {
|
||||
const chat = chatList.find(c => c.id === chatId);
|
||||
setSelectedChat(chat);
|
||||
};
|
||||
|
||||
// Автоматический выбор первого чата при загрузке
|
||||
useEffect(() => {
|
||||
if (chatList.length > 0 && !selectedChat) {
|
||||
handleSelectChat(chatList[0].id);
|
||||
}
|
||||
}, [chatList, selectedChat]);
|
||||
|
||||
// Отправка сообщения
|
||||
const handleSendMessage = () => {
|
||||
if (!messageText.trim()) return;
|
||||
|
||||
const newMessage = {
|
||||
id: messages.length + 1,
|
||||
senderId: currentUser.id,
|
||||
senderName: currentUser.name,
|
||||
text: messageText,
|
||||
timestamp: new Date().toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'}),
|
||||
attachment: null
|
||||
};
|
||||
|
||||
setMessages([...messages, newMessage]);
|
||||
setMessageText("");
|
||||
message.success("Сообщение отправлено");
|
||||
};
|
||||
|
||||
// Загрузка файла
|
||||
const handleFileUpload = (file) => {
|
||||
const newMessage = {
|
||||
id: messages.length + 1,
|
||||
senderId: currentUser.id,
|
||||
senderName: currentUser.name,
|
||||
text: "Отправлен файл:",
|
||||
timestamp: new Date().toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit'}),
|
||||
attachment: file.name
|
||||
};
|
||||
|
||||
setMessages([...messages, newMessage]);
|
||||
message.success(`Файл ${file.name} отправлен`);
|
||||
};
|
||||
|
||||
return {
|
||||
chatList,
|
||||
selectedChat,
|
||||
messages,
|
||||
messageText,
|
||||
setMessageText,
|
||||
selectedCourse,
|
||||
setSelectedCourse,
|
||||
selectedGroup,
|
||||
setSelectedGroup,
|
||||
selectedTopic,
|
||||
setSelectedTopic,
|
||||
searchQuery,
|
||||
setSearchQuery,
|
||||
courses,
|
||||
groups,
|
||||
topics,
|
||||
currentUser,
|
||||
handleSelectChat,
|
||||
handleSendMessage,
|
||||
handleFileUpload,
|
||||
filteredChatList,
|
||||
infoDrawerVisible,
|
||||
setInfoDrawerVisible,
|
||||
chatParticipants
|
||||
};
|
||||
};
|
||||
|
||||
export default useChatPage;
|
||||
Loading…
x
Reference in New Issue
Block a user