diff --git a/src/pages/Article.tsx b/src/pages/Article.tsx index 44b910a..3282ab0 100644 --- a/src/pages/Article.tsx +++ b/src/pages/Article.tsx @@ -5,6 +5,7 @@ import { useEffect } from 'react'; import { fetchArticleById } from '../redux/slices/articles'; import MarkdownPreview from '../views/articleeditor/MarckDownPreview'; import { useQuery } from '../hooks/useQuery'; +import { ArticlesRightPanel } from '../views/home/rightpanel/Articles'; const Article = () => { // Получаем параметры из URL @@ -19,8 +20,12 @@ const Article = () => { return ; } const dispatch = useAppDispatch(); - const article = useAppSelector((state) => state.articles.currentArticle); - const status = useAppSelector((state) => state.articles.statuses.fetchById); + const article = useAppSelector( + (state) => state.articles.fetchArticleById.article, + ); + const status = useAppSelector( + (state) => state.articles.fetchArticleById.status, + ); useEffect(() => { dispatch(fetchArticleById(articleIdNumber)); @@ -65,7 +70,7 @@ const Article = () => { )} -
+ ); }; diff --git a/src/pages/ArticleEditor.tsx b/src/pages/ArticleEditor.tsx index 79002b8..8461e02 100644 --- a/src/pages/ArticleEditor.tsx +++ b/src/pages/ArticleEditor.tsx @@ -23,26 +23,33 @@ const ArticleEditor = () => { const query = useQuery(); const back = query.get('back') ?? undefined; const articleId = Number(query.get('articleId') ?? undefined); - const article = useAppSelector((state) => state.articles.currentArticle); - const refactor = articleId != undefined && !isNaN(articleId); + const refactor = articleId && !isNaN(articleId); + // Достаём данные из redux + const article = useAppSelector( + (state) => state.articles.fetchArticleById.article, + ); + + const statusCreate = useAppSelector( + (state) => state.articles.createArticle.status, + ); + const statusUpdate = useAppSelector( + (state) => state.articles.updateArticle.status, + ); + const statusDelete = useAppSelector( + (state) => state.articles.deleteArticle.status, + ); + + // Локальные состояния const [code, setCode] = useState(article?.content || ''); const [name, setName] = useState(article?.name || ''); const [tagInput, setTagInput] = useState(''); const [tags, setTags] = useState(article?.tags || []); - const [activeEditor, setActiveEditor] = useState(false); - const statusCreate = useAppSelector( - (state) => state.articles.statuses.create, - ); - const statusUpdate = useAppSelector( - (state) => state.articles.statuses.update, - ); - const statusDelete = useAppSelector( - (state) => state.articles.statuses.delete, - ); - + // ========================== + // Теги + // ========================== const addTag = () => { const newTag = tagInput.trim(); if (newTag && !tags.includes(newTag)) { @@ -55,53 +62,63 @@ const ArticleEditor = () => { setTags(tags.filter((tag) => tag !== tagToRemove)); }; + // ========================== + // Эффекты по статусам + // ========================== useEffect(() => { - if (statusCreate == 'successful') { - dispatch(setArticlesStatus({ key: 'create', status: 'idle' })); - navigate(back ? back : '/home/articles'); + if (statusCreate === 'successful') { + dispatch( + setArticlesStatus({ key: 'createArticle', status: 'idle' }), + ); + navigate(back ?? '/home/articles'); } }, [statusCreate]); useEffect(() => { - if (statusDelete == 'successful') { - dispatch(setArticlesStatus({ key: 'delete', status: 'idle' })); - navigate(back ? back : '/home/articles'); - } - }, [statusDelete]); - - useEffect(() => { - if (statusUpdate == 'successful') { - dispatch(setArticlesStatus({ key: 'update', status: 'idle' })); - navigate(back ? back : '/home/articles'); + if (statusUpdate === 'successful') { + dispatch( + setArticlesStatus({ key: 'updateArticle', status: 'idle' }), + ); + navigate(back ?? '/home/articles'); } }, [statusUpdate]); + useEffect(() => { + if (statusDelete === 'successful') { + dispatch( + setArticlesStatus({ key: 'deleteArticle', status: 'idle' }), + ); + navigate(back ?? '/home/articles'); + } + }, [statusDelete]); + + // ========================== + // Получение статьи + // ========================== useEffect(() => { if (articleId) { dispatch(fetchArticleById(articleId)); } }, [articleId]); + // Обновление локального состояния после загрузки статьи useEffect(() => { if (article && refactor) { - setCode(article?.content || ''); - setName(article?.name || ''); - setTags(article?.tags || []); + setCode(article.content || ''); + setName(article.name || ''); + setTags(article.tags || []); } }, [article]); + // ========================== + // Рендер + // ========================== return (
{activeEditor ? ( -
{ - setActiveEditor(false); - }} - /> +
setActiveEditor(false)} /> ) : ( -
navigate(back ? back : '/home/articles')} - /> +
navigate(back ?? '/home/articles')} /> )} {activeEditor ? ( @@ -113,6 +130,8 @@ const ArticleEditor = () => { ? `Редактирование статьи: \"${article?.name}\"` : 'Создание статьи'}
+ + {/* Кнопки действий */}
{refactor ? (
@@ -129,16 +148,16 @@ const ArticleEditor = () => { }} text="Обновить" className="mt-[20px]" - disabled={statusUpdate == 'loading'} + disabled={statusUpdate === 'loading'} /> { - dispatch(deleteArticle(articleId)); - }} + onClick={() => + dispatch(deleteArticle(articleId)) + } color="error" text="Удалить" className="mt-[20px]" - disabled={statusDelete == 'loading'} + disabled={statusDelete === 'loading'} />
) : ( @@ -154,11 +173,12 @@ const ArticleEditor = () => { }} text="Опубликовать" className="mt-[20px]" - disabled={statusCreate == 'loading'} + disabled={statusCreate === 'loading'} /> )}
+ {/* Название */} { className="mt-[20px] max-w-[600px]" type="text" label="Название" - onChange={(v) => { - setName(v); - }} + onChange={setName} placeholder="Новая статья" /> - {/* Блок для тегов */} + {/* Теги */}
{ className="mt-[20px] max-w-[600px]" type="text" label="Теги" - onChange={(v) => { - setTagInput(v); - }} + onChange={setTagInput} defaultState={tagInput} placeholder="arrays" onKeyDown={(e) => { - console.log(e.key); - if (e.key == 'Enter') addTag(); + if (e.key === 'Enter') addTag(); }} /> {
+ {/* Просмотр и переход в редактор */} setActiveEditor(true)} text="Редактировать текст" @@ -222,7 +238,7 @@ const ArticleEditor = () => { /> )} diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 939635f..5b3114c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -11,12 +11,13 @@ import Articles from '../views/home/articles/Articles'; import Groups from '../views/home/groups/Groups'; import Contests from '../views/home/contests/Contests'; import { PrimaryButton } from '../components/button/PrimaryButton'; -import Group from '../views/home/groups/Group'; +import Group from '../views/home/group/Group'; import Contest from '../views/home/contest/Contest'; import Account from '../views/home/account/Account'; import ProtectedRoute from '../components/router/ProtectedRoute'; import { MissionsRightPanel } from '../views/home/rightpanel/Missions'; import { ArticlesRightPanel } from '../views/home/rightpanel/Articles'; +import { GroupRightPanel } from '../views/home/rightpanel/Group'; const Home = () => { const name = useAppSelector((state) => state.auth.username); @@ -78,6 +79,10 @@ const Home = () => { } /> } /> + } + /> } diff --git a/src/redux/slices/articles.ts b/src/redux/slices/articles.ts index 73c59df..e2346dc 100644 --- a/src/redux/slices/articles.ts +++ b/src/redux/slices/articles.ts @@ -1,7 +1,9 @@ import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit'; import axios from '../../axios'; -// ─── Типы ──────────────────────────────────────────── +// ===================== +// Типы +// ===================== type Status = 'idle' | 'loading' | 'successful' | 'failed'; @@ -15,39 +17,145 @@ export interface Article { updatedAt: string; } -interface ArticlesState { - articles: Article[]; - currentArticle?: Article; +interface ArticlesResponse { hasNextPage: boolean; - statuses: { - create: Status; - update: Status; - delete: Status; - fetchAll: Status; - fetchById: Status; + 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; }; - error: string | null; } const initialState: ArticlesState = { - articles: [], - currentArticle: undefined, - hasNextPage: false, - statuses: { - create: 'idle', - update: 'idle', - delete: 'idle', - fetchAll: 'idle', - fetchById: 'idle', + 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, }, - error: null, }; -// ─── Async Thunks ───────────────────────────────────── +// ===================== +// Async Thunks +// ===================== -// POST /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; + } 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/createArticle', + 'articles/create', async ( { name, @@ -57,12 +165,12 @@ export const createArticle = createAsyncThunk( { rejectWithValue }, ) => { try { - const response = await axios.post('/articles', { + const response = await axios.post
('/articles', { name, content, tags, }); - return response.data as Article; + return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при создании статьи', @@ -71,9 +179,9 @@ export const createArticle = createAsyncThunk( }, ); -// PUT /articles/{articleId} +// Обновление статьи export const updateArticle = createAsyncThunk( - 'articles/updateArticle', + 'articles/update', async ( { articleId, @@ -84,12 +192,15 @@ export const updateArticle = createAsyncThunk( { rejectWithValue }, ) => { try { - const response = await axios.put(`/articles/${articleId}`, { - name, - content, - tags, - }); - return response.data as Article; + const response = await axios.put
( + `/articles/${articleId}`, + { + name, + content, + tags, + }, + ); + return response.data; } catch (err: any) { return rejectWithValue( err.response?.data?.message || 'Ошибка при обновлении статьи', @@ -98,9 +209,9 @@ export const updateArticle = createAsyncThunk( }, ); -// DELETE /articles/{articleId} +// Удаление статьи export const deleteArticle = createAsyncThunk( - 'articles/deleteArticle', + 'articles/delete', async (articleId: number, { rejectWithValue }) => { try { await axios.delete(`/articles/${articleId}`); @@ -113,186 +224,136 @@ export const deleteArticle = createAsyncThunk( }, ); -// 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 ──────────────────────────────────────────── +// ===================== +// Slice +// ===================== const articlesSlice = createSlice({ name: 'articles', initialState, reducers: { - clearCurrentArticle: (state) => { - state.currentArticle = undefined; - }, setArticlesStatus: ( state, - action: PayloadAction<{ - key: keyof ArticlesState['statuses']; - status: Status; - }>, + action: PayloadAction<{ key: keyof ArticlesState; status: Status }>, ) => { const { key, status } = action.payload; - state.statuses[key] = status; + if (state[key]) { + (state[key] as any).status = 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 ─── + // fetchArticles builder.addCase(fetchArticles.pending, (state) => { - state.statuses.fetchAll = 'loading'; - state.error = null; + state.fetchArticles.status = 'loading'; + state.fetchArticles.error = undefined; }); 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; + (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; + }); - // ─── FETCH ARTICLE BY ID ─── + // 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.statuses.fetchById = 'loading'; - state.error = null; + state.fetchArticleById.status = 'loading'; + state.fetchArticleById.error = undefined; }); builder.addCase( fetchArticleById.fulfilled, (state, action: PayloadAction
) => { - state.statuses.fetchById = 'successful'; - state.currentArticle = action.payload; + 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( - fetchArticleById.rejected, - (state, action: PayloadAction) => { - state.statuses.fetchById = 'failed'; - state.error = action.payload; + 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 { clearCurrentArticle, setArticlesStatus } = articlesSlice.actions; +export const { setArticlesStatus } = articlesSlice.actions; export const articlesReducer = articlesSlice.reducer; diff --git a/src/views/home/account/articles/ArticlesBlock.tsx b/src/views/home/account/articles/ArticlesBlock.tsx index eba0bc8..eb91f9b 100644 --- a/src/views/home/account/articles/ArticlesBlock.tsx +++ b/src/views/home/account/articles/ArticlesBlock.tsx @@ -3,8 +3,7 @@ import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; import { setMenuActiveProfilePage } from '../../../../redux/slices/store'; import { cn } from '../../../../lib/cn'; import { ChevroneDown, Edit } from '../../../../assets/icons/groups'; -import { fetchArticles } from '../../../../redux/slices/articles'; - +import { fetchMyArticles } from '../../../../redux/slices/articles'; import { useNavigate } from 'react-router-dom'; export interface ArticleItemProps { @@ -13,21 +12,21 @@ export interface ArticleItemProps { tags: string[]; } -const ArticleItem: React.FC = ({ id, name, tags }) => { +const ArticleItem: FC = ({ id, name, tags }) => { const navigate = useNavigate(); + return (
{ - navigate(`/article/${id}?back=/home/account/articles`); - }} + onClick={() => + navigate(`/article/${id}?back=/home/account/articles`) + } > -
+
#{id}
@@ -35,13 +34,14 @@ const ArticleItem: React.FC = ({ id, name, tags }) => { {name}
+
{tags.map((v, i) => (
{v} @@ -50,8 +50,9 @@ const ArticleItem: React.FC = ({ id, name, tags }) => {
Редактировать { e.stopPropagation(); navigate( @@ -69,49 +70,79 @@ interface ArticlesBlockProps { const ArticlesBlock: FC = ({ className = '' }) => { const dispatch = useAppDispatch(); - const articles = useAppSelector((state) => state.articles.articles); const [active, setActive] = useState(true); + // ✅ Берём только "мои статьи" + const articles = useAppSelector( + (state) => state.articles.fetchMyArticles.articles, + ); + const status = useAppSelector( + (state) => state.articles.fetchMyArticles.status, + ); + const error = useAppSelector( + (state) => state.articles.fetchMyArticles.error, + ); + useEffect(() => { dispatch(setMenuActiveProfilePage('articles')); - dispatch(fetchArticles({})); - }, []); + dispatch(fetchMyArticles()); + }, [dispatch]); + return (
+ {/* Заголовок */}
{ - setActive(!active); - }} + onClick={() => setActive(!active)} > Мои статьи toggle
+ + {/* Контент */}
- {articles.map((v, i) => ( - + {status === 'loading' && ( +
+ Загрузка статей... +
+ )} + {status === 'failed' && ( +
+ Ошибка:{' '} + {error || 'Не удалось загрузить статьи'} +
+ )} + {status === 'successful' && + articles.length === 0 && ( +
+ У вас пока нет статей +
+ )} + {articles.map((v) => ( + ))}
diff --git a/src/views/home/articles/Articles.tsx b/src/views/home/articles/Articles.tsx index 5d124a8..5c3caad 100644 --- a/src/views/home/articles/Articles.tsx +++ b/src/views/home/articles/Articles.tsx @@ -7,51 +7,84 @@ import { useNavigate } from 'react-router-dom'; import { fetchArticles } from '../../../redux/slices/articles'; import Filters from './Filter'; -export interface Article { - id: number; - name: string; - tags: string[]; -} - const Articles = () => { const dispatch = useAppDispatch(); const navigate = useNavigate(); - const articles = useAppSelector((state) => state.articles.articles); - const status = useAppSelector((state) => state.articles.statuses.fetchAll); + // ✅ Берём данные из нового состояния + const articles = useAppSelector( + (state) => state.articles.fetchArticles.articles, + ); + const status = useAppSelector( + (state) => state.articles.fetchArticles.status, + ); + const error = useAppSelector((state) => state.articles.fetchArticles.error); useEffect(() => { dispatch(setMenuActivePage('articles')); dispatch(fetchArticles({})); - }, []); + }, [dispatch]); - if (status == 'loading') return
Загрузка...
; + // ======================== + // Состояния загрузки / ошибки + // ======================== + if (status === 'loading') { + return ( +
+ Загрузка статей... +
+ ); + } + if (status === 'failed') { + return ( +
+ Ошибка при загрузке статей + {error && ( +
+ {error} +
+ )} +
+ ); + } + + // ======================== + // Основной контент + // ======================== return ( -
+
+ {/* Заголовок */}
Статьи
{ - navigate('/article/create'); - }} + onClick={() => navigate('/article/create')} text="Создать статью" className="absolute right-0" />
+ {/* Фильтры */} -
- {articles.map((v, i) => ( - - ))} + {/* Список статей */} +
+ {articles.length === 0 ? ( +
+ Пока нет статей +
+ ) : ( + articles.map((v) => ) + )}
-
pages
+ {/* Пагинация (пока заглушка) */} +
+ pages +
); diff --git a/src/views/home/groups/Group.tsx b/src/views/home/group/Group.tsx similarity index 100% rename from src/views/home/groups/Group.tsx rename to src/views/home/group/Group.tsx diff --git a/src/views/home/rightpanel/Group.tsx b/src/views/home/rightpanel/Group.tsx new file mode 100644 index 0000000..28b26fb --- /dev/null +++ b/src/views/home/rightpanel/Group.tsx @@ -0,0 +1,60 @@ +import { FC } from 'react'; + +export const GroupRightPanel: FC = () => { + const items = [ + { + name: 'Игнат Герасименко', + role: 'Администратор', + }, + { + name: 'Алиса Макаренко', + role: 'Модератор', + }, + { + name: 'Федор Картман', + role: 'Модератор', + }, + { + name: 'Карина Механаджанович', + role: 'Участник', + }, + { + name: 'Михаил Ангрский', + role: 'Участник', + }, + { + name: 'newuser', + role: 'Участник (Вы)', + }, + ]; + return ( +
+
+ Пользователи +
+ + {items.map((v, i) => { + return ( + <> + { +
+
+
+
+ {v.name} +
+
+ {v.role} +
+
+
+ } + {i + 1 != items.length && ( +
+ )} + + ); + })} +
+ ); +};