Add ettempts in contests
This commit is contained in:
@@ -9,6 +9,7 @@ import { fetchMissionById, setMissionsStatus } from '../redux/slices/missions';
|
|||||||
import Header from '../views/mission/statement/Header';
|
import Header from '../views/mission/statement/Header';
|
||||||
import MissionSubmissions from '../views/mission/statement/MissionSubmissions';
|
import MissionSubmissions from '../views/mission/statement/MissionSubmissions';
|
||||||
import { useQuery } from '../hooks/useQuery';
|
import { useQuery } from '../hooks/useQuery';
|
||||||
|
import { fetchMyAttemptsInContest } from '../redux/slices/contests';
|
||||||
|
|
||||||
const Mission = () => {
|
const Mission = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
@@ -20,6 +21,10 @@ const Mission = () => {
|
|||||||
const missionStatus = useAppSelector(
|
const missionStatus = useAppSelector(
|
||||||
(state) => state.missions.statuses.fetchById,
|
(state) => state.missions.statuses.fetchById,
|
||||||
);
|
);
|
||||||
|
const attempt = useAppSelector(
|
||||||
|
(state) => state.contests.fetchMyAttemptsInContest.attempts[0],
|
||||||
|
);
|
||||||
|
|
||||||
const missionIdNumber = Number(missionId);
|
const missionIdNumber = Number(missionId);
|
||||||
|
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
@@ -44,6 +49,9 @@ const Mission = () => {
|
|||||||
if (pollingRef.current) return;
|
if (pollingRef.current) return;
|
||||||
|
|
||||||
pollingRef.current = setInterval(async () => {
|
pollingRef.current = setInterval(async () => {
|
||||||
|
if (contestId) {
|
||||||
|
dispatch(fetchMyAttemptsInContest(contestId));
|
||||||
|
}
|
||||||
dispatch(fetchMySubmitsByMission(missionIdNumber));
|
dispatch(fetchMySubmitsByMission(missionIdNumber));
|
||||||
|
|
||||||
const hasWaiting = submissionsRef.current.some(
|
const hasWaiting = submissionsRef.current.some(
|
||||||
@@ -63,6 +71,12 @@ const Mission = () => {
|
|||||||
}, 5000); // 10 секунд
|
}, 5000); // 10 секунд
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (contestId) {
|
||||||
|
dispatch(fetchMyAttemptsInContest(contestId));
|
||||||
|
}
|
||||||
|
}, [contestId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(fetchMissionById(missionIdNumber));
|
dispatch(fetchMissionById(missionIdNumber));
|
||||||
dispatch(fetchMySubmitsByMission(missionIdNumber));
|
dispatch(fetchMySubmitsByMission(missionIdNumber));
|
||||||
@@ -194,7 +208,8 @@ const Mission = () => {
|
|||||||
language: language,
|
language: language,
|
||||||
languageVersion: 'latest',
|
languageVersion: 'latest',
|
||||||
sourceCode: code,
|
sourceCode: code,
|
||||||
contestId: contestId,
|
contestAttemptId:
|
||||||
|
attempt?.attemptId,
|
||||||
}),
|
}),
|
||||||
).unwrap();
|
).unwrap();
|
||||||
dispatch(
|
dispatch(
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ export interface Attempt {
|
|||||||
startedAt: string;
|
startedAt: string;
|
||||||
expiresAt: string;
|
expiresAt: string;
|
||||||
finished: boolean;
|
finished: boolean;
|
||||||
|
submissions?: Submission[];
|
||||||
results?: any[];
|
results?: any[];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,12 +204,11 @@ interface ContestsState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
fetchParticipating: {
|
fetchParticipating: {
|
||||||
contests: Contest[],
|
contests: Contest[];
|
||||||
hasNextPage: boolean,
|
hasNextPage: boolean;
|
||||||
status: Status,
|
status: Status;
|
||||||
error?: string,
|
error?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const emptyContest: Contest = {
|
const emptyContest: Contest = {
|
||||||
@@ -253,12 +253,11 @@ const initialState: ContestsState = {
|
|||||||
checkRegistration: { registered: false, status: 'idle' },
|
checkRegistration: { registered: false, status: 'idle' },
|
||||||
fetchUpcomingEligible: { contests: [], status: 'idle' },
|
fetchUpcomingEligible: { contests: [], status: 'idle' },
|
||||||
fetchParticipating: {
|
fetchParticipating: {
|
||||||
contests: [],
|
contests: [],
|
||||||
hasNextPage: false,
|
hasNextPage: false,
|
||||||
status: 'idle',
|
status: 'idle',
|
||||||
error: undefined,
|
error: undefined,
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// =====================
|
// =====================
|
||||||
@@ -277,19 +276,18 @@ export const fetchParticipatingContests = createAsyncThunk(
|
|||||||
const { page = 0, pageSize = 10 } = params;
|
const { page = 0, pageSize = 10 } = params;
|
||||||
const response = await axios.get<ContestsResponse>(
|
const response = await axios.get<ContestsResponse>(
|
||||||
'/contests/participating',
|
'/contests/participating',
|
||||||
{ params: { page, pageSize } }
|
{ params: { page, pageSize } },
|
||||||
);
|
);
|
||||||
return response.data;
|
return response.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
return rejectWithValue(
|
return rejectWithValue(
|
||||||
err.response?.data?.message || 'Failed to fetch participating contests'
|
err.response?.data?.message ||
|
||||||
|
'Failed to fetch participating contests',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const fetchMySubmissions = createAsyncThunk(
|
export const fetchMySubmissions = createAsyncThunk(
|
||||||
'contests/fetchMySubmissions',
|
'contests/fetchMySubmissions',
|
||||||
async (contestId: number, { rejectWithValue }) => {
|
async (contestId: number, { rejectWithValue }) => {
|
||||||
@@ -928,25 +926,27 @@ const contestsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
builder.addCase(fetchParticipatingContests.pending, (state) => {
|
builder.addCase(fetchParticipatingContests.pending, (state) => {
|
||||||
state.fetchParticipating.status = 'loading';
|
state.fetchParticipating.status = 'loading';
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.addCase(
|
builder.addCase(
|
||||||
fetchParticipatingContests.fulfilled,
|
fetchParticipatingContests.fulfilled,
|
||||||
(state, action: PayloadAction<ContestsResponse>) => {
|
(state, action: PayloadAction<ContestsResponse>) => {
|
||||||
state.fetchParticipating.status = 'successful';
|
state.fetchParticipating.status = 'successful';
|
||||||
state.fetchParticipating.contests = action.payload.contests;
|
state.fetchParticipating.contests = action.payload.contests;
|
||||||
state.fetchParticipating.hasNextPage = action.payload.hasNextPage;
|
state.fetchParticipating.hasNextPage =
|
||||||
}
|
action.payload.hasNextPage;
|
||||||
);
|
},
|
||||||
|
);
|
||||||
builder.addCase(fetchParticipatingContests.rejected, (state, action: any) => {
|
|
||||||
state.fetchParticipating.status = 'failed';
|
|
||||||
state.fetchParticipating.error = action.payload;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
builder.addCase(
|
||||||
|
fetchParticipatingContests.rejected,
|
||||||
|
(state, action: any) => {
|
||||||
|
state.fetchParticipating.status = 'failed';
|
||||||
|
state.fetchParticipating.error = action.payload;
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export interface Submit {
|
|||||||
language: string;
|
language: string;
|
||||||
languageVersion: string;
|
languageVersion: string;
|
||||||
sourceCode: string;
|
sourceCode: string;
|
||||||
contestId?: number;
|
contestAttemptId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Solution {
|
export interface Solution {
|
||||||
|
|||||||
@@ -1,8 +1,7 @@
|
|||||||
import { cn } from '../../../../lib/cn';
|
import { cn } from '../../../../lib/cn';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { Edit } from '../../../../assets/icons/input';
|
import { Edit } from '../../../../assets/icons/input';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../../redux/hooks';
|
import { useAppSelector } from '../../../../redux/hooks';
|
||||||
import { deleteMission } from '../../../../redux/slices/missions';
|
|
||||||
|
|
||||||
export interface MissionItemProps {
|
export interface MissionItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -43,7 +42,6 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
|||||||
setDeleteModalActive,
|
setDeleteModalActive,
|
||||||
}) => {
|
}) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
|
||||||
const difficultyItems = ['Easy', 'Medium', 'Hard'];
|
const difficultyItems = ['Easy', 'Medium', 'Hard'];
|
||||||
const difficultyString =
|
const difficultyString =
|
||||||
difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)];
|
difficultyItems[Math.min(Math.max(0, difficulty - 1), 2)];
|
||||||
|
|||||||
@@ -8,8 +8,6 @@ import { loginUser } from '../../../redux/slices/auth';
|
|||||||
// import { cn } from "../../../lib/cn";
|
// import { cn } from "../../../lib/cn";
|
||||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||||
import { Balloon } from '../../../assets/icons/auth';
|
import { Balloon } from '../../../assets/icons/auth';
|
||||||
import { SecondaryButton } from '../../../components/button/SecondaryButton';
|
|
||||||
import { googleLogo } from '../../../assets/icons/input';
|
|
||||||
|
|
||||||
const Login = () => {
|
const Login = () => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import { registerUser } from '../../../redux/slices/auth';
|
|||||||
import { setMenuActivePage } from '../../../redux/slices/store';
|
import { setMenuActivePage } from '../../../redux/slices/store';
|
||||||
import { Balloon } from '../../../assets/icons/auth';
|
import { Balloon } from '../../../assets/icons/auth';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { SecondaryButton } from '../../../components/button/SecondaryButton';
|
|
||||||
import { Checkbox } from '../../../components/checkbox/Checkbox';
|
import { Checkbox } from '../../../components/checkbox/Checkbox';
|
||||||
import { googleLogo } from '../../../assets/icons/input';
|
|
||||||
|
|
||||||
function isValidEmail(email: string): boolean {
|
function isValidEmail(email: string): boolean {
|
||||||
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
const pattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||||
|
|||||||
@@ -2,8 +2,6 @@ import { FC, useEffect, useState } from 'react';
|
|||||||
import MissionItem from './MissionItem';
|
import MissionItem from './MissionItem';
|
||||||
import {
|
import {
|
||||||
Contest,
|
Contest,
|
||||||
fetchContestById,
|
|
||||||
fetchContests,
|
|
||||||
fetchMyAttemptsInContest,
|
fetchMyAttemptsInContest,
|
||||||
fetchMySubmissions,
|
fetchMySubmissions,
|
||||||
setContestStatus,
|
setContestStatus,
|
||||||
@@ -24,52 +22,54 @@ interface ContestMissionsProps {
|
|||||||
contest?: Contest;
|
contest?: Contest;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { submissions, status } = useAppSelector(
|
const { status } = useAppSelector(
|
||||||
(state) => state.contests.fetchMySubmissions,
|
(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 [attemptsStarted, setAttemptsStarted] = useState<boolean>(false);
|
||||||
|
|
||||||
|
|
||||||
const [time, setTime] = useState(0);
|
const [time, setTime] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
||||||
const calc = (time: string) => {
|
const calc = (time: string) => {
|
||||||
return time != "" && new Date() <= new Date(time);
|
return time != '' && new Date() <= new Date(time);
|
||||||
}
|
};
|
||||||
if (attempts.length && calc(attempts[0].expiresAt)){
|
if (attempts.length && calc(attempts[0].expiresAt)) {
|
||||||
|
setAttemptsStarted(true);
|
||||||
setAttemptsStarted(true);
|
|
||||||
|
|
||||||
const diffMs = new Date(attempts[0].expiresAt).getTime() - new Date().getTime();
|
|
||||||
|
|
||||||
const seconds = Math.floor((diffMs / 1000));
|
|
||||||
|
|
||||||
setTime(seconds)
|
const diffMs =
|
||||||
|
new Date(attempts[0].expiresAt).getTime() -
|
||||||
|
new Date().getTime();
|
||||||
|
|
||||||
|
const seconds = Math.floor(diffMs / 1000);
|
||||||
|
|
||||||
|
setTime(seconds);
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
setTime((t) => {
|
setTime((t) => {
|
||||||
if (t <= 1) {
|
if (t <= 1) {
|
||||||
clearInterval(interval); // остановка таймера
|
clearInterval(interval); // остановка таймера
|
||||||
setAttemptsStarted(false); // можно закрыть попытку или уведомить пользователя
|
setAttemptsStarted(false); // можно закрыть попытку или уведомить пользователя
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return t - 1;
|
return t - 1;
|
||||||
});
|
});
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
}
|
} else setAttemptsStarted(false);
|
||||||
else
|
}, [attempts]);
|
||||||
setAttemptsStarted(false);
|
|
||||||
}, [attempts])
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (contest) dispatch(fetchMySubmissions(contest.id));
|
if (contest) dispatch(fetchMySubmissions(contest.id));
|
||||||
@@ -88,7 +88,7 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
||||||
submissions.some(
|
submissions?.some(
|
||||||
(s) =>
|
(s) =>
|
||||||
s.solution.missionId === mission.id &&
|
s.solution.missionId === mission.id &&
|
||||||
s.solution.status === 'Accepted: All tests passed',
|
s.solution.status === 'Accepted: All tests passed',
|
||||||
@@ -97,11 +97,9 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
|||||||
|
|
||||||
const totalCount = contest.missions?.length ?? 0;
|
const totalCount = contest.missions?.length ?? 0;
|
||||||
|
|
||||||
|
// форматирование: mm:ss
|
||||||
// форматирование: mm:ss
|
const minutes = String(Math.floor(time / 60)).padStart(2, '0');
|
||||||
const minutes = String(Math.floor(time / 60)).padStart(2, "0");
|
const seconds = String(time % 60).padStart(2, '0');
|
||||||
const seconds = String(time % 60).padStart(2, "0");
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] gap-[20px]">
|
<div className=" h-screen grid grid-rows-[74px,40px,1fr] p-[20px] gap-[20px]">
|
||||||
@@ -123,43 +121,60 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className="text-liquid-light font-bold text-[18px]">
|
<div className="text-liquid-light font-bold text-[18px]">
|
||||||
{attemptsStarted
|
{attemptsStarted
|
||||||
? `${minutes}:${seconds}`
|
? `${minutes}:${seconds}`
|
||||||
: `Длительность попытки: ${contest.attemptDurationMinutes ?? 0} минут`}
|
: `Длительность попытки: ${
|
||||||
|
contest.attemptDurationMinutes ?? 0
|
||||||
|
} минут. Осталось попыток ${
|
||||||
|
(contest.maxAttempts ?? 0) -
|
||||||
|
(attempts?.length ?? 0)
|
||||||
|
}/${contest.maxAttempts ?? 0}`}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||||
<div className='flex gap-[20px]'>
|
<div className="flex gap-[20px]">
|
||||||
{ attempts.length == 0 || !attemptsStarted ? <PrimaryButton
|
{attempts.length == 0 || !attemptsStarted ? (
|
||||||
onClick={() => {
|
<PrimaryButton
|
||||||
dispatch(startContestAttempt(contest.id)).unwrap().then(() =>
|
onClick={() => {
|
||||||
{
|
dispatch(startContestAttempt(contest.id))
|
||||||
dispatch(fetchMyAttemptsInContest(contest.id));
|
.unwrap()
|
||||||
}
|
.then(() => {
|
||||||
);
|
dispatch(
|
||||||
}}
|
fetchMyAttemptsInContest(
|
||||||
text="Начать попытку"
|
contest.id,
|
||||||
/> : <></>
|
),
|
||||||
} <PrimaryButton
|
);
|
||||||
onClick={() => {
|
});
|
||||||
navigate(`/contest/${contest.id}/submissions`);
|
}}
|
||||||
}}
|
text="Начать попытку"
|
||||||
text="Мои посылки"
|
disabled={
|
||||||
/></div>
|
(contest.maxAttempts ?? 0) -
|
||||||
|
(attempts?.length ?? 0) <=
|
||||||
|
0
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}{' '}
|
||||||
|
<PrimaryButton
|
||||||
|
onClick={() => {
|
||||||
|
navigate(`/contest/${contest.id}/submissions`);
|
||||||
|
}}
|
||||||
|
text="Мои посылки"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
|
<div className="h-full min-h-0 overflow-y-scroll medium-scrollbar flex flex-col gap-[20px]">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
{(contest.missions ?? []).map((v, i) => {
|
{(contest.missions ?? []).map((v, i) => {
|
||||||
const missionSubmissions = submissions.filter(
|
const missionSubmissions = submissions?.filter(
|
||||||
(s) => s.solution.missionId === v.id,
|
(s) => s.solution.missionId === v.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
const hasSuccess = missionSubmissions.some(
|
const hasSuccess = missionSubmissions?.some(
|
||||||
(s) =>
|
(s) =>
|
||||||
s.solution.status ==
|
s.solution.status ==
|
||||||
'Accepted: All tests passed',
|
'Accepted: All tests passed',
|
||||||
@@ -167,7 +182,8 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
|||||||
|
|
||||||
const status = hasSuccess
|
const status = hasSuccess
|
||||||
? 'success'
|
? 'success'
|
||||||
: missionSubmissions.length > 0
|
: missionSubmissions?.length &&
|
||||||
|
missionSubmissions.length > 0
|
||||||
? 'error'
|
? 'error'
|
||||||
: undefined;
|
: undefined;
|
||||||
|
|
||||||
|
|||||||
@@ -1,130 +1,81 @@
|
|||||||
import SubmissionItem from "./SubmissionItem";
|
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||||
import { useAppDispatch, useAppSelector } from "../../../redux/hooks";
|
import { FC, useEffect } from 'react';
|
||||||
import { FC, useEffect } from "react";
|
import { Contest, fetchMySubmissions } from '../../../redux/slices/contests';
|
||||||
import {
|
import { arrowLeft } from '../../../assets/icons/header';
|
||||||
Contest,
|
import { useNavigate } from 'react-router-dom';
|
||||||
fetchMySubmissions,
|
import SubmissionsBlock from './SubmissionsBlock';
|
||||||
setContestStatus,
|
|
||||||
} from "../../../redux/slices/contests";
|
|
||||||
import { arrowLeft } from "../../../assets/icons/header";
|
|
||||||
import { useNavigate } from "react-router-dom";
|
|
||||||
|
|
||||||
export interface Mission {
|
export interface Mission {
|
||||||
id: number;
|
id: number;
|
||||||
authorId: number;
|
authorId: number;
|
||||||
name: string;
|
name: string;
|
||||||
difficulty: "Easy" | "Medium" | "Hard";
|
difficulty: 'Easy' | 'Medium' | 'Hard';
|
||||||
tags: string[];
|
tags: string[];
|
||||||
timeLimit: number;
|
timeLimit: number;
|
||||||
memoryLimit: number;
|
memoryLimit: number;
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
updatedAt: string;
|
updatedAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SubmissionsProps {
|
interface SubmissionsProps {
|
||||||
contest: Contest;
|
contest: Contest;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Submissions: FC<SubmissionsProps> = ({ contest }) => {
|
const Submissions: FC<SubmissionsProps> = ({ contest }) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const { submissions, status } = useAppSelector(
|
const attempts = useAppSelector(
|
||||||
(state) => state.contests.fetchMySubmissions
|
(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(() => {
|
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
||||||
if (contest && contest.id) dispatch(fetchMySubmissions(contest.id));
|
submissions?.some(
|
||||||
}, [contest]);
|
(s) =>
|
||||||
|
s.solution.missionId === mission.id &&
|
||||||
|
s.solution.status === 'Accepted: All tests passed',
|
||||||
|
),
|
||||||
|
).length;
|
||||||
|
|
||||||
useEffect(() => {
|
const totalCount = contest.missions?.length ?? 0;
|
||||||
if (status == "successful") {
|
|
||||||
dispatch(setContestStatus({ key: "fetchMySubmissions", status: "idle" }));
|
|
||||||
}
|
|
||||||
}, [status]);
|
|
||||||
|
|
||||||
const checkStatus = (status: string) => {
|
return (
|
||||||
if (status == "IncorrectAnswer") return "wronganswer";
|
<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]">
|
||||||
if (status == "TimeLimitError") return "timelimit";
|
<div className="">
|
||||||
return undefined;
|
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
|
||||||
};
|
{contest.name}
|
||||||
|
</div>
|
||||||
const solvedCount = (contest.missions ?? []).filter((mission) =>
|
<div className="flex justify-between h-[24px] items-center gap-[10px]">
|
||||||
submissions.some(
|
<div className="flex items-center">
|
||||||
(s) =>
|
<img
|
||||||
s.solution.missionId === mission.id &&
|
src={arrowLeft}
|
||||||
s.solution.status === "Accepted: All tests passed"
|
className="cursor-pointer"
|
||||||
)
|
onClick={() => {
|
||||||
).length;
|
navigate(`/contest/${contest.id}`);
|
||||||
|
}}
|
||||||
const totalCount = contest.missions?.length ?? 0;
|
/>
|
||||||
|
<span className="text-liquid-light font-bold text-[18px]">
|
||||||
return (
|
Контест #{contest.id}
|
||||||
<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]">
|
</span>
|
||||||
<div className="">
|
</div>
|
||||||
<div className="h-[50px] text-[40px] text-liquid-white font-bold">
|
<div className="text-liquid-white text-[16px] font-bold">{`${solvedCount}/${totalCount} Решено`}</div>
|
||||||
{contest.name}
|
</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>
|
||||||
<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;
|
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;
|
||||||
@@ -3,12 +3,12 @@ import { Account } from '../../../assets/icons/auth';
|
|||||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||||
import { ReverseButton } from '../../../components/button/ReverseButton';
|
import { ReverseButton } from '../../../components/button/ReverseButton';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import { toastSuccess, toastWarning } from '../../../lib/toastNotification';
|
import { toastWarning } from '../../../lib/toastNotification';
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||||
import { addOrUpdateContestMember } from '../../../redux/slices/contests';
|
import { addOrUpdateContestMember } from '../../../redux/slices/contests';
|
||||||
|
|
||||||
export type Role = "None" | "Participant" | "Organizer";
|
export type Role = 'None' | 'Participant' | 'Organizer';
|
||||||
|
|
||||||
export interface ContestItemProps {
|
export interface ContestItemProps {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -67,31 +67,29 @@ const ContestItem: React.FC<ContestItemProps> = ({
|
|||||||
|
|
||||||
const waitTime = new Date(startAt).getTime() - now.getTime();
|
const waitTime = new Date(startAt).getTime() - now.getTime();
|
||||||
|
|
||||||
const [myRole, setMyRole] = useState<Role>("None");
|
const [myRole, setMyRole] = useState<Role>('None');
|
||||||
|
|
||||||
const userId = useAppSelector((state) => state.auth.id);
|
const userId = useAppSelector((state) => state.auth.id);
|
||||||
const {contests: contestsRegistered} = useAppSelector((state) => state.contests.fetchParticipating);
|
const { contests: contestsRegistered } = useAppSelector(
|
||||||
const {contests: contestsMy} = useAppSelector((state) => state.contests.fetchMyContests);
|
(state) => state.contests.fetchParticipating,
|
||||||
|
);
|
||||||
|
const { contests: contestsMy } = useAppSelector(
|
||||||
|
(state) => state.contests.fetchMyContests,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!contestsRegistered || contestsRegistered.length === 0) {
|
if (!contestsRegistered || contestsRegistered.length === 0) {
|
||||||
setMyRole("None");
|
setMyRole('None');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const reg = contestsRegistered.find(c => c.id === id);
|
|
||||||
const my = contestsMy.find(c => c.id === id);
|
|
||||||
|
|
||||||
if (my)
|
|
||||||
setMyRole("Organizer");
|
|
||||||
else if (reg)
|
|
||||||
setMyRole("Participant");
|
|
||||||
else
|
|
||||||
setMyRole("None");
|
|
||||||
}, [contestsRegistered])
|
|
||||||
|
|
||||||
|
const reg = contestsRegistered.find((c) => c.id === id);
|
||||||
|
const my = contestsMy.find((c) => c.id === id);
|
||||||
|
|
||||||
|
if (my) setMyRole('Organizer');
|
||||||
|
else if (reg) setMyRole('Participant');
|
||||||
|
else setMyRole('None');
|
||||||
|
}, [contestsRegistered]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -104,9 +102,9 @@ const ContestItem: React.FC<ContestItemProps> = ({
|
|||||||
: ' bg-liquid-background',
|
: ' bg-liquid-background',
|
||||||
)}
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (myRole == 'None'){
|
if (myRole == 'None') {
|
||||||
toastWarning("Зарегистрируйтесь на контест");
|
toastWarning('Зарегистрируйтесь на контест');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
navigate(`/contest/${id}`);
|
navigate(`/contest/${id}`);
|
||||||
}}
|
}}
|
||||||
@@ -133,15 +131,30 @@ const ContestItem: React.FC<ContestItemProps> = ({
|
|||||||
{myRole == 'None' ? (
|
{myRole == 'None' ? (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
<PrimaryButton onClick={() => {
|
<PrimaryButton
|
||||||
dispatch(addOrUpdateContestMember({contestId: id, member: {userId: Number(userId), role:"Participant"}}))
|
onClick={() => {
|
||||||
}} text="Регистрация" />
|
dispatch(
|
||||||
|
addOrUpdateContestMember({
|
||||||
|
contestId: id,
|
||||||
|
member: {
|
||||||
|
userId: Number(userId),
|
||||||
|
role: 'Participant',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
text="Регистрация"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{' '}
|
{' '}
|
||||||
<ReverseButton onClick={() => {
|
<ReverseButton
|
||||||
navigate(`/contest/${id}`);}} text="Войти" />
|
onClick={() => {
|
||||||
|
navigate(`/contest/${id}`);
|
||||||
|
}}
|
||||||
|
text="Войти"
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { cn } from '../../../lib/cn';
|
|||||||
import { ChevroneDown } from '../../../assets/icons/groups';
|
import { ChevroneDown } from '../../../assets/icons/groups';
|
||||||
import ContestItem from './ContestItem';
|
import ContestItem from './ContestItem';
|
||||||
import { Contest } from '../../../redux/slices/contests';
|
import { Contest } from '../../../redux/slices/contests';
|
||||||
import { useAppSelector } from '../../../redux/hooks';
|
|
||||||
|
|
||||||
interface ContestsBlockProps {
|
interface ContestsBlockProps {
|
||||||
contests: Contest[];
|
contests: Contest[];
|
||||||
@@ -58,8 +57,12 @@ const ContestsBlock: FC<ContestsBlockProps> = ({
|
|||||||
name={v.name}
|
name={v.name}
|
||||||
startAt={v.startsAt ?? new Date().toString()}
|
startAt={v.startsAt ?? new Date().toString()}
|
||||||
duration={
|
duration={
|
||||||
new Date(v.endsAt ?? new Date().toString()).getTime() -
|
new Date(
|
||||||
new Date(v.startsAt ?? new Date().toString()).getTime()
|
v.endsAt ?? new Date().toString(),
|
||||||
|
).getTime() -
|
||||||
|
new Date(
|
||||||
|
v.startsAt ?? new Date().toString(),
|
||||||
|
).getTime()
|
||||||
}
|
}
|
||||||
members={v.members?.length ?? 0}
|
members={v.members?.length ?? 0}
|
||||||
type={i % 2 ? 'second' : 'first'}
|
type={i % 2 ? 'second' : 'first'}
|
||||||
|
|||||||
@@ -1,22 +1,6 @@
|
|||||||
import {
|
|
||||||
FilterDropDown,
|
|
||||||
FilterItem,
|
|
||||||
} from '../../../components/drop-down-list/Filter';
|
|
||||||
import { SorterDropDown } from '../../../components/drop-down-list/Sorter';
|
|
||||||
import { SearchInput } from '../../../components/input/SearchInput';
|
import { SearchInput } from '../../../components/input/SearchInput';
|
||||||
|
|
||||||
const Filters = () => {
|
const Filters = () => {
|
||||||
const items: FilterItem[] = [
|
|
||||||
{ text: 'React', value: 'react' },
|
|
||||||
{ text: 'Vue', value: 'vue' },
|
|
||||||
{ text: 'Angular', value: 'angular' },
|
|
||||||
{ text: 'Svelte', value: 'svelte' },
|
|
||||||
{ text: 'Next.js', value: 'next' },
|
|
||||||
{ text: 'Nuxt', value: 'nuxt' },
|
|
||||||
{ text: 'Solid', value: 'solid' },
|
|
||||||
{ text: 'Qwik', value: 'qwik' },
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
<div className=" h-[50px] mb-[20px] flex gap-[20px] items-center">
|
||||||
<SearchInput onChange={() => {}} placeholder="Поиск группы" />
|
<SearchInput onChange={() => {}} placeholder="Поиск группы" />
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import React from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
chevroneLeft,
|
chevroneLeft,
|
||||||
chevroneRight,
|
chevroneRight,
|
||||||
@@ -6,6 +6,9 @@ import {
|
|||||||
} from '../../../assets/icons/header';
|
} from '../../../assets/icons/header';
|
||||||
import { Logo } from '../../../assets/logos';
|
import { Logo } from '../../../assets/logos';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||||
|
import { useQuery } from '../../../hooks/useQuery';
|
||||||
|
import { fetchMyAttemptsInContest } from '../../../redux/slices/contests';
|
||||||
|
|
||||||
interface HeaderProps {
|
interface HeaderProps {
|
||||||
missionId: number;
|
missionId: number;
|
||||||
@@ -14,6 +17,57 @@ interface HeaderProps {
|
|||||||
|
|
||||||
const Header: React.FC<HeaderProps> = ({ missionId, back }) => {
|
const Header: React.FC<HeaderProps> = ({ missionId, back }) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
|
||||||
|
const query = useQuery();
|
||||||
|
const contestId = Number(query.get('contestId') ?? undefined);
|
||||||
|
|
||||||
|
const attempt = useAppSelector(
|
||||||
|
(state) => state.contests.fetchMyAttemptsInContest.attempts[0],
|
||||||
|
);
|
||||||
|
|
||||||
|
const [time, setTime] = useState(0);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!contestId) return;
|
||||||
|
const calc = (time: string) => {
|
||||||
|
return time != '' && new Date() <= new Date(time);
|
||||||
|
};
|
||||||
|
if (attempt) {
|
||||||
|
if (!calc(attempt.expiresAt)) {
|
||||||
|
navigate('/home/contests');
|
||||||
|
}
|
||||||
|
const diffMs =
|
||||||
|
new Date(attempt.expiresAt).getTime() - new Date().getTime();
|
||||||
|
|
||||||
|
const seconds = Math.floor(diffMs / 1000);
|
||||||
|
|
||||||
|
setTime(seconds);
|
||||||
|
|
||||||
|
const interval = setInterval(() => {
|
||||||
|
setTime((t) => {
|
||||||
|
if (t <= 1) {
|
||||||
|
clearInterval(interval);
|
||||||
|
navigate('/home/contests');
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return t - 1;
|
||||||
|
});
|
||||||
|
}, 1000);
|
||||||
|
|
||||||
|
return () => clearInterval(interval);
|
||||||
|
}
|
||||||
|
}, [attempt]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (contestId) {
|
||||||
|
dispatch(fetchMyAttemptsInContest(contestId));
|
||||||
|
}
|
||||||
|
}, [contestId]);
|
||||||
|
|
||||||
|
const minutes = String(Math.floor(time / 60)).padStart(2, '0');
|
||||||
|
const seconds = String(time % 60).padStart(2, '0');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="w-full h-[60px] flex items-center px-4 gap-[20px]">
|
<header className="w-full h-[60px] flex items-center px-4 gap-[20px]">
|
||||||
<img
|
<img
|
||||||
@@ -59,6 +113,9 @@ const Header: React.FC<HeaderProps> = ({ missionId, back }) => {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{!!contestId && !!attempt && (
|
||||||
|
<div className="">{`${minutes}:${seconds}`}</div>
|
||||||
|
)}
|
||||||
</header>
|
</header>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -19,9 +19,16 @@ interface MissionSubmissionsProps {
|
|||||||
contestId?: number;
|
contestId?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId, contestId }) => {
|
const MissionSubmissions: FC<MissionSubmissionsProps> = ({
|
||||||
|
missionId,
|
||||||
|
contestId,
|
||||||
|
}) => {
|
||||||
const submissions = useAppSelector(
|
const submissions = useAppSelector(
|
||||||
(state) => state.submin.submitsById[missionId] || []
|
(state) => state.submin.submitsById[missionId] || [],
|
||||||
|
);
|
||||||
|
|
||||||
|
const attempt = useAppSelector(
|
||||||
|
(state) => state.contests.fetchMyAttemptsInContest.attempts[0],
|
||||||
);
|
);
|
||||||
|
|
||||||
const checkStatus = (status: string) => {
|
const checkStatus = (status: string) => {
|
||||||
@@ -32,7 +39,9 @@ const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId, contestId
|
|||||||
|
|
||||||
// Если contestId передан, фильтруем по нему, иначе показываем все
|
// Если contestId передан, фильтруем по нему, иначе показываем все
|
||||||
const filteredSubmissions = contestId
|
const filteredSubmissions = contestId
|
||||||
? submissions.filter((v) => v.contestId === contestId)
|
? attempt?.submissions?.filter(
|
||||||
|
(v) => v.solution.missionId == missionId,
|
||||||
|
) ?? []
|
||||||
: submissions;
|
: submissions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
Reference in New Issue
Block a user