group chat init

This commit is contained in:
Виталий Лавшонок
2025-11-21 22:10:26 +03:00
parent e904297bb9
commit 304c734169
5 changed files with 219 additions and 7 deletions

View File

@@ -0,0 +1,190 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from '../../axios';
// =========================================
// Типы
// =========================================
export type Status = 'idle' | 'loading' | 'successful' | 'failed';
export interface ChatMessage {
id: number;
groupId: number;
authorId: number;
authorUsername: string;
content: string;
createdAt: string;
}
interface FetchMessagesParams {
groupId: number;
limit?: number;
afterMessageId?: number | null;
afterCreatedAt?: string | null;
timeoutSeconds?: number;
}
interface SendMessageParams {
groupId: number;
content: string;
}
// =========================================
// State
// =========================================
interface GroupChatState {
messages: Record<number, ChatMessage[]>; // messages[groupId][]
fetchMessages: {
status: Status;
error?: string;
};
sendMessage: {
status: Status;
error?: string;
};
}
const initialState: GroupChatState = {
messages: {},
fetchMessages: {
status: 'idle',
error: undefined,
},
sendMessage: {
status: 'idle',
error: undefined,
},
};
// =========================================
// Thunks
// =========================================
// Получить сообщения группы (обычный запрос или long polling)
export const fetchGroupMessages = createAsyncThunk(
'groupChat/fetchGroupMessages',
async (params: FetchMessagesParams, { rejectWithValue }) => {
try {
const response = await axios.get(`/groups/${params.groupId}/chat`, {
params: {
limit: params.limit,
afterMessageId: params.afterMessageId,
afterCreatedAt: params.afterCreatedAt,
timeoutSeconds: params.timeoutSeconds,
},
});
return {
groupId: params.groupId,
messages: response.data as ChatMessage[],
};
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message ||
'Ошибка при получении сообщений группы',
);
}
},
);
// Отправить новое сообщение
export const sendGroupMessage = createAsyncThunk(
'groupChat/sendGroupMessage',
async ({ groupId, content }: SendMessageParams, { rejectWithValue }) => {
try {
const response = await axios.post(`/groups/${groupId}/chat`, {
content,
});
return response.data as ChatMessage;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка при отправке сообщения',
);
}
},
);
// =========================================
// Slice
// =========================================
const groupChatSlice = createSlice({
name: 'groupChat',
initialState,
reducers: {
clearChat(state, action: PayloadAction<number>) {
delete state.messages[action.payload];
},
},
extraReducers: (builder) => {
// fetchGroupMessages
builder.addCase(fetchGroupMessages.pending, (state) => {
state.fetchMessages.status = 'loading';
});
builder.addCase(
fetchGroupMessages.fulfilled,
(
state,
action: PayloadAction<{
groupId: number;
messages: ChatMessage[];
}>,
) => {
state.fetchMessages.status = 'successful';
const { groupId, messages } = action.payload;
if (!state.messages[groupId]) state.messages[groupId] = [];
// Если пришли последние сообщения — заменяем
// Если long polling — дополняем
if (messages.length > 0) {
const existing = state.messages[groupId];
// Фильтруем дубликаты
const ids = new Set(existing.map((m) => m.id));
const newMessages = messages.filter((m) => !ids.has(m.id));
state.messages[groupId] = [...existing, ...newMessages];
}
},
);
builder.addCase(fetchGroupMessages.rejected, (state, action: any) => {
state.fetchMessages.status = 'failed';
state.fetchMessages.error = action.payload;
});
// sendMessage
builder.addCase(sendGroupMessage.pending, (state) => {
state.sendMessage.status = 'loading';
});
builder.addCase(
sendGroupMessage.fulfilled,
(state, action: PayloadAction<ChatMessage>) => {
state.sendMessage.status = 'successful';
const msg = action.payload;
if (!state.messages[msg.groupId]) {
state.messages[msg.groupId] = [];
}
state.messages[msg.groupId].push(msg);
},
);
builder.addCase(sendGroupMessage.rejected, (state, action: any) => {
state.sendMessage.status = 'failed';
state.sendMessage.error = action.payload;
});
},
});
export const { clearChat } = groupChatSlice.actions;
export const groupChatReducer = groupChatSlice.reducer;

View File

@@ -7,6 +7,7 @@ import { contestsReducer } from './slices/contests';
import { groupsReducer } from './slices/groups';
import { articlesReducer } from './slices/articles';
import { groupFeedReducer } from './slices/groupfeed';
import { groupChatReducer } from './slices/groupChat';
// использование
// import { useAppDispatch, useAppSelector } from '../redux/hooks';
@@ -27,6 +28,7 @@ export const store = configureStore({
groups: groupsReducer,
articles: articlesReducer,
groupfeed: groupFeedReducer,
groupchat: groupChatReducer,
},
});