group posts

This commit is contained in:
Виталий Лавшонок
2025-11-15 22:23:26 +03:00
parent dfc2985209
commit 56b6f9b339
28 changed files with 624 additions and 47 deletions

View File

@@ -1,24 +1,50 @@
import { FC } from 'react';
import { FC, useEffect } from 'react';
import { cn } from '../../../lib/cn';
import { useParams, Navigate } from 'react-router-dom';
import { useParams, Navigate, Routes, Route } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import { fetchGroupById } from '../../../redux/slices/groups';
import GroupMenu from './GroupMenu';
import { Posts } from './posts/Posts';
import { SearchInput } from '../../../components/input/SearchInput';
import { Chat } from './chat/Chat';
import { Contests } from './contests/Contests';
interface GroupsBlockProps {}
const Group: FC<GroupsBlockProps> = () => {
const { groupId } = useParams<{ groupId: string }>();
const groupIdNumber = Number(groupId);
if (!groupId || isNaN(groupIdNumber) || !groupIdNumber) {
const groupId = Number(useParams<{ groupId: string }>().groupId);
if (!groupId) {
return <Navigate to="/home/groups" replace />;
}
const dispatch = useAppDispatch();
const group = useAppSelector((state) => state.groups.fetchGroupById.group);
useEffect(() => {
dispatch(fetchGroupById(groupId));
}, [groupId]);
console.log(group);
return (
<div
className={cn(
'border-b-[1px] border-b-liquid-lighter rounded-[10px]',
' h-screen w-full text-liquid-white p-[20px] flex gap-[20px] flex-col',
)}
>
{groupIdNumber}
<div className="font-bold text-[40px]">{group?.name}</div>
<GroupMenu groupId={groupId} />
<Routes>
<Route path="home" element={<Posts groupId={groupId} />} />
<Route path="chat" element={<Chat />} />
<Route path="contests" element={<Contests />} />
<Route
path="*"
element={<Navigate to={`/group/${groupId}/home`} />}
/>
</Routes>
</div>
);
};

View File

@@ -0,0 +1,96 @@
import { MessageChat, Home, Cup } from '../../../assets/icons/group';
import React, { FC } from 'react';
import { Link } from 'react-router-dom';
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
import {
setMenuActivePage,
setMenuActiveProfilePage,
} from '../../../redux/slices/store';
interface MenuItemProps {
icon: string;
text: string;
href: string;
page: string;
profilePage: string;
active?: boolean;
}
const MenuItem: React.FC<MenuItemProps> = ({
icon,
text = '',
href = '',
active = false,
page = '',
profilePage = '',
}) => {
const dispatch = useAppDispatch();
return (
<Link
to={href}
className={`
flex items-center gap-3 p-[16px] rounded-[10px] h-[40px] text-[18px] font-bold
transition-all duration-300 text-liquid-white
active:scale-95 hover:bg-liquid-lighter hover:ring-[1px] hover:ring-liquid-light hover:ring-inset
${active && 'bg-liquid-lighter '}
`}
onClick={() => {
dispatch(setMenuActivePage(page));
dispatch(setMenuActiveProfilePage(profilePage));
}}
>
<img src={icon} />
<span>{text}</span>
</Link>
);
};
interface GroupMenuProps {
groupId: number;
}
const GroupMenu: FC<GroupMenuProps> = ({ groupId }) => {
const menuItems = [
{
text: 'Главная',
href: `/group/${groupId}/home`,
icon: Home,
page: 'group',
profilePage: 'home',
},
{
text: 'Чат',
href: `/group/${groupId}/chat`,
icon: MessageChat,
page: 'group',
profilePage: 'chat',
},
{
text: 'Контесты',
href: `/group/${groupId}/contests`,
icon: Cup,
page: 'group',
profilePage: 'contests',
},
];
const activeGroupPage = useAppSelector(
(state) => state.store.menu.activeGroupPage,
);
return (
<div className="w-full relative flex gap-[10px]">
{menuItems.map((v, i) => (
<MenuItem
{...v}
key={i}
active={activeGroupPage == v.profilePage}
/>
))}
</div>
);
};
export default GroupMenu;

View File

@@ -0,0 +1,12 @@
import { useEffect } from 'react';
import { useAppDispatch } from '../../../../redux/hooks';
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
export const Chat = () => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(setMenuActiveGroupPage('chat'));
}, []);
return <></>;
};

View File

@@ -0,0 +1,12 @@
import { useEffect } from 'react';
import { useAppDispatch } from '../../../../redux/hooks';
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
export const Contests = () => {
const dispatch = useAppDispatch();
useEffect(() => {
dispatch(setMenuActiveGroupPage('contests'));
}, []);
return <></>;
};

View File

View File

@@ -0,0 +1,83 @@
import { FC, useEffect } from 'react';
import { useAppSelector, useAppDispatch } from '../../../../redux/hooks';
import { fetchGroupPosts } from '../../../../redux/slices/groupfeed';
import { SearchInput } from '../../../../components/input/SearchInput';
import { setMenuActiveGroupPage } from '../../../../redux/slices/store';
interface PostsProps {
groupId: number;
}
export const Posts: FC<PostsProps> = ({ groupId }) => {
const dispatch = useAppDispatch();
const { pages, status } = useAppSelector(
(state) => state.groupfeed.fetchPosts,
);
// Загружаем только первую страницу
useEffect(() => {
dispatch(fetchGroupPosts({ groupId, page: 0, pageSize: 20 }));
}, [groupId]);
useEffect(() => {
dispatch(setMenuActiveGroupPage('home'));
}, []);
const page0 = pages[0];
return (
<div className="h-full overflow-y-scroll thin-dark-scrollbar">
<div className="h-[40px] mb-[20px]">
<SearchInput
onChange={(v) => {}}
placeholder="Поиск сообщений"
/>
</div>
{status === 'loading' && <div>Загрузка...</div>}
{status === 'failed' && <div>Ошибка загрузки постов</div>}
{status == 'successful' &&
page0?.items &&
page0.items.length > 0 ? (
<div className="space-y-4">
{page0.items.map((post) => (
<div
key={post.id}
className="border border-gray-700 rounded p-3"
>
<div>
<b>ID:</b> {post.id}
</div>
<div>
<b>Название:</b> {post.name}
</div>
<div>
<b>Содержимое:</b> {post.content}
</div>
<div>
<b>Автор:</b> {post.authorUsername}
</div>
<div>
<b>Автор ID:</b> {post.authorId}
</div>
<div>
<b>Group ID:</b> {post.groupId}
</div>
<div>
<b>Создан:</b> {post.createdAt}
</div>
<div>
<b>Обновлён:</b> {post.updatedAt}
</div>
</div>
))}
</div>
) : status === 'successful' ? (
<div>Постов пока нет</div>
) : null}
</div>
);
};