Add ettempts in contests
This commit is contained in:
@@ -2,8 +2,6 @@ import { FC, useEffect, useState } from 'react';
|
||||
import MissionItem from './MissionItem';
|
||||
import {
|
||||
Contest,
|
||||
fetchContestById,
|
||||
fetchContests,
|
||||
fetchMyAttemptsInContest,
|
||||
fetchMySubmissions,
|
||||
setContestStatus,
|
||||
@@ -24,52 +22,54 @@ interface ContestMissionsProps {
|
||||
contest?: Contest;
|
||||
}
|
||||
|
||||
|
||||
|
||||
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const { submissions, status } = useAppSelector(
|
||||
const { status } = useAppSelector(
|
||||
(state) => state.contests.fetchMySubmissions,
|
||||
);
|
||||
|
||||
const attempts = useAppSelector((state) => state.contests.fetchMyAttemptsInContest.attempts);
|
||||
const attempts = useAppSelector(
|
||||
(state) => state.contests.fetchMyAttemptsInContest.attempts,
|
||||
);
|
||||
const submissions = useAppSelector(
|
||||
(state) =>
|
||||
state.contests.fetchMyAttemptsInContest.attempts[0]?.submissions,
|
||||
);
|
||||
|
||||
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));
|
||||
return time != '' && new Date() <= new Date(time);
|
||||
};
|
||||
if (attempts.length && calc(attempts[0].expiresAt)) {
|
||||
setAttemptsStarted(true);
|
||||
|
||||
setTime(seconds)
|
||||
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;
|
||||
clearInterval(interval); // остановка таймера
|
||||
setAttemptsStarted(false); // можно закрыть попытку или уведомить пользователя
|
||||
return 0;
|
||||
}
|
||||
return t - 1;
|
||||
});
|
||||
}, 1000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
}
|
||||
else
|
||||
setAttemptsStarted(false);
|
||||
}, [attempts])
|
||||
} else setAttemptsStarted(false);
|
||||
}, [attempts]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contest) dispatch(fetchMySubmissions(contest.id));
|
||||
@@ -88,7 +88,7 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
}
|
||||
|
||||
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
||||
submissions.some(
|
||||
submissions?.some(
|
||||
(s) =>
|
||||
s.solution.missionId === mission.id &&
|
||||
s.solution.status === 'Accepted: All tests passed',
|
||||
@@ -97,11 +97,9 @@ 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");
|
||||
|
||||
// форматирование: 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]">
|
||||
@@ -123,43 +121,60 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-liquid-light font-bold text-[18px]">
|
||||
{attemptsStarted
|
||||
{attemptsStarted
|
||||
? `${minutes}:${seconds}`
|
||||
: `Длительность попытки: ${contest.attemptDurationMinutes ?? 0} минут`}
|
||||
: `Длительность попытки: ${
|
||||
contest.attemptDurationMinutes ?? 0
|
||||
} минут. Осталось попыток ${
|
||||
(contest.maxAttempts ?? 0) -
|
||||
(attempts?.length ?? 0)
|
||||
}/${contest.maxAttempts ?? 0}`}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||
<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 className="flex gap-[20px]">
|
||||
{attempts.length == 0 || !attemptsStarted ? (
|
||||
<PrimaryButton
|
||||
onClick={() => {
|
||||
dispatch(startContestAttempt(contest.id))
|
||||
.unwrap()
|
||||
.then(() => {
|
||||
dispatch(
|
||||
fetchMyAttemptsInContest(
|
||||
contest.id,
|
||||
),
|
||||
);
|
||||
});
|
||||
}}
|
||||
text="Начать попытку"
|
||||
disabled={
|
||||
(contest.maxAttempts ?? 0) -
|
||||
(attempts?.length ?? 0) <=
|
||||
0
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<></>
|
||||
)}{' '}
|
||||
<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]">
|
||||
<div className="w-full">
|
||||
{(contest.missions ?? []).map((v, i) => {
|
||||
const missionSubmissions = submissions.filter(
|
||||
const missionSubmissions = submissions?.filter(
|
||||
(s) => s.solution.missionId === v.id,
|
||||
);
|
||||
|
||||
const hasSuccess = missionSubmissions.some(
|
||||
const hasSuccess = missionSubmissions?.some(
|
||||
(s) =>
|
||||
s.solution.status ==
|
||||
'Accepted: All tests passed',
|
||||
@@ -167,7 +182,8 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
|
||||
const status = hasSuccess
|
||||
? 'success'
|
||||
: missionSubmissions.length > 0
|
||||
: missionSubmissions?.length &&
|
||||
missionSubmissions.length > 0
|
||||
? 'error'
|
||||
: undefined;
|
||||
|
||||
|
||||
@@ -1,130 +1,81 @@
|
||||
import SubmissionItem from "./SubmissionItem";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
||||
import { FC, useEffect } from "react";
|
||||
import {
|
||||
Contest,
|
||||
fetchMySubmissions,
|
||||
setContestStatus,
|
||||
} from "../../../redux/slices/contests";
|
||||
import { arrowLeft } from "../../../assets/icons/header";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { Contest, fetchMySubmissions } from '../../../redux/slices/contests';
|
||||
import { arrowLeft } from '../../../assets/icons/header';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import SubmissionsBlock from './SubmissionsBlock';
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: "Easy" | "Medium" | "Hard";
|
||||
tags: string[];
|
||||
timeLimit: number;
|
||||
memoryLimit: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: 'Easy' | 'Medium' | 'Hard';
|
||||
tags: string[];
|
||||
timeLimit: number;
|
||||
memoryLimit: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface SubmissionsProps {
|
||||
contest: Contest;
|
||||
contest: Contest;
|
||||
}
|
||||
|
||||
const Submissions: FC<SubmissionsProps> = ({ contest }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { submissions, status } = useAppSelector(
|
||||
(state) => state.contests.fetchMySubmissions
|
||||
);
|
||||
const attempts = useAppSelector(
|
||||
(state) => state.contests.fetchMyAttemptsInContest.attempts,
|
||||
);
|
||||
const submissions = useAppSelector(
|
||||
(state) =>
|
||||
state.contests.fetchMyAttemptsInContest.attempts[0]?.submissions,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (contest && contest.id) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (contest && contest.id) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
||||
submissions?.some(
|
||||
(s) =>
|
||||
s.solution.missionId === mission.id &&
|
||||
s.solution.status === 'Accepted: All tests passed',
|
||||
),
|
||||
).length;
|
||||
|
||||
useEffect(() => {
|
||||
if (status == "successful") {
|
||||
dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" }));
|
||||
}
|
||||
}, [status]);
|
||||
const totalCount = contest.missions?.length ?? 0;
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
if (status == "IncorrectAnswer") return "wronganswer";
|
||||
if (status == "TimeLimitError") return "timelimit";
|
||||
return undefined;
|
||||
};
|
||||
|
||||
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
||||
submissions.some(
|
||||
(s) =>
|
||||
s.solution.missionId === mission.id &&
|
||||
s.solution.status === "Accepted: All tests passed"
|
||||
)
|
||||
).length;
|
||||
|
||||
const totalCount = contest.missions?.length ?? 0;
|
||||
|
||||
return (
|
||||
<div className="h-full w-[calc(100%+250px)] box-border overflow-y-scroll overflow-x-hidden thin-scrollbar p-[20px] flex flex-col gap-[20px]">
|
||||
<div className="">
|
||||
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
|
||||
{contest.name}
|
||||
return (
|
||||
<div className="h-full w-[calc(100%+250px)] box-border overflow-y-scroll overflow-x-hidden thin-scrollbar p-[20px] flex flex-col gap-[20px]">
|
||||
<div className="">
|
||||
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
|
||||
{contest.name}
|
||||
</div>
|
||||
<div className="flex justify-between h-[24px] items-center gap-[10px]">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={arrowLeft}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate(`/contest/${contest.id}`);
|
||||
}}
|
||||
/>
|
||||
<span className="text-liquid-light font-bold text-[18px]">
|
||||
Контест #{contest.id}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-full overflow-y-scroll medium-scrollbar pr-[20px]">
|
||||
{attempts?.map((v, i) => (
|
||||
<SubmissionsBlock key={i} attempt={v} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex justify-between h-[24px] items-center gap-[10px]">
|
||||
<div className="flex items-center">
|
||||
<img
|
||||
src={arrowLeft}
|
||||
className="cursor-pointer"
|
||||
onClick={() => {
|
||||
navigate(`/contest/${contest.id}`);
|
||||
}}
|
||||
/>
|
||||
<span className="text-liquid-light font-bold text-[18px]">
|
||||
Контест #{contest.id}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div className="grid grid-cols-7 text-center items-center h-[43px] mb-[10px] text-[16px] font-bold text-liquid-white">
|
||||
<div>Посылка</div>
|
||||
<div>Когда</div>
|
||||
<div>Задача</div>
|
||||
<div>Язык</div>
|
||||
<div>Вердикт</div>
|
||||
<div>Время</div>
|
||||
<div>Память</div>
|
||||
</div>
|
||||
|
||||
{!submissions || submissions.length == 0 ? (
|
||||
<div className="text-liquid-brightmain text-[16px] font-medium text-center mt-[50px]">Вы еще ничего не отсылали</div>
|
||||
) : (
|
||||
<>
|
||||
{submissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
id={v.id ?? 0}
|
||||
datetime={v.solution.time}
|
||||
missionId={v.solution.missionId}
|
||||
language={v.solution.language}
|
||||
verdict={
|
||||
v.solution.testerMessage?.includes("Compilation failed")
|
||||
? "Compilation failed"
|
||||
: v.solution.testerMessage
|
||||
}
|
||||
duration={1000}
|
||||
memory={256 * 1024 * 1024}
|
||||
type={i % 2 ? "second" : "first"}
|
||||
status={
|
||||
v.solution.testerMessage == "All tests passed"
|
||||
? "success"
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
};
|
||||
|
||||
export default Submissions;
|
||||
|
||||
75
src/views/home/contest/SubmissionsBlock.tsx
Normal file
75
src/views/home/contest/SubmissionsBlock.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import SubmissionItem from './SubmissionItem';
|
||||
import { FC } from 'react';
|
||||
import { Attempt } from '../../../redux/slices/contests';
|
||||
|
||||
interface SubmissionsBlockProps {
|
||||
attempt: Attempt;
|
||||
}
|
||||
|
||||
const SubmissionsBlock: FC<SubmissionsBlockProps> = ({ attempt }) => {
|
||||
const submissions = attempt?.submissions;
|
||||
const isFinished = new Date(attempt.expiresAt) < new Date();
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
if (status == 'IncorrectAnswer') return 'wronganswer';
|
||||
if (status == 'TimeLimitError') return 'timelimit';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="mb-[50px]">
|
||||
<div className="flex items-center justify-center text-liquid-white font-bold text-[20px]">{`Попытка #${attempt.attemptId}`}</div>
|
||||
{!submissions || submissions.length == 0 ? (
|
||||
<></>
|
||||
) : (
|
||||
<div className="grid grid-cols-7 text-center items-center h-[43px] mb-[10px] text-[16px] font-bold text-liquid-white">
|
||||
<div>Посылка</div>
|
||||
<div>Когда</div>
|
||||
<div>Задача</div>
|
||||
<div>Язык</div>
|
||||
<div>Вердикт</div>
|
||||
<div>Время</div>
|
||||
<div>Память</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!submissions || submissions.length == 0 ? (
|
||||
<div className="text-liquid-brightmain text-[16px] font-medium text-center">
|
||||
{isFinished
|
||||
? 'Вы ничего не посылали в этот сеанс'
|
||||
: 'Вы еще ничего не отсылали'}
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{submissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
id={v.id ?? 0}
|
||||
datetime={v.solution.time}
|
||||
missionId={v.solution.missionId}
|
||||
language={v.solution.language}
|
||||
verdict={
|
||||
v.solution.testerMessage?.includes(
|
||||
'Compilation failed',
|
||||
)
|
||||
? 'Compilation failed'
|
||||
: v.solution.testerMessage
|
||||
}
|
||||
duration={1000}
|
||||
memory={256 * 1024 * 1024}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
status={
|
||||
v.solution.testerMessage == 'All tests passed'
|
||||
? 'success'
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
<div className="h-[1px] bg-liquid-lighter mt-[50px]"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmissionsBlock;
|
||||
Reference in New Issue
Block a user