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 ArticlesState { articles: Article[]; currentArticle?: Article; hasNextPage: boolean; statuses: { create: Status; update: Status; delete: Status; fetchAll: Status; fetchById: Status; }; error: string | null; } const initialState: ArticlesState = { articles: [], currentArticle: undefined, hasNextPage: false, statuses: { create: 'idle', update: 'idle', delete: 'idle', fetchAll: 'idle', fetchById: 'idle', }, error: null, }; // ─── Async Thunks ───────────────────────────────────── // POST /articles export const createArticle = createAsyncThunk( 'articles/createArticle', async ( { name, content, tags, }: { name: string; content: string; tags: string[] }, { rejectWithValue }, ) => { try { const response = await axios.post('/articles', { name, content, tags, }); return response.data as Article; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при создании статьи', ); } }, ); // PUT /articles/{articleId} export const updateArticle = createAsyncThunk( 'articles/updateArticle', 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 as Article; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при обновлении статьи', ); } }, ); // DELETE /articles/{articleId} export const deleteArticle = createAsyncThunk( 'articles/deleteArticle', async (articleId: number, { rejectWithValue }) => { try { await axios.delete(`/articles/${articleId}`); return articleId; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при удалении статьи', ); } }, ); // GET /articles 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 as { hasNextPage: boolean; articles: Article[]; }; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при получении статей', ); } }, ); // GET /articles/{articleId} export const fetchArticleById = createAsyncThunk( 'articles/fetchArticleById', async (articleId: number, { rejectWithValue }) => { try { const response = await axios.get(`/articles/${articleId}`); return response.data as Article; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при получении статьи', ); } }, ); // ─── Slice ──────────────────────────────────────────── const articlesSlice = createSlice({ name: 'articles', initialState, reducers: { clearCurrentArticle: (state) => { state.currentArticle = undefined; }, setArticlesStatus: ( state, action: PayloadAction<{ key: keyof ArticlesState['statuses']; status: Status; }>, ) => { const { key, status } = action.payload; state.statuses[key] = status; }, }, extraReducers: (builder) => { // ─── CREATE ARTICLE ─── builder.addCase(createArticle.pending, (state) => { state.statuses.create = 'loading'; state.error = null; }); builder.addCase( createArticle.fulfilled, (state, action: PayloadAction
) => { state.statuses.create = 'successful'; state.articles.push(action.payload); }, ); builder.addCase( createArticle.rejected, (state, action: PayloadAction) => { state.statuses.create = 'failed'; state.error = action.payload; }, ); // ─── UPDATE ARTICLE ─── builder.addCase(updateArticle.pending, (state) => { state.statuses.update = 'loading'; state.error = null; }); builder.addCase( updateArticle.fulfilled, (state, action: PayloadAction
) => { state.statuses.update = 'successful'; const index = state.articles.findIndex( (a) => a.id === action.payload.id, ); if (index !== -1) state.articles[index] = action.payload; if (state.currentArticle?.id === action.payload.id) state.currentArticle = action.payload; }, ); builder.addCase( updateArticle.rejected, (state, action: PayloadAction) => { state.statuses.update = 'failed'; state.error = action.payload; }, ); // ─── DELETE ARTICLE ─── builder.addCase(deleteArticle.pending, (state) => { state.statuses.delete = 'loading'; state.error = null; }); builder.addCase( deleteArticle.fulfilled, (state, action: PayloadAction) => { state.statuses.delete = 'successful'; state.articles = state.articles.filter( (a) => a.id !== action.payload, ); if (state.currentArticle?.id === action.payload) state.currentArticle = undefined; }, ); builder.addCase( deleteArticle.rejected, (state, action: PayloadAction) => { state.statuses.delete = 'failed'; state.error = action.payload; }, ); // ─── FETCH ARTICLES ─── builder.addCase(fetchArticles.pending, (state) => { state.statuses.fetchAll = 'loading'; state.error = null; }); builder.addCase( fetchArticles.fulfilled, ( state, action: PayloadAction<{ hasNextPage: boolean; articles: Article[]; }>, ) => { state.statuses.fetchAll = 'successful'; state.articles = action.payload.articles; state.hasNextPage = action.payload.hasNextPage; }, ); builder.addCase( fetchArticles.rejected, (state, action: PayloadAction) => { state.statuses.fetchAll = 'failed'; state.error = action.payload; }, ); // ─── FETCH ARTICLE BY ID ─── builder.addCase(fetchArticleById.pending, (state) => { state.statuses.fetchById = 'loading'; state.error = null; }); builder.addCase( fetchArticleById.fulfilled, (state, action: PayloadAction
) => { state.statuses.fetchById = 'successful'; state.currentArticle = action.payload; }, ); builder.addCase( fetchArticleById.rejected, (state, action: PayloadAction) => { state.statuses.fetchById = 'failed'; state.error = action.payload; }, ); }, }); export const { clearCurrentArticle, setArticlesStatus } = articlesSlice.actions; export const articlesReducer = articlesSlice.reducer;