contest submisssions
This commit is contained in:
@@ -61,7 +61,6 @@ const ContestEditor = () => {
|
||||
attemptDurationMinutes: 60,
|
||||
maxAttempts: 1,
|
||||
allowEarlyFinish: true,
|
||||
groupIds: [],
|
||||
missionIds: [],
|
||||
articleIds: [],
|
||||
});
|
||||
@@ -70,6 +69,7 @@ const ContestEditor = () => {
|
||||
|
||||
|
||||
const statusDelete = useAppSelector((state) => state.contests.deleteContest.status)
|
||||
const statusUpdate = useAppSelector((state) => state.contests.updateContest.status);
|
||||
|
||||
const { contest: contestById, status: contestByIdstatus } = useAppSelector(
|
||||
(state) => state.contests.fetchContestById,
|
||||
@@ -127,6 +127,14 @@ const ContestEditor = () => {
|
||||
}
|
||||
}, [statusDelete])
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (statusUpdate == "successful"){
|
||||
dispatch(setContestStatus({key: "updateContest", status: "idle"}))
|
||||
navigate('/home/account/contests')
|
||||
}
|
||||
}, [statusUpdate])
|
||||
|
||||
useEffect(() => {
|
||||
if (refactor) {
|
||||
dispatch(fetchContestById(contestId));
|
||||
@@ -138,7 +146,6 @@ const ContestEditor = () => {
|
||||
setContest({
|
||||
...contestById,
|
||||
// groupIds: contestById.groups.map(group => group.groupId),
|
||||
groupIds: [],
|
||||
missionIds: contestById.missions?.map(mission => mission.id),
|
||||
articleIds: contestById.articles?.map(article => article.articleId),
|
||||
visibility: 'Public',
|
||||
|
||||
@@ -20,6 +20,7 @@ const Mission = () => {
|
||||
|
||||
const query = useQuery();
|
||||
const back = query.get('back') ?? undefined;
|
||||
const contestId = Number(query.get('contestId') ?? undefined);
|
||||
|
||||
if (!missionId || isNaN(missionIdNumber)) {
|
||||
if (back) return <Navigate to={back} replace />;
|
||||
@@ -179,13 +180,14 @@ const Mission = () => {
|
||||
<PrimaryButton
|
||||
text="Отправить"
|
||||
onClick={async () => {
|
||||
console.log(contestId);
|
||||
await dispatch(
|
||||
submitMission({
|
||||
missionId: missionIdNumber,
|
||||
language: language,
|
||||
languageVersion: 'latest',
|
||||
sourceCode: code,
|
||||
contestId: null,
|
||||
contestId: contestId,
|
||||
}),
|
||||
).unwrap();
|
||||
dispatch(
|
||||
@@ -198,7 +200,7 @@ const Mission = () => {
|
||||
</div>
|
||||
|
||||
<div className="h-full w-full ">
|
||||
<MissionSubmissions missionId={missionIdNumber} />
|
||||
<MissionSubmissions missionId={missionIdNumber} contestId={contestId} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -5,6 +5,36 @@ import axios from '../../axios';
|
||||
// Типы
|
||||
// =====================
|
||||
|
||||
// =====================
|
||||
// Типы для посылок
|
||||
// =====================
|
||||
|
||||
export interface Solution {
|
||||
id: number;
|
||||
missionId: number;
|
||||
language: string;
|
||||
languageVersion: string;
|
||||
sourceCode: string;
|
||||
status: string;
|
||||
time: string;
|
||||
testerState: string;
|
||||
testerErrorCode: string;
|
||||
testerMessage: string;
|
||||
currentTest: number;
|
||||
amountOfTests: number;
|
||||
}
|
||||
|
||||
export interface Submission {
|
||||
id: number;
|
||||
userId: number;
|
||||
solution: Solution;
|
||||
contestId: number;
|
||||
contestName: string;
|
||||
sourceType: string;
|
||||
}
|
||||
|
||||
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
@@ -38,7 +68,8 @@ export interface Contest {
|
||||
attemptDurationMinutes?: number;
|
||||
maxAttempts?: number;
|
||||
allowEarlyFinish?: boolean;
|
||||
groups?: Group[];
|
||||
groupId?: number;
|
||||
groupName?: string;
|
||||
missions?: Mission[];
|
||||
articles?: any[];
|
||||
members?: Member[];
|
||||
@@ -59,7 +90,8 @@ export interface CreateContestBody {
|
||||
attemptDurationMinutes?: number;
|
||||
maxAttempts?: number;
|
||||
allowEarlyFinish?: boolean;
|
||||
groupIds?: number[];
|
||||
groupId?: number;
|
||||
groupName?: string;
|
||||
missionIds?: number[];
|
||||
articleIds?: number[];
|
||||
}
|
||||
@@ -87,6 +119,12 @@ interface ContestsState {
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
fetchMySubmissions: {
|
||||
submissions: Submission[];
|
||||
status: Status;
|
||||
error?: string;
|
||||
};
|
||||
|
||||
// 🆕 Добавляем updateContest и deleteContest
|
||||
updateContest: {
|
||||
contest: Contest;
|
||||
@@ -129,7 +167,8 @@ const initialState: ContestsState = {
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groups: [],
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
@@ -137,6 +176,12 @@ const initialState: ContestsState = {
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
fetchMySubmissions: {
|
||||
submissions: [],
|
||||
status: 'idle',
|
||||
error: undefined,
|
||||
},
|
||||
|
||||
createContest: {
|
||||
contest: {
|
||||
id: 0,
|
||||
@@ -149,7 +194,8 @@ const initialState: ContestsState = {
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groups: [],
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
@@ -169,7 +215,8 @@ const initialState: ContestsState = {
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groups: [],
|
||||
groupId: undefined,
|
||||
groupName: undefined,
|
||||
missions: [],
|
||||
articles: [],
|
||||
members: [],
|
||||
@@ -198,6 +245,24 @@ const initialState: ContestsState = {
|
||||
// Async Thunks
|
||||
// =====================
|
||||
|
||||
// Мои посылки в контесте
|
||||
export const fetchMySubmissions = createAsyncThunk(
|
||||
'contests/fetchMySubmissions',
|
||||
async (contestId: number, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await axios.get<Submission[]>(
|
||||
`/contests/${contestId}/submissions/my`,
|
||||
);
|
||||
return response.data;
|
||||
} catch (err: any) {
|
||||
return rejectWithValue(
|
||||
err.response?.data?.message || 'Failed to fetch my submissions',
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
// Все контесты
|
||||
export const fetchContests = createAsyncThunk(
|
||||
'contests/fetchAll',
|
||||
@@ -353,6 +418,25 @@ const contestsSlice = createSlice({
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
// 🆕 fetchMySubmissions
|
||||
builder.addCase(fetchMySubmissions.pending, (state) => {
|
||||
state.fetchMySubmissions.status = 'loading';
|
||||
state.fetchMySubmissions.error = undefined;
|
||||
});
|
||||
builder.addCase(
|
||||
fetchMySubmissions.fulfilled,
|
||||
(state, action: PayloadAction<Submission[]>) => {
|
||||
state.fetchMySubmissions.status = 'successful';
|
||||
state.fetchMySubmissions.submissions = action.payload;
|
||||
},
|
||||
);
|
||||
builder.addCase(fetchMySubmissions.rejected, (state, action: any) => {
|
||||
state.fetchMySubmissions.status = 'failed';
|
||||
state.fetchMySubmissions.error = action.payload;
|
||||
});
|
||||
|
||||
|
||||
|
||||
// fetchContests
|
||||
builder.addCase(fetchContests.pending, (state) => {
|
||||
state.fetchContests.status = 'loading';
|
||||
|
||||
@@ -56,6 +56,7 @@ const initialState: SubmitState = {
|
||||
export const submitMission = createAsyncThunk(
|
||||
'submit/submitMission',
|
||||
async (submitData: Submit, { rejectWithValue }) => {
|
||||
console.log(submitData);
|
||||
try {
|
||||
const response = await axios.post('/submits', submitData);
|
||||
return response.data;
|
||||
|
||||
@@ -59,12 +59,12 @@ const ContestsBlock: FC<ContestsBlockProps> = ({
|
||||
key={i}
|
||||
id={v.id}
|
||||
name={v.name}
|
||||
startAt={v.startsAt}
|
||||
startAt={v.startsAt ?? ''}
|
||||
duration={
|
||||
new Date(v.endsAt).getTime() -
|
||||
new Date(v.startsAt).getTime()
|
||||
new Date(v.endsAt ?? '').getTime() -
|
||||
new Date(v.startsAt ?? '').getTime()
|
||||
}
|
||||
members={v.members.length}
|
||||
members={(v.members??[]).length}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
/>
|
||||
) : (
|
||||
@@ -72,13 +72,13 @@ const ContestsBlock: FC<ContestsBlockProps> = ({
|
||||
key={i}
|
||||
id={v.id}
|
||||
name={v.name}
|
||||
startAt={v.startsAt}
|
||||
startAt={v.startsAt ?? ''}
|
||||
statusRegister={'reg'}
|
||||
duration={
|
||||
new Date(v.endsAt).getTime() -
|
||||
new Date(v.startsAt).getTime()
|
||||
new Date(v.endsAt ?? '').getTime() -
|
||||
new Date(v.startsAt ?? '').getTime()
|
||||
}
|
||||
members={v.members.length}
|
||||
members={(v.members??[]).length}
|
||||
type={i % 2 ? 'second' : 'first'}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
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 { Navigate, Route, Routes, useNavigate, useParams } from 'react-router-dom';
|
||||
import { fetchContestById } from '../../../redux/slices/contests';
|
||||
import ContestMissions from './Missions';
|
||||
import { PrimaryButton } from '../../../components/button/PrimaryButton';
|
||||
import Submissions from './Submissions';
|
||||
|
||||
export interface Article {
|
||||
id: number;
|
||||
@@ -12,14 +14,15 @@ export interface Article {
|
||||
}
|
||||
|
||||
const Contest = () => {
|
||||
const navigate = useNavigate();
|
||||
const { contestId } = useParams<{ contestId: string }>();
|
||||
const contestIdNumber =
|
||||
contestId && /^\d+$/.test(contestId) ? parseInt(contestId, 10) : null;
|
||||
if (contestIdNumber === null) {
|
||||
if (!contestIdNumber) {
|
||||
return <Navigate to="/home/contests" replace />;
|
||||
}
|
||||
const dispatch = useAppDispatch();
|
||||
const contest = useAppSelector((state) => state.contests.selectedContest);
|
||||
const contest = useAppSelector((state) => state.contests.fetchContestById.contest);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(setMenuActivePage('contest'));
|
||||
@@ -31,12 +34,19 @@ const Contest = () => {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PrimaryButton onClick={() => {navigate(`/contest/${contestIdNumber}/submissions`)}} text='Мои посылки' />
|
||||
<Routes>
|
||||
|
||||
<Route
|
||||
path="submissions"
|
||||
element={<Submissions contestId={contestIdNumber} />}
|
||||
/>
|
||||
<Route
|
||||
path="*"
|
||||
element={<ContestMissions contest={contest} />}
|
||||
/>
|
||||
</Routes>
|
||||
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useNavigate } from 'react-router-dom';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
|
||||
export interface MissionItemProps {
|
||||
contestId: number;
|
||||
id: number;
|
||||
name: string;
|
||||
timeLimit?: number;
|
||||
@@ -24,6 +25,7 @@ export function formatBytesToMB(bytes: number): string {
|
||||
}
|
||||
|
||||
const MissionItem: React.FC<MissionItemProps> = ({
|
||||
contestId,
|
||||
id,
|
||||
name,
|
||||
timeLimit = 1000,
|
||||
@@ -48,7 +50,7 @@ const MissionItem: React.FC<MissionItemProps> = ({
|
||||
'cursor-pointer brightness-100 hover:brightness-125 transition-all duration-300',
|
||||
)}
|
||||
onClick={() => {
|
||||
navigate(`/mission/${id}?back=${path}`);
|
||||
navigate(`/mission/${id}?back=${path}&contestId=${contestId}`);
|
||||
}}
|
||||
>
|
||||
<div className="text-[18px] font-bold">#{id}</div>
|
||||
|
||||
@@ -9,7 +9,7 @@ export interface Article {
|
||||
}
|
||||
|
||||
interface ContestMissionsProps {
|
||||
contest: Contest | null;
|
||||
contest?: Contest;
|
||||
}
|
||||
|
||||
const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
@@ -25,8 +25,10 @@ const ContestMissions: FC<ContestMissionsProps> = ({ contest }) => {
|
||||
{contest?.name} {contest.id}
|
||||
</div>
|
||||
<div className="w-full">
|
||||
{contest.missions.map((v, i) => (
|
||||
{(contest.missions ?? []).map((v, i) => (
|
||||
<MissionItem
|
||||
contestId={contest.id}
|
||||
key={i}
|
||||
id={v.id}
|
||||
name={v.name}
|
||||
timeLimit={v.timeLimitMilliseconds}
|
||||
|
||||
83
src/views/home/contest/SubmissionItem.tsx
Normal file
83
src/views/home/contest/SubmissionItem.tsx
Normal file
@@ -0,0 +1,83 @@
|
||||
import { cn } from '../../../lib/cn';
|
||||
// import { IconError, IconSuccess } from "../../../assets/icons/missions";
|
||||
// import { useNavigate } from "react-router-dom";
|
||||
|
||||
export interface SubmissionItemProps {
|
||||
id: number;
|
||||
language: string;
|
||||
time: string;
|
||||
verdict: string;
|
||||
type: 'first' | 'second';
|
||||
status?: 'success' | 'wronganswer' | 'timelimit';
|
||||
}
|
||||
|
||||
export function formatMilliseconds(ms: number): string {
|
||||
const rounded = Math.round(ms) / 1000;
|
||||
const formatted = rounded.toString().replace(/\.?0+$/, '');
|
||||
return `${formatted} c`;
|
||||
}
|
||||
|
||||
export function formatBytesToMB(bytes: number): string {
|
||||
const megabytes = Math.floor(bytes / (1024 * 1024));
|
||||
return `${megabytes} МБ`;
|
||||
}
|
||||
|
||||
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}`;
|
||||
}
|
||||
|
||||
const SubmissionItem: React.FC<SubmissionItemProps> = ({
|
||||
id,
|
||||
language,
|
||||
time,
|
||||
verdict,
|
||||
type,
|
||||
status,
|
||||
}) => {
|
||||
// const navigate = useNavigate();
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
' w-full relative rounded-[10px] text-liquid-white',
|
||||
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',
|
||||
status == 'wronganswer' &&
|
||||
'border-l-[11px] border-l-liquid-red pl-[9px]',
|
||||
status == 'timelimit' &&
|
||||
'border-l-[11px] border-l-liquid-orange pl-[9px]',
|
||||
status == 'success' &&
|
||||
'border-l-[11px] border-l-liquid-green pl-[9px]',
|
||||
'cursor-pointer brightness-100 hover:brightness-125 transition-all duration-300',
|
||||
)}
|
||||
onClick={() => {}}
|
||||
>
|
||||
<div className="text-[18px] font-bold">#{id}</div>
|
||||
<div className="text-[18px] font-bold text-center">
|
||||
{formatDate(time)}
|
||||
</div>
|
||||
<div className="text-[18px] font-bold text-center">{language}</div>
|
||||
<div
|
||||
className={cn(
|
||||
'text-[18px] font-bold text-center',
|
||||
status == 'wronganswer' && 'text-liquid-red',
|
||||
status == 'timelimit' && 'text-liquid-orange',
|
||||
status == 'success' && 'text-liquid-green',
|
||||
)}
|
||||
>
|
||||
{verdict}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SubmissionItem;
|
||||
@@ -0,0 +1,73 @@
|
||||
import SubmissionItem from './SubmissionItem';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { fetchMySubmissions, setContestStatus } from '../../../redux/slices/contests';
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
authorId: number;
|
||||
name: string;
|
||||
difficulty: 'Easy' | 'Medium' | 'Hard';
|
||||
tags: string[];
|
||||
timeLimit: number;
|
||||
memoryLimit: number;
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
interface SubmissionsProps {
|
||||
contestId: number;
|
||||
}
|
||||
|
||||
const Submissions: FC<SubmissionsProps> = ({ contestId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {submissions, status} = useAppSelector(
|
||||
(state) => state.contests.fetchMySubmissions
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchMySubmissions(contestId));
|
||||
}, [contestId]);
|
||||
|
||||
useEffect(() => {
|
||||
if (status == "successful"){
|
||||
dispatch(setContestStatus({key:"fetchMySubmissions", status: "idle"}));
|
||||
}
|
||||
}, [status])
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
if (status == 'IncorrectAnswer') return 'wronganswer';
|
||||
if (status == 'TimeLimitError') return 'timelimit';
|
||||
return undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="h-full w-full box-border overflow-y-scroll overflow-x-hidden thin-scrollbar pr-[10px]">
|
||||
{submissions &&
|
||||
submissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
id={v.id??0}
|
||||
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>
|
||||
);
|
||||
};
|
||||
|
||||
export default Submissions;
|
||||
|
||||
@@ -37,7 +37,6 @@ const ModalCreateContest: FC<ModalCreateContestProps> = ({
|
||||
attemptDurationMinutes: 0,
|
||||
maxAttempts: 0,
|
||||
allowEarlyFinish: false,
|
||||
groupIds: [],
|
||||
missionIds: [],
|
||||
articleIds: [],
|
||||
});
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import SubmissionItem from './SubmissionItem';
|
||||
import { useAppSelector } from '../../../redux/hooks';
|
||||
import { useAppDispatch, useAppSelector } from '../../../redux/hooks';
|
||||
import { FC, useEffect } from 'react';
|
||||
import { fetchMySubmissions } from '../../../redux/slices/contests';
|
||||
|
||||
export interface Mission {
|
||||
id: number;
|
||||
@@ -16,13 +17,18 @@ export interface Mission {
|
||||
|
||||
interface MissionSubmissionsProps {
|
||||
missionId: number;
|
||||
contestId?: number;
|
||||
}
|
||||
|
||||
const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId }) => {
|
||||
const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId, contestId }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const submissions = useAppSelector(
|
||||
(state) => state.submin.submitsById[missionId],
|
||||
);
|
||||
|
||||
|
||||
const {submissions: contestSubmission, status: contestStatus} = useAppSelector((state) => state.contests.fetchMySubmissions);
|
||||
|
||||
useEffect(() => {}, []);
|
||||
|
||||
const checkStatus = (status: string) => {
|
||||
@@ -31,9 +37,40 @@ const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId }) => {
|
||||
return undefined;
|
||||
};
|
||||
|
||||
|
||||
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]">
|
||||
{submissions &&
|
||||
|
||||
{contestId ?
|
||||
contestSubmission &&
|
||||
contestSubmission.filter(v => v.solution.missionId == missionId).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)
|
||||
}
|
||||
/>
|
||||
))
|
||||
:
|
||||
submissions &&
|
||||
submissions.map((v, i) => (
|
||||
<SubmissionItem
|
||||
key={i}
|
||||
@@ -54,7 +91,8 @@ const MissionSubmissions: FC<MissionSubmissionsProps> = ({ missionId }) => {
|
||||
: checkStatus(v.solution.testerErrorCode)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
))
|
||||
}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user