contests
This commit is contained in:
@@ -1,17 +1,64 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useAppDispatch } from '../../../../redux/hooks';
|
||||
import { setMenuActiveProfilePage } from '../../../../redux/slices/store';
|
||||
import { FC, useEffect } from "react";
|
||||
import { useAppDispatch } from "../../../../redux/hooks";
|
||||
import { setMenuActiveProfilePage } from "../../../../redux/slices/store";
|
||||
import { cn } from "../../../../lib/cn";
|
||||
|
||||
|
||||
interface ItemProps {
|
||||
count: number;
|
||||
totalCount: number;
|
||||
title: string;
|
||||
color?: "default" | "red" | "green" | "orange";
|
||||
}
|
||||
|
||||
const Item: FC<ItemProps> = ({count, totalCount, title, color = "default"}) => {
|
||||
|
||||
return <div className={cn("flex flex-row rounded-full bg-liquid-lighter px-[16px] py-[8px] gap-[10px] text-[14px]",
|
||||
color == "default" && "text-liquid-light",
|
||||
color == "red" && "text-liquid-red",
|
||||
color == "green" && "text-liquid-green",
|
||||
color == "orange" && "text-liquid-orange",
|
||||
)}>
|
||||
<div>{count}/{totalCount}</div>
|
||||
<div>{title}</div>
|
||||
</div>
|
||||
};
|
||||
|
||||
const MissionsBlock = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActiveProfilePage('missions'));
|
||||
dispatch(setMenuActiveProfilePage("missions"));
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="h-full w-full relative flex items-center justify-center text-[60px] font-bold">
|
||||
Пока пусто :(
|
||||
<div className="h-full w-full relative overflow-y-scroll medium-scrollbar">
|
||||
<div className="w-full flex flex-col">
|
||||
<div className="p-[20px] flex flex-col gap-[20px]">
|
||||
<div className="text-[24px] font-bold text-liquid-white">Решенные задачи</div>
|
||||
<div className="flex flex-row justify-between items-start">
|
||||
|
||||
<div className="flex gap-[10px]">
|
||||
<Item count={14} totalCount={123} title="Задачи"/>
|
||||
</div>
|
||||
<div className="flex gap-[20px]">
|
||||
<Item count={14} totalCount={123} title="Easy" color="green"/>
|
||||
<Item count={14} totalCount={123} title="Medium" color="orange"/>
|
||||
<Item count={14} totalCount={123} title="Hard" color="red"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div className="text-[24px] font-bold text-liquid-white">Компетенции</div>
|
||||
|
||||
<div className="flex flex-wrap gap-[10px]">
|
||||
<Item count={14} totalCount={123} title="Массивы"/>
|
||||
<Item count={14} totalCount={123} title="Списки"/>
|
||||
<Item count={14} totalCount={123} title="Стэк"/>
|
||||
</div>
|
||||
</div>
|
||||
<div>Недавиние задачи</div>
|
||||
<div>Мои задачи</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -24,6 +24,7 @@ const Contest = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
const contest = useAppSelector((state) => state.contests.fetchContestById.contest);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActivePage('contest'));
|
||||
}, []);
|
||||
@@ -33,13 +34,12 @@ const Contest = () => {
|
||||
}, [contestIdNumber]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PrimaryButton onClick={() => {navigate(`/contest/${contestIdNumber}/submissions`)}} text='Мои посылки' />
|
||||
<div className='w-full h-full'>
|
||||
<Routes>
|
||||
|
||||
<Route
|
||||
path="submissions"
|
||||
element={<Submissions contestId={contestIdNumber} />}
|
||||
element={<Submissions contest={contest}/>}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
|
||||
@@ -10,7 +10,7 @@ export interface MissionItemProps {
|
||||
timeLimit?: number;
|
||||
memoryLimit?: number;
|
||||
type?: 'first' | 'second';
|
||||
status?: 'empty' | 'success' | 'error';
|
||||
status?: 'success' | 'error';
|
||||
}
|
||||
|
||||
export function formatMilliseconds(ms: number): string {
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { FC } from 'react';
|
||||
import MissionItem from './MissionItem';
|
||||
import { Contest } from '../../../redux/slices/contests';
|
||||
import { FC, useEffect } from "react";
|
||||
import MissionItem from "./MissionItem";
|
||||
import {
|
||||
Contest,
|
||||
fetchMySubmissions,
|
||||
setContestStatus,
|
||||
} from "../../../redux/slices/contests";
|
||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
||||
import { PrimaryButton } from "../../../components/button/PrimaryButton";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { arrowLeft } from "../../../assets/icons/header";
|
||||
|
||||
export interface Article {
|
||||
id: number;
|
||||
@@ -13,19 +21,88 @@ interface ContestMissionsProps {
|
||||
}
|
||||
|
||||
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useAppDispatch();
|
||||
const { submissions, status } = useAppSelector(
|
||||
(state) => state.contests.fetchMySubmissions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (contest) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status == "successful") {
|
||||
dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" }));
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
if (!contest) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
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-screen grid grid-rows-[74px,1fr] p-[20px] gap-[20px]">
|
||||
<div className=""></div>
|
||||
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
|
||||
<div className="h-[40px] w-ufll ">
|
||||
{contest?.name} {contest.id}
|
||||
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] 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(`/home/contests`);
|
||||
}}
|
||||
/>
|
||||
<span className="text-liquid-light font-bold text-[18px]">
|
||||
Контест #{contest.id}
|
||||
</span>
|
||||
</div>
|
||||
<div>{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
|
||||
onClick={() => {
|
||||
navigate(`/contest/${contest.id}/submissions`);
|
||||
}}
|
||||
text="Мои посылки"
|
||||
/>
|
||||
</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) => (
|
||||
{(contest.missions ?? []).map((v, i) => {
|
||||
const missionSubmissions = submissions.filter(
|
||||
(s) => s.solution.missionId === v.id
|
||||
);
|
||||
|
||||
const hasSuccess = missionSubmissions.some(
|
||||
(s) => s.solution.status == "Accepted: All tests passed"
|
||||
);
|
||||
|
||||
console.log(missionSubmissions);
|
||||
|
||||
const status = hasSuccess
|
||||
? "success"
|
||||
: missionSubmissions.length > 0
|
||||
? "error"
|
||||
: undefined;
|
||||
|
||||
return (
|
||||
<MissionItem
|
||||
contestId={contest.id}
|
||||
key={i}
|
||||
@@ -33,9 +110,11 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
name={v.name}
|
||||
timeLimit={v.timeLimitMilliseconds}
|
||||
memoryLimit={v.memoryLimitBytes}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
status={status}
|
||||
type={i % 2 ? "second" : "first"}
|
||||
/>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -4,9 +4,12 @@ import { cn } from '../../../lib/cn';
|
||||
|
||||
export interface SubmissionItemProps {
|
||||
id: number;
|
||||
datetime: string;
|
||||
missionId: number;
|
||||
language: string;
|
||||
time: string;
|
||||
verdict: string;
|
||||
duration: number;
|
||||
memory: number;
|
||||
type: 'first' | 'second';
|
||||
status?: 'success' | 'wronganswer' | 'timelimit';
|
||||
}
|
||||
@@ -37,20 +40,23 @@ function formatDate(dateString: string): string {
|
||||
|
||||
const SubmissionItem: React.FC<SubmissionItemProps> = ({
|
||||
id,
|
||||
datetime,
|
||||
missionId,
|
||||
language,
|
||||
time,
|
||||
verdict,
|
||||
duration,
|
||||
memory,
|
||||
type,
|
||||
status,
|
||||
status
|
||||
}) => {
|
||||
// const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
' w-full relative rounded-[10px] text-liquid-white',
|
||||
' w-full relative rounded-[10px] text-liquid-white text-center text-bold text-[16px] py-[8px]',
|
||||
type == 'first' ? 'bg-liquid-lighter' : 'bg-liquid-background',
|
||||
'grid grid-cols-[80px,1fr,1fr,2fr] grid-flow-col gap-[20px] px-[20px] box-border items-center',
|
||||
'grid grid-cols-7 grid-flow-col gap-[20px] px-[20px] box-border items-center',
|
||||
status == 'wronganswer' &&
|
||||
'border-l-[11px] border-l-liquid-red pl-[9px]',
|
||||
status == 'timelimit' &&
|
||||
@@ -63,8 +69,9 @@ const SubmissionItem: React.FC<SubmissionItemProps> = ({
|
||||
>
|
||||
<div className="text-[18px] font-bold">#{id}</div>
|
||||
<div className="text-[18px] font-bold text-center">
|
||||
{formatDate(time)}
|
||||
{formatDate(datetime)}
|
||||
</div>
|
||||
<div>{missionId} </div>
|
||||
<div className="text-[18px] font-bold text-center">{language}</div>
|
||||
<div
|
||||
className={cn(
|
||||
@@ -76,6 +83,10 @@ const SubmissionItem: React.FC<SubmissionItemProps> = ({
|
||||
>
|
||||
{verdict}
|
||||
</div>
|
||||
<div>{formatMilliseconds(duration)}</div>
|
||||
<div>
|
||||
{formatBytesToMB(memory)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
import SubmissionItem from './SubmissionItem';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { fetchMySubmissions, setContestStatus } from '../../../redux/slices/contests';
|
||||
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";
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: 'Easy' | 'Medium' | 'Hard';
|
||||
difficulty: "Easy" | "Medium" | "Hard";
|
||||
tags: string[];
|
||||
timeLimit: number;
|
||||
memoryLimit: number;
|
||||
@@ -16,56 +22,106 @@ export interface Mission {
|
||||
}
|
||||
|
||||
interface SubmissionsProps {
|
||||
contestId: number;
|
||||
contest: Contest;
|
||||
}
|
||||
|
||||
const Submissions: FC<SubmissionsProps> = ({ contestId }) => {
|
||||
const Submissions: FC<SubmissionsProps> = ({ contest }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const { submissions, status } = useAppSelector(
|
||||
(state) => state.contests.fetchMySubmissions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchMySubmissions(contestId));
|
||||
}, [contestId]);
|
||||
if (contest && contest.id) dispatch(fetchMySubmissions(contest.id));
|
||||
}, [contest]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status == "successful") {
|
||||
dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" }));
|
||||
}
|
||||
}, [status])
|
||||
}, [status]);
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
if (status == 'IncorrectAnswer') return 'wronganswer';
|
||||
if (status == 'TimeLimitError') return 'timelimit';
|
||||
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-full box-border overflow-y-scroll overflow-x-hidden thin-scrollbar pr-[10px]">
|
||||
{submissions &&
|
||||
submissions.map((v, i) => (
|
||||
<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>
|
||||
<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}
|
||||
time={v.solution.time}
|
||||
verdict={
|
||||
v.solution.testerMessage?.includes(
|
||||
'Compilation failed',
|
||||
)
|
||||
? 'Compilation failed'
|
||||
v.solution.testerMessage?.includes("Compilation failed")
|
||||
? "Compilation failed"
|
||||
: v.solution.testerMessage
|
||||
}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
duration={1000}
|
||||
memory={256 * 1024 * 1024}
|
||||
type={i % 2 ? "second" : "first"}
|
||||
status={
|
||||
v.solution.testerMessage == 'All tests passed'
|
||||
? 'success'
|
||||
v.solution.testerMessage == "All tests passed"
|
||||
? "success"
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import SubmissionItem from './SubmissionItem';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { fetchMySubmissions } from '../../../redux/slices/contests';
|
||||
import { useAppSelector } from '../../../redux/hooks';
|
||||
import { FC } from 'react';
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
@@ -21,78 +20,42 @@ interface MissionSubmissionsProps {
|
||||
}
|
||||
|
||||
const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId, contestId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const submissions = useAppSelector(
|
||||
(state) => state.submin.submitsById[missionId],
|
||||
(state) => state.submin.submitsById[missionId] || []
|
||||
);
|
||||
|
||||
|
||||
const {submissions: contestSubmission, status: contestStatus} = useAppSelector((state) => state.contests.fetchMySubmissions);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
if (status == 'IncorrectAnswer') return 'wronganswer';
|
||||
if (status == 'TimeLimitError') return 'timelimit';
|
||||
if (status === 'IncorrectAnswer') return 'wronganswer';
|
||||
if (status === 'TimeLimitError') return 'timelimit';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Если contestId передан, фильтруем по нему, иначе показываем все
|
||||
const filteredSubmissions = contestId
|
||||
? submissions.filter((v) => v.contestId === contestId)
|
||||
: submissions;
|
||||
|
||||
useEffect(() => {
|
||||
if (contestId){
|
||||
dispatch(fetchMySubmissions(contestId));
|
||||
}
|
||||
}, [contestId, missionId])
|
||||
return (
|
||||
<div className="h-full w-full box-border overflow-y-scroll overflow-x-hidden thin-scrollbar pr-[10px]">
|
||||
|
||||
{contestId ?
|
||||
contestSubmission &&
|
||||
contestSubmission.filter(v => v.solution.missionId == missionId).map((v, i) => (
|
||||
{filteredSubmissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
key={v.id}
|
||||
id={v.id}
|
||||
language={v.solution.language}
|
||||
time={v.solution.time}
|
||||
verdict={
|
||||
v.solution.testerMessage?.includes(
|
||||
'Compilation failed',
|
||||
)
|
||||
v.solution.testerMessage?.includes('Compilation failed')
|
||||
? 'Compilation failed'
|
||||
: v.solution.testerMessage
|
||||
}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
status={
|
||||
v.solution.testerMessage == 'All tests passed'
|
||||
v.solution.testerMessage === 'All tests passed'
|
||||
? 'success'
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))
|
||||
:
|
||||
submissions &&
|
||||
submissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
id={v.id}
|
||||
language={v.solution.language}
|
||||
time={v.solution.time}
|
||||
verdict={
|
||||
v.solution.testerMessage?.includes(
|
||||
'Compilation failed',
|
||||
)
|
||||
? 'Compilation failed'
|
||||
: v.solution.testerMessage
|
||||
}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
status={
|
||||
v.solution.testerMessage == 'All tests passed'
|
||||
? 'success'
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))
|
||||
}
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user