Files
LiquidCode_Frontend/src/redux/slices/groupfeed.ts
Виталий Лавшонок fd34761745 add contests
2025-12-05 23:42:18 +03:00

348 lines
9.9 KiB
TypeScript

import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from '../../axios';
// =====================
// Типы
// =====================
type Status = 'idle' | 'loading' | 'successful' | 'failed';
export interface Post {
id: number;
groupId: number;
authorId: number;
authorUsername: string;
name: string;
content: string;
createdAt: string;
updatedAt: string;
}
export interface PostsPage {
items: Post[];
hasNext: boolean;
}
// =====================
// Состояние
// =====================
interface PostsState {
fetchPosts: {
pages: Record<number, PostsPage>; // страница => данные
status: Status;
error?: string;
};
fetchPostById: {
post?: Post;
status: Status;
error?: string;
};
createPost: {
post?: Post;
status: Status;
error?: string;
};
updatePost: {
post?: Post;
status: Status;
error?: string;
};
deletePost: {
deletedId?: number;
status: Status;
error?: string;
};
}
const initialState: PostsState = {
fetchPosts: {
pages: {},
status: 'idle',
error: undefined,
},
fetchPostById: {
post: undefined,
status: 'idle',
error: undefined,
},
createPost: {
post: undefined,
status: 'idle',
error: undefined,
},
updatePost: {
post: undefined,
status: 'idle',
error: undefined,
},
deletePost: {
deletedId: undefined,
status: 'idle',
error: undefined,
},
};
// =====================
// Async Thunks
// =====================
// Получить посты группы (пагинация)
export const fetchGroupPosts = createAsyncThunk(
'posts/fetchGroupPosts',
async (
{
groupId,
page = 0,
pageSize = 100,
}: { groupId: number; page?: number; pageSize?: number },
{ rejectWithValue },
) => {
try {
const response = await axios.get(
`/groups/${groupId}/feed?page=${page}&pageSize=${pageSize}`,
);
return { page, data: response.data as PostsPage };
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка загрузки постов',
);
}
},
);
// Получить один пост
export const fetchPostById = createAsyncThunk(
'posts/fetchPostById',
async (
{ groupId, postId }: { groupId: number; postId: number },
{ rejectWithValue },
) => {
try {
const response = await axios.get(
`/groups/${groupId}/feed/${postId}`,
);
return response.data as Post;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка загрузки поста',
);
}
},
);
// Создать пост
export const createPost = createAsyncThunk(
'posts/createPost',
async (
{
groupId,
name,
content,
}: { groupId: number; name: string; content: string },
{ rejectWithValue },
) => {
try {
const response = await axios.post(`/groups/${groupId}/feed`, {
name,
content,
});
return response.data as Post;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка создания поста',
);
}
},
);
// Обновить пост
export const updatePost = createAsyncThunk(
'posts/updatePost',
async (
{
groupId,
postId,
name,
content,
}: {
groupId: number;
postId: number;
name: string;
content: string;
},
{ rejectWithValue },
) => {
try {
const response = await axios.put(
`/groups/${groupId}/feed/${postId}`,
{
name,
content,
},
);
return response.data as Post;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка обновления поста',
);
}
},
);
// Удалить пост
export const deletePost = createAsyncThunk(
'posts/deletePost',
async (
{ groupId, postId }: { groupId: number; postId: number },
{ rejectWithValue },
) => {
try {
await axios.delete(`/groups/${groupId}/feed/${postId}`);
return postId;
} catch (err: any) {
return rejectWithValue(
err.response?.data?.message || 'Ошибка удаления поста',
);
}
},
);
// =====================
// Slice
// =====================
const postsSlice = createSlice({
name: 'posts',
initialState,
reducers: {
setGroupFeedStatus: (
state,
action: PayloadAction<{ key: keyof PostsState; status: Status }>,
) => {
const { key, status } = action.payload;
if (state[key]) {
(state[key] as any).status = status;
}
},
},
extraReducers: (builder) => {
// fetchGroupPosts
builder.addCase(fetchGroupPosts.pending, (state) => {
state.fetchPosts.status = 'loading';
});
builder.addCase(
fetchGroupPosts.fulfilled,
(
state,
action: PayloadAction<{ page: number; data: PostsPage }>,
) => {
const { page, data } = action.payload;
state.fetchPosts.status = 'successful';
state.fetchPosts.pages[page] = data;
},
);
builder.addCase(fetchGroupPosts.rejected, (state, action: any) => {
state.fetchPosts.status = 'failed';
state.fetchPosts.error = action.payload;
});
// fetchPostById
builder.addCase(fetchPostById.pending, (state) => {
state.fetchPostById.status = 'loading';
});
builder.addCase(
fetchPostById.fulfilled,
(state, action: PayloadAction<Post>) => {
state.fetchPostById.status = 'successful';
state.fetchPostById.post = action.payload;
},
);
builder.addCase(fetchPostById.rejected, (state, action: any) => {
state.fetchPostById.status = 'failed';
state.fetchPostById.error = action.payload;
});
// createPost
builder.addCase(createPost.pending, (state) => {
state.createPost.status = 'loading';
});
builder.addCase(
createPost.fulfilled,
(state, action: PayloadAction<Post>) => {
state.createPost.status = 'successful';
state.createPost.post = action.payload;
// добавляем сразу в первую страницу (page = 0)
if (state.fetchPosts.pages[0]) {
state.fetchPosts.pages[0].items.unshift(action.payload);
}
},
);
builder.addCase(createPost.rejected, (state, action: any) => {
state.createPost.status = 'failed';
state.createPost.error = action.payload;
});
// updatePost
builder.addCase(updatePost.pending, (state) => {
state.updatePost.status = 'loading';
});
builder.addCase(
updatePost.fulfilled,
(state, action: PayloadAction<Post>) => {
state.updatePost.status = 'successful';
state.updatePost.post = action.payload;
// обновим в списках
for (const page of Object.values(state.fetchPosts.pages)) {
const index = page.items.findIndex(
(p) => p.id === action.payload.id,
);
if (index !== -1) page.items[index] = action.payload;
}
// обновим если открыт одиночный пост
if (state.fetchPostById.post?.id === action.payload.id) {
state.fetchPostById.post = action.payload;
}
},
);
builder.addCase(updatePost.rejected, (state, action: any) => {
state.updatePost.status = 'failed';
state.updatePost.error = action.payload;
});
// deletePost
builder.addCase(deletePost.pending, (state) => {
state.deletePost.status = 'loading';
});
builder.addCase(
deletePost.fulfilled,
(state, action: PayloadAction<number>) => {
state.deletePost.status = 'successful';
state.deletePost.deletedId = action.payload;
// удалить из всех страниц
for (const page of Object.values(state.fetchPosts.pages)) {
page.items = page.items.filter(
(p) => p.id !== action.payload,
);
}
// если открыт индивидуальный пост
if (state.fetchPostById.post?.id === action.payload) {
state.fetchPostById.post = undefined;
}
},
);
builder.addCase(deletePost.rejected, (state, action: any) => {
state.deletePost.status = 'failed';
state.deletePost.error = action.payload;
});
},
});
export const { setGroupFeedStatus } = postsSlice.actions;
export const groupFeedReducer = postsSlice.reducer;