import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; // ===================== // Типы // ===================== type Status = 'idle' | 'loading' | 'successful' | 'failed'; export interface Article { id: number; authorId: number; name: string; content: string; tags: string[]; createdAt: string; updatedAt: string; } interface ArticlesResponse { hasNextPage: boolean; articles: Article[]; } // ===================== // Состояние // ===================== interface ArticlesState { fetchArticles: { articles: Article[]; hasNextPage: boolean; status: Status; error?: string; }; fetchArticleById: { article?: Article; status: Status; error?: string; }; createArticle: { article?: Article; status: Status; error?: string; }; updateArticle: { article?: Article; status: Status; error?: string; }; deleteArticle: { status: Status; error?: string; }; fetchMyArticles: { articles: Article[]; status: Status; error?: string; }; } const initialState: ArticlesState = { fetchArticles: { articles: [], hasNextPage: false, status: 'idle', error: undefined, }, fetchArticleById: { article: undefined, status: 'idle', error: undefined, }, createArticle: { article: undefined, status: 'idle', error: undefined, }, updateArticle: { article: undefined, status: 'idle', error: undefined, }, deleteArticle: { status: 'idle', error: undefined, }, fetchMyArticles: { articles: [], status: 'idle', error: undefined, }, }; // ===================== // Async Thunks // ===================== // Все статьи export const fetchArticles = createAsyncThunk( 'articles/fetchArticles', async ( { page = 0, pageSize = 10, tags, }: { page?: number; pageSize?: number; tags?: string[] } = {}, { rejectWithValue }, ) => { try { const params: any = { page, pageSize }; if (tags && tags.length > 0) params.tags = tags; const response = await axios.get('/articles', { params, }); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при получении статей', ); } }, ); // Мои статьи export const fetchMyArticles = createAsyncThunk( 'articles/fetchMyArticles', async (_, { rejectWithValue }) => { try { const response = await axios.get('/articles/my'); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при получении моих статей', ); } }, ); // Статья по ID export const fetchArticleById = createAsyncThunk( 'articles/fetchById', async (articleId: number, { rejectWithValue }) => { try { const response = await axios.get
(`/articles/${articleId}`); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при получении статьи', ); } }, ); // Создание статьи export const createArticle = createAsyncThunk( 'articles/create', async ( { name, content, tags, }: { name: string; content: string; tags: string[] }, { rejectWithValue }, ) => { try { const response = await axios.post
('/articles', { name, content, tags, }); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при создании статьи', ); } }, ); // Обновление статьи export const updateArticle = createAsyncThunk( 'articles/update', async ( { articleId, name, content, tags, }: { articleId: number; name: string; content: string; tags: string[] }, { rejectWithValue }, ) => { try { const response = await axios.put
( `/articles/${articleId}`, { name, content, tags, }, ); return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при обновлении статьи', ); } }, ); // Удаление статьи export const deleteArticle = createAsyncThunk( 'articles/delete', async (articleId: number, { rejectWithValue }) => { try { await axios.delete(`/articles/${articleId}`); return articleId; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при удалении статьи', ); } }, ); // ===================== // Slice // ===================== const articlesSlice = createSlice({ name: 'articles', initialState, reducers: { setArticlesStatus: ( state, action: PayloadAction<{ key: keyof ArticlesState; status: Status }>, ) => { const { key, status } = action.payload; if (state[key]) { (state[key] as any).status = status; } }, }, extraReducers: (builder) => { // fetchArticles builder.addCase(fetchArticles.pending, (state) => { state.fetchArticles.status = 'loading'; state.fetchArticles.error = undefined; }); builder.addCase( fetchArticles.fulfilled, (state, action: PayloadAction) => { state.fetchArticles.status = 'successful'; state.fetchArticles.articles = action.payload.articles; state.fetchArticles.hasNextPage = action.payload.hasNextPage; }, ); builder.addCase(fetchArticles.rejected, (state, action: any) => { state.fetchArticles.status = 'failed'; state.fetchArticles.error = action.payload; }); // fetchMyArticles builder.addCase(fetchMyArticles.pending, (state) => { state.fetchMyArticles.status = 'loading'; state.fetchMyArticles.error = undefined; }); builder.addCase( fetchMyArticles.fulfilled, (state, action: PayloadAction) => { state.fetchMyArticles.status = 'successful'; state.fetchMyArticles.articles = action.payload; }, ); builder.addCase(fetchMyArticles.rejected, (state, action: any) => { state.fetchMyArticles.status = 'failed'; state.fetchMyArticles.error = action.payload; }); // fetchArticleById builder.addCase(fetchArticleById.pending, (state) => { state.fetchArticleById.status = 'loading'; state.fetchArticleById.error = undefined; }); builder.addCase( fetchArticleById.fulfilled, (state, action: PayloadAction
) => { state.fetchArticleById.status = 'successful'; state.fetchArticleById.article = action.payload; }, ); builder.addCase(fetchArticleById.rejected, (state, action: any) => { state.fetchArticleById.status = 'failed'; state.fetchArticleById.error = action.payload; }); // createArticle builder.addCase(createArticle.pending, (state) => { state.createArticle.status = 'loading'; state.createArticle.error = undefined; }); builder.addCase( createArticle.fulfilled, (state, action: PayloadAction
) => { state.createArticle.status = 'successful'; state.createArticle.article = action.payload; }, ); builder.addCase(createArticle.rejected, (state, action: any) => { state.createArticle.status = 'failed'; state.createArticle.error = action.payload; }); // updateArticle builder.addCase(updateArticle.pending, (state) => { state.updateArticle.status = 'loading'; state.updateArticle.error = undefined; }); builder.addCase( updateArticle.fulfilled, (state, action: PayloadAction
) => { state.updateArticle.status = 'successful'; state.updateArticle.article = action.payload; }, ); builder.addCase(updateArticle.rejected, (state, action: any) => { state.updateArticle.status = 'failed'; state.updateArticle.error = action.payload; }); // deleteArticle builder.addCase(deleteArticle.pending, (state) => { state.deleteArticle.status = 'loading'; state.deleteArticle.error = undefined; }); builder.addCase( deleteArticle.fulfilled, (state, action: PayloadAction) => { state.deleteArticle.status = 'successful'; state.fetchArticles.articles = state.fetchArticles.articles.filter( (a) => a.id !== action.payload, ); state.fetchMyArticles.articles = state.fetchMyArticles.articles.filter( (a) => a.id !== action.payload, ); }, ); builder.addCase(deleteArticle.rejected, (state, action: any) => { state.deleteArticle.status = 'failed'; state.deleteArticle.error = action.payload; }); }, }); export const { setArticlesStatus } = articlesSlice.actions; export const articlesReducer = articlesSlice.reducer;