Add attempts
This commit is contained in:
@@ -2,7 +2,7 @@ import { useEffect } from 'react';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||
import { Navigate, Route, Routes, useParams } from 'react-router-dom';
|
||||
import { fetchContestById } from '../../../redux/slices/contests';
|
||||
import { fetchContestById, fetchMyAttemptsInContest } from '../../../redux/slices/contests';
|
||||
import ContestMissions from './Missions';
|
||||
import Submissions from './Submissions';
|
||||
|
||||
@@ -30,6 +30,7 @@ const Contest = () => {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchContestById(contestIdNumber));
|
||||
dispatch(fetchMyAttemptsInContest(contestIdNumber));
|
||||
}, [contestIdNumber]);
|
||||
|
||||
return (
|
||||
|
||||
@@ -2,6 +2,7 @@ import { cn } from '../../../lib/cn';
|
||||
import { IconError, IconSuccess } from '../../../assets/icons/missions';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { toastWarning } from '../../../lib/toastNotification';
|
||||
|
||||
export interface MissionItemProps {
|
||||
contestId: number;
|
||||
@@ -11,6 +12,7 @@ export interface MissionItemProps {
|
||||
memoryLimit?: number;
|
||||
type?: 'first' | 'second';
|
||||
status?: 'success' | 'error';
|
||||
attemptsStarted?: boolean;
|
||||
}
|
||||
|
||||
export function formatMilliseconds(ms: number): string {
|
||||
@@ -32,6 +34,7 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
||||
memoryLimit = 256 * 1024 * 1024,
|
||||
type,
|
||||
status,
|
||||
attemptsStarted,
|
||||
}) => {
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
@@ -50,7 +53,12 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
||||
'cursor-pointer brightness-100 hover:brightness-125 transition-all duration-300',
|
||||
)}
|
||||
onClick={() => {
|
||||
navigate(`/mission/${id}?back=${path}&contestId=${contestId}`);
|
||||
if (attemptsStarted){
|
||||
navigate(`/mission/${id}?back=${path}&contestId=${contestId}`);
|
||||
}
|
||||
else{
|
||||
toastWarning("Нужно начать попытку")
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="text-[18px] font-bold">#{id}</div>
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { FC, useEffect } from 'react';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import MissionItem from './MissionItem';
|
||||
import {
|
||||
Contest,
|
||||
fetchContestById,
|
||||
fetchContests,
|
||||
fetchMyAttemptsInContest,
|
||||
fetchMySubmissions,
|
||||
setContestStatus,
|
||||
startContestAttempt,
|
||||
} from '../../../redux/slices/contests';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||
@@ -20,6 +24,8 @@ interface ContestMissionsProps {
|
||||
contest?: Contest;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
@@ -27,6 +33,44 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
(state) => state.contests.fetchMySubmissions,
|
||||
);
|
||||
|
||||
const attempts = useAppSelector((state) => state.contests.fetchMyAttemptsInContest.attempts);
|
||||
const [attemptsStarted, setAttemptsStarted] = useState<boolean>(false);
|
||||
|
||||
|
||||
const [time, setTime] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
const calc = (time: string) => {
|
||||
return time != "" && new Date() <= new Date(time);
|
||||
}
|
||||
if (attempts.length && calc(attempts[0].expiresAt)){
|
||||
|
||||
setAttemptsStarted(true);
|
||||
|
||||
const diffMs = new Date(attempts[0].expiresAt).getTime() - new Date().getTime();
|
||||
|
||||
const seconds = Math.floor((diffMs / 1000));
|
||||
|
||||
setTime(seconds)
|
||||
|
||||
const interval = setInterval(() => {
|
||||
setTime((t) => {
|
||||
if (t <= 1) {
|
||||
clearInterval(interval); // остановка таймера
|
||||
setAttemptsStarted(false); // можно закрыть попытку или уведомить пользователя
|
||||
return 0;
|
||||
}
|
||||
return t - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
else
|
||||
setAttemptsStarted(false);
|
||||
}, [attempts])
|
||||
|
||||
useEffect(() => {
|
||||
if (contest) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
@@ -53,6 +97,12 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
|
||||
const totalCount = contest.missions?.length ?? 0;
|
||||
|
||||
|
||||
// форматирование: mm:ss
|
||||
const minutes = String(Math.floor(time / 60)).padStart(2, "0");
|
||||
const seconds = String(time % 60).padStart(2, "0");
|
||||
|
||||
|
||||
return (
|
||||
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] gap-[20px]">
|
||||
<div className="">
|
||||
@@ -72,17 +122,34 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
Контест #{contest.id}
|
||||
</span>
|
||||
</div>
|
||||
<div>{contest.attemptDurationMinutes ?? 0} минут</div>
|
||||
<div className="text-liquid-light font-bold text-[18px]">
|
||||
{attemptsStarted
|
||||
? `${minutes}:${seconds}`
|
||||
: `Длительность попытки: ${contest.attemptDurationMinutes ?? 0} минут`}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||
<PrimaryButton
|
||||
<div className='flex gap-[20px]'>
|
||||
{ attempts.length == 0 || !attemptsStarted ? <PrimaryButton
|
||||
onClick={() => {
|
||||
dispatch(startContestAttempt(contest.id)).unwrap().then(() =>
|
||||
{
|
||||
dispatch(fetchMyAttemptsInContest(contest.id));
|
||||
}
|
||||
);
|
||||
}}
|
||||
text="Начать попытку"
|
||||
/> : <></>
|
||||
} <PrimaryButton
|
||||
onClick={() => {
|
||||
navigate(`/contest/${contest.id}/submissions`);
|
||||
}}
|
||||
text="Мои посылки"
|
||||
/>
|
||||
/></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
|
||||
@@ -106,6 +173,7 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
|
||||
return (
|
||||
<MissionItem
|
||||
attemptsStarted={attemptsStarted}
|
||||
contestId={contest.id}
|
||||
key={i}
|
||||
id={v.id}
|
||||
|
||||
@@ -33,6 +33,7 @@ const Submissions: FC<SubmissionsProps> = ({ contest }) => {
|
||||
(state) => state.contests.fetchMySubmissions
|
||||
);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (contest && contest.id) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
|
||||
Reference in New Issue
Block a user