diff --git a/src/views/home/account/Account.tsx b/src/views/home/account/Account.tsx index 7dbf7af..5d4530a 100644 --- a/src/views/home/account/Account.tsx +++ b/src/views/home/account/Account.tsx @@ -1,9 +1,9 @@ import { Navigate, Route, Routes } from 'react-router-dom'; import AccountMenu from './AccoutMenu'; import RightPanel from './RightPanel'; -import MissionsBlock from './MissionsBlock'; -import ContestsBlock from './ContestsBlock'; -import ArticlesBlock from './ArticlesBlock'; +import MissionsBlock from './missions/MissionsBlock'; +import Contests from './contests/Contests'; +import ArticlesBlock from './articles/ArticlesBlock'; import { useAppDispatch } from '../../../redux/hooks'; import { useEffect } from 'react'; import { setMenuActivePage } from '../../../redux/slices/store'; @@ -32,10 +32,7 @@ const Account = () => { path="articles" element={} /> - } - /> + } /> { - const dispatch = useAppDispatch(); - - useEffect(() => { - dispatch(setMenuActiveProfilePage('contests')); - }, []); - return ( -
- Пока пусто :( -
- ); -}; - -export default ContestsBlock; diff --git a/src/views/home/account/ArticlesBlock.tsx b/src/views/home/account/articles/ArticlesBlock.tsx similarity index 93% rename from src/views/home/account/ArticlesBlock.tsx rename to src/views/home/account/articles/ArticlesBlock.tsx index 1d4f141..eba0bc8 100644 --- a/src/views/home/account/ArticlesBlock.tsx +++ b/src/views/home/account/articles/ArticlesBlock.tsx @@ -1,9 +1,9 @@ import { FC, useEffect, useState } from 'react'; -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 { 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 { useNavigate } from 'react-router-dom'; diff --git a/src/views/home/account/contests/ContestItem.tsx b/src/views/home/account/contests/ContestItem.tsx new file mode 100644 index 0000000..2245aa1 --- /dev/null +++ b/src/views/home/account/contests/ContestItem.tsx @@ -0,0 +1,124 @@ +import { cn } from '../../../../lib/cn'; +import { Account } from '../../../../assets/icons/auth'; +import { PrimaryButton } from '../../../../components/button/PrimaryButton'; +import { ReverseButton } from '../../../../components/button/ReverseButton'; +import { useNavigate } from 'react-router-dom'; + +export interface ContestItemProps { + id: number; + name: string; + startAt: string; + duration: number; + members: number; + statusRegister: 'reg' | 'nonreg'; + type: 'first' | 'second'; +} + +function formatDate(dateString: string): string { + const date = new Date(dateString); + + const day = date.getDate().toString().padStart(2, '0'); + const month = (date.getMonth() + 1).toString().padStart(2, '0'); + const year = date.getFullYear(); + + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + + return `${day}/${month}/${year}\n${hours}:${minutes}`; +} + +function formatWaitTime(ms: number): string { + const minutes = Math.floor(ms / 60000); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) { + const remainder = days % 10; + let suffix = 'дней'; + if (remainder === 1 && days !== 11) suffix = 'день'; + else if (remainder >= 2 && remainder <= 4 && (days < 10 || days > 20)) + suffix = 'дня'; + return `${days} ${suffix}`; + } else if (hours > 0) { + const mins = minutes % 60; + return mins > 0 ? `${hours} ч ${mins} мин` : `${hours} ч`; + } else { + return `${minutes} мин`; + } +} + +const ContestItem: React.FC = ({ + id, + name, + startAt, + duration, + members, + statusRegister, + type, +}) => { + const navigate = useNavigate(); + + const now = new Date(); + + const waitTime = new Date(startAt).getTime() - now.getTime(); + + return ( +
{ + navigate(`/contest/${id}`); + }} + > +
{name}
+
+ {/* {authors.map((v, i) =>

{v}

)} */} + valavshonok +
+
+ {formatDate(startAt)} +
+
{formatWaitTime(duration)}
+ {waitTime > 0 && ( +
+ {'До начала\n' + formatWaitTime(waitTime)} +
+ )} +
+
{members}
+ +
+
+ {statusRegister == 'reg' ? ( + <> + {' '} + { + e.stopPropagation(); + }} + text="Регистрация" + /> + + ) : ( + <> + {' '} + { + e.stopPropagation(); + }} + text="Вы записаны" + /> + + )} +
+
+ ); +}; + +export default ContestItem; diff --git a/src/views/home/account/contests/Contests.tsx b/src/views/home/account/contests/Contests.tsx new file mode 100644 index 0000000..a68f224 --- /dev/null +++ b/src/views/home/account/contests/Contests.tsx @@ -0,0 +1,62 @@ +import { useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from '../../../../redux/hooks'; +import { setMenuActiveProfilePage } from '../../../../redux/slices/store'; +import { fetchContests } from '../../../../redux/slices/contests'; +import ContestsBlock from './ContestsBlock'; + +const Contests = () => { + const dispatch = useAppDispatch(); + const now = new Date(); + + const [modalActive, setModalActive] = useState(false); + + // Берём данные из Redux + const contests = useAppSelector((state) => state.contests.contests); + const status = useAppSelector((state) => state.contests.statuses.create); + const error = useAppSelector((state) => state.contests.error); + + // При загрузке страницы — выставляем активную вкладку и подгружаем контесты + useEffect(() => { + dispatch(fetchContests({})); + }, []); + + useEffect(() => { + dispatch(setMenuActiveProfilePage('contests')); + }, []); + + if (status == 'loading') { + return ( +
Загрузка контестов...
+ ); + } + + if (error) { + return
Ошибка: {error}
; + } + + return ( +
+ { + const endTime = new Date(contest.endsAt).getTime(); + return endTime >= now.getTime(); + })} + /> + + { + const endTime = new Date(contest.endsAt).getTime(); + return endTime < now.getTime(); + })} + /> +
+ ); +}; + +export default Contests; diff --git a/src/views/home/account/contests/ContestsBlock.tsx b/src/views/home/account/contests/ContestsBlock.tsx new file mode 100644 index 0000000..fbe2ded --- /dev/null +++ b/src/views/home/account/contests/ContestsBlock.tsx @@ -0,0 +1,76 @@ +import { useState, FC } from 'react'; +import { cn } from '../../../../lib/cn'; +import { ChevroneDown } from '../../../../assets/icons/groups'; +import ContestItem from './ContestItem'; +import { Contest } from '../../../../redux/slices/contests'; + +interface ContestsBlockProps { + contests: Contest[]; + title: string; + className?: string; + type?: string; +} + +const ContestsBlock: FC = ({ + contests, + title, + className, +}) => { + const [active, setActive] = useState(title != 'Скрытые'); + + return ( +
+
{ + setActive(!active); + }} + > + {title} + +
+
+
+
+ {contests.map((v, i) => ( + + ))} +
+
+
+
+ ); +}; + +export default ContestsBlock; diff --git a/src/views/home/account/MissionsBlock.tsx b/src/views/home/account/missions/MissionsBlock.tsx similarity index 75% rename from src/views/home/account/MissionsBlock.tsx rename to src/views/home/account/missions/MissionsBlock.tsx index 1fce2a8..c20a241 100644 --- a/src/views/home/account/MissionsBlock.tsx +++ b/src/views/home/account/missions/MissionsBlock.tsx @@ -1,6 +1,6 @@ import { useEffect } from 'react'; -import { useAppDispatch } from '../../../redux/hooks'; -import { setMenuActiveProfilePage } from '../../../redux/slices/store'; +import { useAppDispatch } from '../../../../redux/hooks'; +import { setMenuActiveProfilePage } from '../../../../redux/slices/store'; const MissionsBlock = () => { const dispatch = useAppDispatch();