From 390f1f52c8fc0442b3b716eda3302425b835061b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=92=D0=B8=D1=82=D0=B0=D0=BB=D0=B8=D0=B9=20=D0=9B=D0=B0?= =?UTF-8?q?=D0=B2=D1=88=D0=BE=D0=BD=D0=BE=D0=BA?= <114582703+valavshonok@users.noreply.github.com> Date: Sun, 23 Nov 2025 10:30:31 +0300 Subject: [PATCH] add group chat --- src/assets/icons/input/index.ts | 2 + src/assets/icons/input/send.svg | 3 + .../drop-down-list/DropDownList.tsx | 1 - src/components/modal/ConfirmModal.tsx | 2 +- src/pages/ContestEditor.tsx | 4 +- src/redux/slices/groupChat.ts | 57 ++--- src/views/articleeditor/Editor.tsx | 8 +- src/views/home/articles/Filter.tsx | 8 +- src/views/home/contest/Missions.tsx | 210 +++++++++--------- src/views/home/contests/Filter.tsx | 8 +- src/views/home/group/Group.tsx | 1 - src/views/home/group/chat/Chat.tsx | 179 ++++++++++++--- src/views/home/group/chat/MessageItem.tsx | 81 +++++++ src/views/home/group/posts/ModalCreate.tsx | 2 - src/views/home/group/posts/ModalUpdate.tsx | 5 +- src/views/home/group/posts/PostItem.tsx | 3 - src/views/home/group/posts/Posts.tsx | 4 +- src/views/home/groups/Filter.tsx | 8 +- src/views/home/groups/GroupItem.tsx | 1 - src/views/home/groups/ModalInvite.tsx | 5 +- src/views/home/missions/Filter.tsx | 8 +- src/views/home/rightpanel/Articles.tsx | 6 +- src/views/home/rightpanel/Missions.tsx | 6 +- src/views/home/rightpanel/group/Group.tsx | 6 +- .../home/rightpanel/group/ModalLeave.tsx | 3 - .../home/rightpanel/group/ModalUpdate.tsx | 5 - src/views/mission/statement/Statement.tsx | 4 +- tsconfig.app.tsbuildinfo | 1 - 28 files changed, 414 insertions(+), 217 deletions(-) create mode 100644 src/assets/icons/input/send.svg create mode 100644 src/views/home/group/chat/MessageItem.tsx delete mode 100644 tsconfig.app.tsbuildinfo diff --git a/src/assets/icons/input/index.ts b/src/assets/icons/input/index.ts index 08cfb70..57e404c 100644 --- a/src/assets/icons/input/index.ts +++ b/src/assets/icons/input/index.ts @@ -5,6 +5,7 @@ import upload from './upload.svg'; import chevroneDropDownList from './chevron-drop-down.svg'; import checkMark from './check-mark.svg'; import Edit from './edit.svg'; +import Send from './send.svg'; export { Edit, @@ -14,4 +15,5 @@ export { upload, chevroneDropDownList, checkMark, + Send, }; diff --git a/src/assets/icons/input/send.svg b/src/assets/icons/input/send.svg new file mode 100644 index 0000000..fa89813 --- /dev/null +++ b/src/assets/icons/input/send.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/drop-down-list/DropDownList.tsx b/src/components/drop-down-list/DropDownList.tsx index d088bfb..ba4b5fc 100644 --- a/src/components/drop-down-list/DropDownList.tsx +++ b/src/components/drop-down-list/DropDownList.tsx @@ -44,7 +44,6 @@ export const DropDownList: React.FC = ({ setValue(defaultState != undefined ? defaultState : items[0]); }, [defaultState]); - console.log(defaultState, items); return (
{ })); setMissionIdInput(''); }) - .catch((err) => {}); + .catch((err) => { + err; + }); }; const removeMission = (removeId: number) => { diff --git a/src/redux/slices/groupChat.ts b/src/redux/slices/groupChat.ts index e97db39..9233a9d 100644 --- a/src/redux/slices/groupChat.ts +++ b/src/redux/slices/groupChat.ts @@ -19,7 +19,8 @@ export interface ChatMessage { interface FetchMessagesParams { groupId: number; limit?: number; - afterMessageId?: number | null; + afterMessageId?: number; + timeoutSeconds?: number; } interface SendMessageParams { @@ -33,8 +34,7 @@ interface SendMessageParams { interface GroupChatState { messages: Record; // по группам - hasMore: Record; // есть ли ещё старые сообщения - isInitialLoaded: Record; // загружена ли первая порция + lastMessage: Record; fetchMessages: { status: Status; @@ -48,8 +48,7 @@ interface GroupChatState { const initialState: GroupChatState = { messages: {}, - hasMore: {}, - isInitialLoaded: {}, + lastMessage: {}, fetchMessages: { status: 'idle', error: undefined, @@ -73,13 +72,13 @@ export const fetchGroupMessages = createAsyncThunk( params: { limit: params.limit, afterMessageId: params.afterMessageId, + timeoutSeconds: params.timeoutSeconds, }, }); return { groupId: params.groupId, messages: response.data as ChatMessage[], - afterMessageId: params.afterMessageId, }; } catch (err: any) { return rejectWithValue( @@ -117,8 +116,18 @@ const groupChatSlice = createSlice({ reducers: { clearChat(state, action: PayloadAction) { delete state.messages[action.payload]; - delete state.hasMore[action.payload]; - delete state.isInitialLoaded[action.payload]; + }, + setGroupChatStatus: ( + state, + action: PayloadAction<{ + key: keyof GroupChatState; + status: Status; + }>, + ) => { + const { key, status } = action.payload; + if (state[key]) { + (state[key] as any).status = status; + } }, }, extraReducers: (builder) => { @@ -134,28 +143,22 @@ const groupChatSlice = createSlice({ action: PayloadAction<{ groupId: number; messages: ChatMessage[]; - afterMessageId?: number | null; }>, ) => { - const { groupId, messages, afterMessageId } = action.payload; + const { groupId, messages } = action.payload; const existing = state.messages[groupId] || []; - // первичная загрузка - if (!afterMessageId) { - state.messages[groupId] = messages; - state.isInitialLoaded[groupId] = true; - state.hasMore[groupId] = messages.length > 0; - } - // догружаем старые (scroll up) - else if (afterMessageId) { - const ids = new Set(existing.map((m) => m.id)); - const filtered = messages.filter((m) => !ids.has(m.id)); - console.log('messages', messages); - console.log('filtered', filtered); - console.log('ids', ids); - console.log('existing', existing); - state.messages[groupId] = [...existing, ...filtered]; - state.hasMore[groupId] = filtered.length > 0; + const ids = new Set(existing.map((m) => m.id)); + const filtered = messages.filter((m) => !ids.has(m.id)); + + state.messages[groupId] = [...existing, ...filtered].sort( + (a, b) => a.id - b.id, + ); + if (state.messages[groupId].length) { + state.lastMessage[groupId] = + state.messages[groupId][ + state.messages[groupId].length - 1 + ].id; } state.fetchMessages.status = 'successful'; @@ -190,5 +193,5 @@ const groupChatSlice = createSlice({ }, }); -export const { clearChat } = groupChatSlice.actions; +export const { clearChat, setGroupChatStatus } = groupChatSlice.actions; export const groupChatReducer = groupChatSlice.reducer; diff --git a/src/views/articleeditor/Editor.tsx b/src/views/articleeditor/Editor.tsx index 8fd18ad..f109cef 100644 --- a/src/views/articleeditor/Editor.tsx +++ b/src/views/articleeditor/Editor.tsx @@ -68,14 +68,14 @@ function greet(user: User) { return \`Привет, \${user.name}! 👋 Роль: \${user.role}\`; } -console.log(greet({ name: "Ты", role: "Разработчик" })); +consol.log(greet({ name: "Ты", role: "Разработчик" })); \`\`\` Пример **JavaScript**: \`\`\`js const sum = (a, b) => a + b; -console.log(sum(2, 3)); // 5 +consol.log(sum(2, 3)); // 5 \`\`\` Пример **Python**: @@ -256,9 +256,7 @@ const MarkdownEditor: FC = ({ markdown.slice(cursorPos); setMarkdown(newText); - } catch (err) { - console.error('Ошибка загрузки изображения:', err); - } + } catch (err) {} } } }; diff --git a/src/views/home/articles/Filter.tsx b/src/views/home/articles/Filter.tsx index 1d8a82f..d56a78b 100644 --- a/src/views/home/articles/Filter.tsx +++ b/src/views/home/articles/Filter.tsx @@ -36,13 +36,17 @@ const Filters = () => { text: 'ID', }, ]} - onChange={(v) => {}} + onChange={(v) => { + v; + }} /> {}} + onChange={(values) => { + values; + }} />
); diff --git a/src/views/home/contest/Missions.tsx b/src/views/home/contest/Missions.tsx index 1893df9..f4c90d6 100644 --- a/src/views/home/contest/Missions.tsx +++ b/src/views/home/contest/Missions.tsx @@ -1,124 +1,126 @@ -import { FC, useEffect } from "react"; -import MissionItem from "./MissionItem"; +import { FC, useEffect } from 'react'; +import MissionItem from './MissionItem'; import { - Contest, - fetchMySubmissions, - setContestStatus, -} from "../../../redux/slices/contests"; -import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; -import { PrimaryButton } from "../../../components/button/PrimaryButton"; -import { useNavigate } from "react-router-dom"; -import { arrowLeft } from "../../../assets/icons/header"; + Contest, + fetchMySubmissions, + setContestStatus, +} from '../../../redux/slices/contests'; +import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; +import { PrimaryButton } from '../../../components/button/PrimaryButton'; +import { useNavigate } from 'react-router-dom'; +import { arrowLeft } from '../../../assets/icons/header'; export interface Article { - id: number; - name: string; - tags: string[]; + id: number; + name: string; + tags: string[]; } interface ContestMissionsProps { - contest?: Contest; + contest?: Contest; } const ContestMissions: FC = ({ contest }) => { - const navigate = useNavigate(); - const dispatch = useAppDispatch(); - const { submissions, status } = useAppSelector( - (state) => state.contests.fetchMySubmissions - ); + const navigate = useNavigate(); + const dispatch = useAppDispatch(); + const { submissions, status } = useAppSelector( + (state) => state.contests.fetchMySubmissions, + ); - useEffect(() => { - if (contest) dispatch(fetchMySubmissions(contest.id)); - }, [contest]); + useEffect(() => { + if (contest) dispatch(fetchMySubmissions(contest.id)); + }, [contest]); - useEffect(() => { - if (status == "successful") { - dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" })); + useEffect(() => { + if (status == 'successful') { + dispatch( + setContestStatus({ key: 'fetchMySubmissions', status: 'idle' }), + ); + } + }, [status]); + + if (!contest) { + return <>; } - }, [status]); - if (!contest) { - return <>; - } + const solvedCount = (contest.missions ?? []).filter((mission) => + submissions.some( + (s) => + s.solution.missionId === mission.id && + s.solution.status === 'Accepted: All tests passed', + ), + ).length; - const solvedCount = (contest.missions ?? []).filter((mission) => - submissions.some( - (s) => - s.solution.missionId === mission.id && - s.solution.status === "Accepted: All tests passed" - ) - ).length; + const totalCount = contest.missions?.length ?? 0; - const totalCount = contest.missions?.length ?? 0; + return ( +
+
+
+ {contest.name} +
+
+
+ { + navigate(`/home/contests`); + }} + /> + + Контест #{contest.id} + +
+
{contest.attemptDurationMinutes ?? 0} минут
+
+
+
+
{`${solvedCount}/${totalCount} Решено`}
+ { + navigate(`/contest/${contest.id}/submissions`); + }} + text="Мои посылки" + /> +
- return ( -
-
-
- {contest.name} +
+
+ {(contest.missions ?? []).map((v, i) => { + const missionSubmissions = submissions.filter( + (s) => s.solution.missionId === v.id, + ); + + const hasSuccess = missionSubmissions.some( + (s) => + s.solution.status == + 'Accepted: All tests passed', + ); + + const status = hasSuccess + ? 'success' + : missionSubmissions.length > 0 + ? 'error' + : undefined; + + return ( + + ); + })} +
+
-
-
- { - navigate(`/home/contests`); - }} - /> - - Контест #{contest.id} - -
-
{contest.attemptDurationMinutes ?? 0} минут
-
-
-
-
{`${solvedCount}/${totalCount} Решено`}
- { - navigate(`/contest/${contest.id}/submissions`); - }} - text="Мои посылки" - /> -
- -
-
- {(contest.missions ?? []).map((v, i) => { - const missionSubmissions = submissions.filter( - (s) => s.solution.missionId === v.id - ); - - const hasSuccess = missionSubmissions.some( - (s) => s.solution.status == "Accepted: All tests passed" - ); - - console.log(missionSubmissions); - - const status = hasSuccess - ? "success" - : missionSubmissions.length > 0 - ? "error" - : undefined; - - return ( - - ); - })} -
-
-
- ); + ); }; export default ContestMissions; diff --git a/src/views/home/contests/Filter.tsx b/src/views/home/contests/Filter.tsx index ca01a9d..d56a78b 100644 --- a/src/views/home/contests/Filter.tsx +++ b/src/views/home/contests/Filter.tsx @@ -36,13 +36,17 @@ const Filters = () => { text: 'ID', }, ]} - onChange={(v) => console.log(v)} + onChange={(v) => { + v; + }} /> console.log(values)} + onChange={(values) => { + values; + }} />
); diff --git a/src/views/home/group/Group.tsx b/src/views/home/group/Group.tsx index 1642452..a982ba2 100644 --- a/src/views/home/group/Group.tsx +++ b/src/views/home/group/Group.tsx @@ -5,7 +5,6 @@ import { useAppDispatch, useAppSelector } from '../../../redux/hooks'; import { fetchGroupById } from '../../../redux/slices/groups'; import GroupMenu from './GroupMenu'; import { Posts } from './posts/Posts'; -import { SearchInput } from '../../../components/input/SearchInput'; import { Chat } from './chat/Chat'; import { Contests } from './contests/Contests'; diff --git a/src/views/home/group/chat/Chat.tsx b/src/views/home/group/chat/Chat.tsx index dc7bd71..0b9c939 100644 --- a/src/views/home/group/chat/Chat.tsx +++ b/src/views/home/group/chat/Chat.tsx @@ -1,8 +1,14 @@ -import { FC, useEffect, useRef } from 'react'; +import { FC, useEffect, useRef, useState } from 'react'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { setMenuActiveGroupPage } from '../../../../redux/slices/store'; -import { fetchGroupMessages } from '../../../../redux/slices/groupChat'; +import { + fetchGroupMessages, + sendGroupMessage, + setGroupChatStatus, +} from '../../../../redux/slices/groupChat'; import { SearchInput } from '../../../../components/input/SearchInput'; +import { MessageItem } from './MessageItem'; +import { Send } from '../../../../assets/icons/input'; interface GroupChatProps { groupId: number; @@ -13,22 +19,27 @@ const CHUNK_SIZE = 10; export const Chat: FC = ({ groupId }) => { const dispatch = useAppDispatch(); const messages = useAppSelector((s) => s.groupchat.messages[groupId] || []); - const hasMore = useAppSelector((s) => s.groupchat.hasMore[groupId]); - const isInitialLoaded = useAppSelector( - (s) => s.groupchat.isInitialLoaded[groupId], + const messagesState = useAppSelector( + (state) => state.groupchat.fetchMessages.status, ); + const lastMessageId = useAppSelector( + (state) => state.groupchat.lastMessage[groupId] || 0, + ); + const user = useAppSelector((state) => state.auth); + + const [text, setText] = useState(''); + const [firstMessagesFetch, setFirctMessagesFetch] = useState(true); const scrollRef = useRef(null); + // добавлено: ref для хранения предыдущей высоты + const prevHeightRef = useRef(0); + // активируем таб useEffect(() => { dispatch(setMenuActiveGroupPage('chat')); }, []); - useEffect(() => { - console.log(messages); - }, [messages]); - // первичная загрузка useEffect(() => { dispatch( @@ -39,23 +50,112 @@ export const Chat: FC = ({ groupId }) => { ); }, [groupId]); - // автоскролл вниз после начальной загрузки + // автоскролл вниз после начальной загрузки (но не при догрузке) useEffect(() => { - if (!isInitialLoaded || !scrollRef.current) return; - scrollRef.current.scrollTop = scrollRef.current.scrollHeight; - }, [isInitialLoaded, messages.length]); + const div = scrollRef.current; + if (!div) return; + + // если prevHeightRef == 0 — значит это не догрузка, а обычная загрузка + if (prevHeightRef.current === 0) { + div.scrollTop = div.scrollHeight; + } + }, [messages.length]); + + // добавлено: компенсирование скролла при догрузке + useEffect(() => { + const div = scrollRef.current; + if (!div) return; + + if (prevHeightRef.current > 0) { + const diff = div.scrollHeight - prevHeightRef.current; + div.scrollTop = diff; // компенсируем смещение + + prevHeightRef.current = 0; // сбрасываем + } + }, [messages]); + + useEffect(() => { + if (messagesState == 'successful') { + dispatch( + setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }), + ); + } + if (messagesState == 'failed') { + dispatch( + setGroupChatStatus({ key: 'fetchMessages', status: 'idle' }), + ); + } + }, [messagesState]); + + const lastMessageIdRef = useRef(null); + + useEffect(() => { + lastMessageIdRef.current = lastMessageId; + if (firstMessagesFetch) { + setFirctMessagesFetch(false); + dispatch( + fetchGroupMessages({ + groupId, + afterMessageId: lastMessageIdRef.current, + timeoutSeconds: 10, + }), + ); + } + }, [messages]); + + useEffect(() => { + const interval = setInterval(() => { + if (lastMessageIdRef.current === null) return; + + dispatch( + fetchGroupMessages({ + groupId, + afterMessageId: lastMessageIdRef.current, + timeoutSeconds: 10, + }), + ); + }, 10000); + + return () => clearInterval(interval); + }, [groupId]); + + const handleSend = () => { + if (!text.trim()) return; + + dispatch( + sendGroupMessage({ + groupId, + content: text.trim(), + }), + ).then(() => { + setText(''); + setTimeout(() => { + const div = scrollRef.current; + if (div) div.scrollTop = div.scrollHeight; + }, 0); + }); + }; + + const handleEnter = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSend(); + } + }; // догрузка старых сообщений при скролле вверх const handleScroll = () => { const div = scrollRef.current; - if (!div || !hasMore) return; + if (!div) return; + + // если скролл в верхней точке + if (div.scrollTop === 0) { + prevHeightRef.current = div.scrollHeight; // запоминаем высоту до загрузки - if (div.scrollTop < 100) { const first = messages[0]; - if (!first) return; + if (!first || first.id == 1) return; const beforeId = first.id - CHUNK_SIZE; - console.log(beforeId); dispatch( fetchGroupMessages({ @@ -69,7 +169,7 @@ export const Chat: FC = ({ groupId }) => { return (
-
+
= ({ groupId }) => { ref={scrollRef} onScroll={handleScroll} > -
- {messages.map((msg) => ( -
-
- {msg.authorUsername} {msg.id} -
-
{msg.content}
-
- {new Date(msg.createdAt).toLocaleString()} -
-
+
+ {messages.map((msg, i) => ( + ))}
-
footer / input bar
+
); diff --git a/src/views/home/group/chat/MessageItem.tsx b/src/views/home/group/chat/MessageItem.tsx new file mode 100644 index 0000000..d6932e5 --- /dev/null +++ b/src/views/home/group/chat/MessageItem.tsx @@ -0,0 +1,81 @@ +import { FC } from 'react'; +import { useAppSelector } from '../../../../redux/hooks'; + +function convertDate(isoString: string) { + const date = new Date(isoString); + + const dd = String(date.getUTCDate()).padStart(2, '0'); + const mm = String(date.getUTCMonth() + 1).padStart(2, '0'); + const yyyy = date.getUTCFullYear(); + + const hh = String(date.getUTCHours()).padStart(2, '0'); + const min = String(date.getUTCMinutes()).padStart(2, '0'); + + return `${dd}.${mm}.${yyyy} ${hh}:${min}`; +} + +interface MessageItemProps { + id: number; + groupId: number; + authorId: number; + authorUsername: string; + createdAt: string; + message: string; + myMessage: boolean; +} + +export const MessageItem: FC = ({ + authorId, + authorUsername, + createdAt, + message, + myMessage, +}) => { + const members = useAppSelector( + (state) => state.groups.fetchGroupById.group?.members, + ); + const member = members?.find((m) => m.userId === authorId); + + return myMessage ? ( +
+
+
+
+ {convertDate(createdAt)} +
+
+ +
+
+ {message} +
+
+
+
+ ) : ( +
+
+
+
+
+
{authorUsername}
+
+ {member ? member.role : 'роль не найдена'} +
+
+
+
+ {convertDate(createdAt)} +
+
+
+ +
+
+ {message} +
+
+
+
+ ); +}; diff --git a/src/views/home/group/posts/ModalCreate.tsx b/src/views/home/group/posts/ModalCreate.tsx index 39882ae..859e5b5 100644 --- a/src/views/home/group/posts/ModalCreate.tsx +++ b/src/views/home/group/posts/ModalCreate.tsx @@ -2,9 +2,7 @@ import { FC, useEffect, useState } from 'react'; import { Modal } from '../../../../components/modal/Modal'; import { PrimaryButton } from '../../../../components/button/PrimaryButton'; import { SecondaryButton } from '../../../../components/button/SecondaryButton'; -import { Input } from '../../../../components/input/Input'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; -import { createGroup } from '../../../../redux/slices/groups'; import MarkdownEditor from '../../../articleeditor/Editor'; import { createPost, diff --git a/src/views/home/group/posts/ModalUpdate.tsx b/src/views/home/group/posts/ModalUpdate.tsx index 55ada02..24bafad 100644 --- a/src/views/home/group/posts/ModalUpdate.tsx +++ b/src/views/home/group/posts/ModalUpdate.tsx @@ -2,12 +2,9 @@ import { FC, useEffect, useState } from 'react'; import { Modal } from '../../../../components/modal/Modal'; import { PrimaryButton } from '../../../../components/button/PrimaryButton'; import { SecondaryButton } from '../../../../components/button/SecondaryButton'; -import { Input } from '../../../../components/input/Input'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; -import { createGroup } from '../../../../redux/slices/groups'; import MarkdownEditor, { MarkDownPattern } from '../../../articleeditor/Editor'; import { - createPost, deletePost, fetchPostById, setGroupFeedStatus, @@ -55,7 +52,7 @@ const ModalUpdate: FC = ({ }, [statusDelete]); useEffect(() => { - dispatch(fetchPostById({ groupId, postId })); + if (postId) dispatch(fetchPostById({ groupId, postId })); }, [postId]); return ( diff --git a/src/views/home/group/posts/PostItem.tsx b/src/views/home/group/posts/PostItem.tsx index 98e34d9..3042524 100644 --- a/src/views/home/group/posts/PostItem.tsx +++ b/src/views/home/group/posts/PostItem.tsx @@ -32,13 +32,10 @@ interface PostItemProps { export const PostItem: FC = ({ id, - groupId, authorId, authorUsername, - name, content, createdAt, - updatedAt, isAdmin, setModalUpdateActive, setUpdatePostId, diff --git a/src/views/home/group/posts/Posts.tsx b/src/views/home/group/posts/Posts.tsx index e5cc012..0bee7e0 100644 --- a/src/views/home/group/posts/Posts.tsx +++ b/src/views/home/group/posts/Posts.tsx @@ -58,7 +58,9 @@ export const Posts: FC = ({ groupId }) => {
{}} + onChange={(v) => { + v; + }} placeholder="Поиск сообщений" /> {isAdmin && ( diff --git a/src/views/home/groups/Filter.tsx b/src/views/home/groups/Filter.tsx index 1d8a82f..d56a78b 100644 --- a/src/views/home/groups/Filter.tsx +++ b/src/views/home/groups/Filter.tsx @@ -36,13 +36,17 @@ const Filters = () => { text: 'ID', }, ]} - onChange={(v) => {}} + onChange={(v) => { + v; + }} /> {}} + onChange={(values) => { + values; + }} />
); diff --git a/src/views/home/groups/GroupItem.tsx b/src/views/home/groups/GroupItem.tsx index 4a979b0..a0a0d0b 100644 --- a/src/views/home/groups/GroupItem.tsx +++ b/src/views/home/groups/GroupItem.tsx @@ -44,7 +44,6 @@ const GroupItem: React.FC = ({ id, name, visible, - role, description, setUpdateGroup, setUpdateActive, diff --git a/src/views/home/groups/ModalInvite.tsx b/src/views/home/groups/ModalInvite.tsx index 7983671..f4fc8e3 100644 --- a/src/views/home/groups/ModalInvite.tsx +++ b/src/views/home/groups/ModalInvite.tsx @@ -4,7 +4,6 @@ import { fetchGroupJoinLink } from '../../../redux/slices/groups'; import { Modal } from '../../../components/modal/Modal'; import { PrimaryButton } from '../../../components/button/PrimaryButton'; import { SecondaryButton } from '../../../components/button/SecondaryButton'; -import { Input } from '../../../components/input/Input'; import { toastSuccess } from '../../../lib/toastNotification'; interface ModalInviteProps { @@ -54,9 +53,7 @@ const ModalInvite: FC = ({ await navigator.clipboard.writeText(inviteLink); toastSuccess('Приглашение скопировано в буфер обмена!'); setActive(false); - } catch (err) { - console.error('Не удалось скопировать ссылку:', err); - } + } catch (err) {} }; return ( diff --git a/src/views/home/missions/Filter.tsx b/src/views/home/missions/Filter.tsx index ca01a9d..d56a78b 100644 --- a/src/views/home/missions/Filter.tsx +++ b/src/views/home/missions/Filter.tsx @@ -36,13 +36,17 @@ const Filters = () => { text: 'ID', }, ]} - onChange={(v) => console.log(v)} + onChange={(v) => { + v; + }} /> console.log(values)} + onChange={(values) => { + values; + }} />
); diff --git a/src/views/home/rightpanel/Articles.tsx b/src/views/home/rightpanel/Articles.tsx index 48a9511..5c2827e 100644 --- a/src/views/home/rightpanel/Articles.tsx +++ b/src/views/home/rightpanel/Articles.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, Fragment } from 'react'; export const ArticlesRightPanel: FC = () => { const items = [ @@ -23,7 +23,7 @@ export const ArticlesRightPanel: FC = () => { {items.map((v, i) => { return ( - <> + {
{v.name} @@ -32,7 +32,7 @@ export const ArticlesRightPanel: FC = () => { {i + 1 != items.length && (
)} - + ); })}
diff --git a/src/views/home/rightpanel/Missions.tsx b/src/views/home/rightpanel/Missions.tsx index 2d2abe8..fc94021 100644 --- a/src/views/home/rightpanel/Missions.tsx +++ b/src/views/home/rightpanel/Missions.tsx @@ -1,4 +1,4 @@ -import { FC } from 'react'; +import { FC, Fragment } from 'react'; import { cn } from '../../../lib/cn'; export const MissionsRightPanel: FC = () => { @@ -32,7 +32,7 @@ export const MissionsRightPanel: FC = () => { {items.map((v, i) => { return ( - <> + {
{v.name}
@@ -60,7 +60,7 @@ export const MissionsRightPanel: FC = () => { {i + 1 != items.length && (
)} - + ); })}
diff --git a/src/views/home/rightpanel/group/Group.tsx b/src/views/home/rightpanel/group/Group.tsx index f1f9d7f..5c86644 100644 --- a/src/views/home/rightpanel/group/Group.tsx +++ b/src/views/home/rightpanel/group/Group.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react'; +import { FC, Fragment, useEffect, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { fetchGroupById, GroupMember } from '../../../../redux/slices/groups'; @@ -54,7 +54,7 @@ export const GroupRightPanel: FC = () => { {group?.members.map((v, i) => { return ( - <> + {
@@ -101,7 +101,7 @@ export const GroupRightPanel: FC = () => { {i + 1 != group?.members.length && (
)} - + ); })} diff --git a/src/views/home/rightpanel/group/ModalLeave.tsx b/src/views/home/rightpanel/group/ModalLeave.tsx index 5427b2f..c9339c3 100644 --- a/src/views/home/rightpanel/group/ModalLeave.tsx +++ b/src/views/home/rightpanel/group/ModalLeave.tsx @@ -2,13 +2,10 @@ import { FC, useEffect, useState } from 'react'; import { Modal } from '../../../../components/modal/Modal'; import { PrimaryButton } from '../../../../components/button/PrimaryButton'; import { SecondaryButton } from '../../../../components/button/SecondaryButton'; -import { Input } from '../../../../components/input/Input'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { - deleteGroup, removeGroupMember, setGroupsStatus, - updateGroup, } from '../../../../redux/slices/groups'; import ConfirmModal from '../../../../components/modal/ConfirmModal'; import { useNavigate } from 'react-router-dom'; diff --git a/src/views/home/rightpanel/group/ModalUpdate.tsx b/src/views/home/rightpanel/group/ModalUpdate.tsx index 3062cd2..c4223e9 100644 --- a/src/views/home/rightpanel/group/ModalUpdate.tsx +++ b/src/views/home/rightpanel/group/ModalUpdate.tsx @@ -2,16 +2,13 @@ import { FC, useEffect, useState } from 'react'; import { Modal } from '../../../../components/modal/Modal'; import { PrimaryButton } from '../../../../components/button/PrimaryButton'; import { SecondaryButton } from '../../../../components/button/SecondaryButton'; -import { Input } from '../../../../components/input/Input'; import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { addGroupMember, - deleteGroup, fetchGroupById, GroupMember, removeGroupMember, setGroupsStatus, - updateGroup, } from '../../../../redux/slices/groups'; import ConfirmModal from '../../../../components/modal/ConfirmModal'; import { DropDownList } from '../../../../components/drop-down-list/DropDownList'; @@ -31,7 +28,6 @@ const ModalUpdate: FC = ({ active, setActive, groupId, - userId, user, adminUser, groupName, @@ -76,7 +72,6 @@ const ModalUpdate: FC = ({ }, [statusUpdate]); useEffect(() => { - console.log(user); if (user) { setUserRole( user?.role.includes('Creator') ? 'Creator' : user?.role, diff --git a/src/views/mission/statement/Statement.tsx b/src/views/mission/statement/Statement.tsx index e313c62..608db35 100644 --- a/src/views/mission/statement/Statement.tsx +++ b/src/views/mission/statement/Statement.tsx @@ -17,9 +17,7 @@ const CopyableDiv: FC = ({ content }) => { try { await navigator.clipboard.writeText(content); alert('Скопировано!'); - } catch (err) { - console.error('Ошибка копирования:', err); - } + } catch (err) {} }; return ( diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo deleted file mode 100644 index 22d984f..0000000 --- a/tsconfig.app.tsbuildinfo +++ /dev/null @@ -1 +0,0 @@ -{"root":["./src/app.tsx","./src/axios.ts","./src/main.tsx","./src/vite-env.d.ts","./src/assets/icons/account/index.ts","./src/assets/icons/auth/index.ts","./src/assets/icons/filters/index.ts","./src/assets/icons/group/index.ts","./src/assets/icons/groups/index.ts","./src/assets/icons/header/index.ts","./src/assets/icons/input/index.ts","./src/assets/icons/menu/index.ts","./src/assets/icons/missions/index.ts","./src/assets/logos/index.ts","./src/components/button/primarybutton.tsx","./src/components/button/reversebutton.tsx","./src/components/button/secondarybutton.tsx","./src/components/checkbox/checkbox.tsx","./src/components/drop-down-list/dropdownlist.tsx","./src/components/drop-down-list/filter.tsx","./src/components/drop-down-list/sorter.tsx","./src/components/input/daterangeinput.tsx","./src/components/input/input.tsx","./src/components/input/searchinput.tsx","./src/components/modal/confirmmodal.tsx","./src/components/modal/modal.tsx","./src/components/router/protectedroute.tsx","./src/components/switch/switch.tsx","./src/config/colors.ts","./src/hooks/useclickoutside.ts","./src/hooks/usequery.ts","./src/lib/cn.ts","./src/lib/toastnotification.ts","./src/pages/article.tsx","./src/pages/articleeditor.tsx","./src/pages/contesteditor.tsx","./src/pages/home.tsx","./src/pages/mission.tsx","./src/redux/hooks.ts","./src/redux/store.ts","./src/redux/slices/articles.ts","./src/redux/slices/auth.ts","./src/redux/slices/contests.ts","./src/redux/slices/groupfeed.ts","./src/redux/slices/groups.ts","./src/redux/slices/missions.ts","./src/redux/slices/store.ts","./src/redux/slices/submit.ts","./src/views/article/header.tsx","./src/views/articleeditor/editor.tsx","./src/views/articleeditor/header.tsx","./src/views/articleeditor/marckdownpreview.tsx","./src/views/home/account/account.tsx","./src/views/home/account/accoutmenu.tsx","./src/views/home/account/rightpanel.tsx","./src/views/home/account/articles/articlesblock.tsx","./src/views/home/account/contests/contests.tsx","./src/views/home/account/contests/contestsblock.tsx","./src/views/home/account/contests/mycontestitem.tsx","./src/views/home/account/contests/registercontestitem.tsx","./src/views/home/account/missions/missions.tsx","./src/views/home/account/missions/missionsblock.tsx","./src/views/home/account/missions/mymissionitem.tsx","./src/views/home/articles/articleitem.tsx","./src/views/home/articles/articles.tsx","./src/views/home/articles/filter.tsx","./src/views/home/auth/login.tsx","./src/views/home/auth/register.tsx","./src/views/home/contest/contest.tsx","./src/views/home/contest/missionitem.tsx","./src/views/home/contest/missions.tsx","./src/views/home/contest/submissionitem.tsx","./src/views/home/contest/submissions.tsx","./src/views/home/contests/contestitem.tsx","./src/views/home/contests/contests.tsx","./src/views/home/contests/contestsblock.tsx","./src/views/home/contests/filter.tsx","./src/views/home/contests/modalcreate.tsx","./src/views/home/group/group.tsx","./src/views/home/group/groupmenu.tsx","./src/views/home/group/chat/chat.tsx","./src/views/home/group/contests/contests.tsx","./src/views/home/group/posts/modalcreate.tsx","./src/views/home/group/posts/modalupdate.tsx","./src/views/home/group/posts/postitem.tsx","./src/views/home/group/posts/posts.tsx","./src/views/home/groupinviter/groupinvite.tsx","./src/views/home/groups/filter.tsx","./src/views/home/groups/groupitem.tsx","./src/views/home/groups/groups.tsx","./src/views/home/groups/groupsblock.tsx","./src/views/home/groups/modalcreate.tsx","./src/views/home/groups/modalinvite.tsx","./src/views/home/groups/modalupdate.tsx","./src/views/home/menu/menu.tsx","./src/views/home/menu/menuitem.tsx","./src/views/home/missions/filter.tsx","./src/views/home/missions/missionitem.tsx","./src/views/home/missions/missions.tsx","./src/views/home/missions/modalcreate.tsx","./src/views/home/rightpanel/articles.tsx","./src/views/home/rightpanel/missions.tsx","./src/views/home/rightpanel/group/group.tsx","./src/views/home/rightpanel/group/modalleave.tsx","./src/views/home/rightpanel/group/modalupdate.tsx","./src/views/mission/codeeditor/codeeditor.tsx","./src/views/mission/statement/header.tsx","./src/views/mission/statement/latextcontainer.tsx","./src/views/mission/statement/missionsubmissions.tsx","./src/views/mission/statement/statement.tsx","./src/views/mission/statement/submissionitem.tsx","./src/views/mission/submission/submission.tsx"],"errors":true,"version":"5.6.2"} \ No newline at end of file