update articles slice

This commit is contained in:
Виталий Лавшонок
2025-11-13 16:32:32 +03:00
parent 18d17f895d
commit ded41ba7f0
8 changed files with 496 additions and 285 deletions

View File

@@ -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<ArticleItemProps> = ({ id, name, tags }) => {
const ArticleItem: FC<ArticleItemProps> = ({ id, name, tags }) => {
const navigate = useNavigate();
return (
<div
className={cn(
'w-full relative rounded-[10px] text-liquid-white mb-[20px]',
// type == "first" ? "bg-liquid-lighter" : "bg-liquid-background",
'gap-[20px] px-[20px] py-[10px] box-border ',
'gap-[20px] px-[20px] py-[10px] box-border',
'border-b-[1px] border-b-liquid-lighter cursor-pointer hover:bg-liquid-lighter transition-all duration-300',
)}
onClick={() => {
navigate(`/article/${id}?back=/home/account/articles`);
}}
onClick={() =>
navigate(`/article/${id}?back=/home/account/articles`)
}
>
<div className="h-[23px] flex ">
<div className="h-[23px] flex">
<div className="text-[18px] font-bold w-[60px] mr-[20px] flex items-center">
#{id}
</div>
@@ -35,13 +34,14 @@ const ArticleItem: React.FC<ArticleItemProps> = ({ id, name, tags }) => {
{name}
</div>
</div>
<div className="text-[14px] flex text-liquid-light gap-[10px] mt-[10px]">
{tags.map((v, i) => (
<div
key={i}
className={cn(
'rounded-full px-[16px] py-[8px] bg-liquid-lighter',
v == 'Sertificated' && 'text-liquid-green',
v === 'Sertificated' && 'text-liquid-green',
)}
>
{v}
@@ -50,8 +50,9 @@ const ArticleItem: React.FC<ArticleItemProps> = ({ id, name, tags }) => {
</div>
<img
className=" absolute right-[10px] top-[10px] h-[24px] w-[24px] hover:bg-liquid-light rounded-[5px] transition-all duration-300"
className="absolute right-[10px] top-[10px] h-[24px] w-[24px] hover:bg-liquid-light rounded-[5px] transition-all duration-300"
src={Edit}
alt="Редактировать"
onClick={(e) => {
e.stopPropagation();
navigate(
@@ -69,49 +70,79 @@ interface ArticlesBlockProps {
const ArticlesBlock: FC<ArticlesBlockProps> = ({ className = '' }) => {
const dispatch = useAppDispatch();
const articles = useAppSelector((state) => state.articles.articles);
const [active, setActive] = useState<boolean>(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 (
<div className="h-full w-full relative p-[20px]">
<div
className={cn(
' border-b-[1px] border-b-liquid-lighter rounded-[10px]',
'border-b-[1px] border-b-liquid-lighter rounded-[10px]',
className,
)}
>
{/* Заголовок */}
<div
className={cn(
' h-[40px] text-[24px] font-bold flex gap-[10px] border-b-[1px] border-b-transparent items-center cursor-pointer transition-all duration-300',
active && ' border-b-liquid-lighter',
'h-[40px] text-[24px] font-bold flex gap-[10px] border-b-[1px] border-b-transparent items-center cursor-pointer transition-all duration-300',
active && 'border-b-liquid-lighter',
)}
onClick={() => {
setActive(!active);
}}
onClick={() => setActive(!active)}
>
<span>Мои статьи</span>
<img
src={ChevroneDown}
alt="toggle"
className={cn(
'transition-all duration-300',
active && 'rotate-180',
)}
/>
</div>
{/* Контент */}
<div
className={cn(
' grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-300',
'grid grid-flow-row grid-rows-[0fr] opacity-0 transition-all duration-300',
active && 'grid-rows-[1fr] opacity-100',
)}
>
<div className="overflow-hidden">
<div className="grid gap-[20px] pt-[20px] pb-[20px] box-border">
{articles.map((v, i) => (
<ArticleItem key={i} {...v} />
{status === 'loading' && (
<div className="text-liquid-light">
Загрузка статей...
</div>
)}
{status === 'failed' && (
<div className="text-liquid-red">
Ошибка:{' '}
{error || 'Не удалось загрузить статьи'}
</div>
)}
{status === 'successful' &&
articles.length === 0 && (
<div className="text-liquid-light">
У вас пока нет статей
</div>
)}
{articles.map((v) => (
<ArticleItem key={v.id} {...v} />
))}
</div>
</div>

View File

@@ -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 <div>Загрузка...</div>;
// ========================
// Состояния загрузки / ошибки
// ========================
if (status === 'loading') {
return (
<div className="h-full w-full flex items-center justify-center text-liquid-light text-[18px]">
Загрузка статей...
</div>
);
}
if (status === 'failed') {
return (
<div className="h-full w-full flex flex-col items-center justify-center text-liquid-red text-[18px]">
Ошибка при загрузке статей
{error && (
<div className="text-liquid-light text-[14px] mt-2">
{error}
</div>
)}
</div>
);
}
// ========================
// Основной контент
// ========================
return (
<div className=" h-full w-full box-border p-[20px] pt-[20px]">
<div className="h-full w-full box-border p-[20px]">
<div className="h-full box-border">
{/* Заголовок */}
<div className="relative flex items-center mb-[20px]">
<div className="h-[50px] text-[40px] font-bold text-liquid-white flex items-center">
Статьи
</div>
<SecondaryButton
onClick={() => {
navigate('/article/create');
}}
onClick={() => navigate('/article/create')}
text="Создать статью"
className="absolute right-0"
/>
</div>
{/* Фильтры */}
<Filters />
<div>
{articles.map((v, i) => (
<ArticleItem key={i} {...v} />
))}
{/* Список статей */}
<div className="mt-[20px]">
{articles.length === 0 ? (
<div className="text-liquid-light text-[16px]">
Пока нет статей
</div>
) : (
articles.map((v) => <ArticleItem key={v.id} {...v} />)
)}
</div>
<div>pages</div>
{/* Пагинация (пока заглушка) */}
<div className="mt-[20px] text-liquid-light text-[14px]">
pages
</div>
</div>
</div>
);

View File

@@ -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 (
<div className="h-screen w-full overflow-y-scroll thin-dark-scrollbar p-[20px] gap-[5px] flex flex-col">
<div className="text-liquid-white font-bold text-[18px]">
Пользователи
</div>
{items.map((v, i) => {
return (
<>
{
<div className="text-liquid-light text-[16px] grid grid-cols-[40px,1fr] gap-[10px] items-center cursor-pointer hover:bg-liquid-lighter transition-all duration-300 rounded-[10px] p-[5px]">
<div className="h-[40px] w-[40px] rounded-[10px] bg-[#D9D9D9]"></div>
<div className="flex flex-col">
<div className="text-liquid-white font-bold text-[16px] leading-5">
{v.name}
</div>
<div className="text-liquid-light font-normal text-[16px] leading-5">
{v.role}
</div>
</div>
</div>
}
{i + 1 != items.length && (
<div className="h-[1px] w-full bg-liquid-lighter"></div>
)}
</>
);
})}
</div>
);
};