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