diff --git a/src/App.tsx b/src/App.tsx index 474940a..6b269fc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -5,7 +5,7 @@ import { Route, Routes } from "react-router-dom"; // import { Input } from "./components/input/Input"; // import { Switch } from "./components/switch/Switch"; import Home from "./pages/Home"; -import CodeEditor from "./views/mission/codeeditor/CodeEditor"; +// import CodeEditor from "./views/mission/codeeditor/CodeEditor"; import Statement from "./views/mission/statement/Statement"; import MissionStatement from "./views/mission/statement/Mission"; import Mission from "./pages/Mission"; @@ -19,7 +19,7 @@ function App() { } /> } /> }/> - } /> + {/* } /> */} } /> } /> diff --git a/src/pages/Mission.tsx b/src/pages/Mission.tsx index 751f89c..64af650 100644 --- a/src/pages/Mission.tsx +++ b/src/pages/Mission.tsx @@ -1,24 +1,93 @@ import { useParams, Navigate } from 'react-router-dom'; - - +import CodeEditor from '../views/mission/codeeditor/CodeEditor'; +import Statement, { StatementData } from '../views/mission/statement/Statement'; +import { PrimaryButton } from '../components/button/PrimaryButton'; +import { useEffect, useState } from 'react'; +import { useAppDispatch, useAppSelector } from '../redux/hooks'; +import { submitMission } from '../redux/slices/submit'; +import { fetchMissionById } from '../redux/slices/missions'; const Mission = () => { + const dispatch = useAppDispatch(); + // Получаем параметры из URL const { missionId } = useParams<{ missionId: string }>(); + const mission = useAppSelector((state) => state.missions.currentMission); + const missionIdNumber = Number(missionId); + + const [code, setCode] = useState(""); + const [language, setLanguage] = useState(""); // Если missionId нет, редиректим на /home - if (!missionId) { + + // Если missionId нет или не число — редиректим + if (!missionId || isNaN(missionIdNumber)) { return ; } + useEffect(() => { + dispatch(fetchMissionById(missionIdNumber)); + }, []); + +if (!mission || !mission.statements || mission.statements.length === 0) { + return
Загрузка или миссия не найдена...
; +} + + + +const statementRaw = mission.statements[0]; + let statementData: StatementData = { id: mission.id }; + + try { + const statementTexts = JSON.parse(statementRaw.statementTexts["problem-properties.json"]); + // console.log(mission); + statementData = { + id: statementRaw.id, + legend: statementTexts.legend, + timeLimit: statementTexts.timeLimit, + output: statementTexts.output, + input: statementTexts.input, + sampleTests: statementTexts.sampleTests, + name: statementTexts.name, + memoryLimit: statementTexts.memoryLimit, + tags: mission.tags, + notes: statementTexts.notes, + }; + } catch (err) { + console.error("Ошибка парсинга statementTexts:", err); + } + return ( -
+
+
- {missionId} + />
+
+
+ { setCode(value); }} + onChangeLanguage={((value: string) => { setLanguage(value); })} + /> +
+
+ { + dispatch(submitMission({ + missionId: missionIdNumber, + language: language, + languageVersion: "latest", + sourceCode: code, + contestId: null, + + })) + }} /> +
+
+
); }; diff --git a/src/redux/slices/missions.ts b/src/redux/slices/missions.ts index 571942d..8beb53d 100644 --- a/src/redux/slices/missions.ts +++ b/src/redux/slices/missions.ts @@ -41,7 +41,7 @@ const initialState: MissionsState = { export const fetchMissions = createAsyncThunk( "missions/fetchMissions", async ( - { page = 0, pageSize = 10, tags }: { page?: number; pageSize?: number; tags?: string[] }, + { page = 0, pageSize = 10, tags = [] }: { page?: number; pageSize?: number; tags?: string[] }, { rejectWithValue } ) => { try { @@ -120,6 +120,7 @@ const missionsSlice = createSlice({ }); builder.addCase(fetchMissionById.fulfilled, (state, action: PayloadAction) => { state.status = "successful"; + console.log(action.payload); state.currentMission = action.payload; }); builder.addCase(fetchMissionById.rejected, (state, action: PayloadAction) => { diff --git a/src/redux/slices/submit.ts b/src/redux/slices/submit.ts new file mode 100644 index 0000000..e970497 --- /dev/null +++ b/src/redux/slices/submit.ts @@ -0,0 +1,161 @@ +import { createSlice, createAsyncThunk, PayloadAction } from "@reduxjs/toolkit"; +import axios from "../../axios"; + +// Типы данных +export interface Submit { + id?: number; + missionId: number; + language: string; + languageVersion: string; + sourceCode: string; + contestId: number | null; +} + +export interface SubmitStatus { + SubmitId: number; + State: string; + ErrorCode: string; + Message: string; + CurrentTest: number; + AmountOfTests: number; +} + +interface SubmitState { + submits: Submit[]; + currentSubmit?: Submit; + status: "idle" | "loading" | "successful" | "failed"; + error: string | null; +} + +// Начальное состояние +const initialState: SubmitState = { + submits: [], + currentSubmit: undefined, + status: "idle", + error: null, +}; + +// AsyncThunk: Отправка решения +export const submitMission = createAsyncThunk( + "submit/submitMission", + async (submitData: Submit, { rejectWithValue }) => { + try { + const response = await axios.post("/submits", submitData); + return response.data; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Submit failed"); + } + } +); + +// AsyncThunk: Получить все свои отправки +export const fetchMySubmits = createAsyncThunk( + "submit/fetchMySubmits", + async (_, { rejectWithValue }) => { + try { + const response = await axios.get("/submits/my"); + return response.data as Submit[]; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to fetch submits"); + } + } +); + +// AsyncThunk: Получить конкретную отправку по ID +export const fetchSubmitById = createAsyncThunk( + "submit/fetchSubmitById", + async (id: number, { rejectWithValue }) => { + try { + const response = await axios.get(`/submits/${id}`); + return response.data as Submit; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to fetch submit"); + } + } +); + +// AsyncThunk: Получить свои отправки для конкретной миссии +export const fetchMySubmitsByMission = createAsyncThunk( + "submit/fetchMySubmitsByMission", + async (missionId: number, { rejectWithValue }) => { + try { + const response = await axios.get(`/submits/my/mission/${missionId}`); + return response.data as Submit[]; + } catch (err: any) { + return rejectWithValue(err.response?.data?.message || "Failed to fetch mission submits"); + } + } +); + +// Slice +const submitSlice = createSlice({ + name: "submit", + initialState, + reducers: { + clearCurrentSubmit: (state) => { + state.currentSubmit = undefined; + state.status = "idle"; + state.error = null; + }, + }, + extraReducers: (builder) => { + // Отправка решения + builder.addCase(submitMission.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(submitMission.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.submits.push(action.payload); + }); + builder.addCase(submitMission.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // Получить все свои отправки + builder.addCase(fetchMySubmits.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchMySubmits.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.submits = action.payload; + }); + builder.addCase(fetchMySubmits.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // Получить отправку по ID + builder.addCase(fetchSubmitById.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchSubmitById.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.currentSubmit = action.payload; + }); + builder.addCase(fetchSubmitById.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + + // Получить отправки по миссии + builder.addCase(fetchMySubmitsByMission.pending, (state) => { + state.status = "loading"; + state.error = null; + }); + builder.addCase(fetchMySubmitsByMission.fulfilled, (state, action: PayloadAction) => { + state.status = "successful"; + state.submits = action.payload; + }); + builder.addCase(fetchMySubmitsByMission.rejected, (state, action: PayloadAction) => { + state.status = "failed"; + state.error = action.payload; + }); + }, +}); + +export const { clearCurrentSubmit } = submitSlice.actions; +export const submitReducer = submitSlice.reducer; diff --git a/src/redux/store.ts b/src/redux/store.ts index 834d822..6ea89a8 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -2,6 +2,7 @@ import { configureStore } from "@reduxjs/toolkit"; import { authReducer } from "./slices/auth"; import { storeReducer } from "./slices/store"; import { missionsReducer } from "./slices/missions"; +import { submitReducer } from "./slices/submit"; // использование @@ -19,6 +20,7 @@ export const store = configureStore({ auth: authReducer, store: storeReducer, missions: missionsReducer, + submin: submitReducer, }, }); diff --git a/src/views/home/menu/Menu.tsx b/src/views/home/menu/Menu.tsx index ddf329b..0e5cab3 100644 --- a/src/views/home/menu/Menu.tsx +++ b/src/views/home/menu/Menu.tsx @@ -11,7 +11,6 @@ const Menu = () => { {text: "Группы", href: "/home/groups", icon: Users, page: "groups" }, {text: "Контесты", href: "/home/contests", icon: Cup, page: "contests" }, {text: "Аккаунт", href: "/home/account", icon: Account, page: "account" }, - {text: "Загрузка", href: "/upload", icon: Account, page: "p" }, ]; const activePage = useAppSelector((state) => state.store.menu.activePage); diff --git a/src/views/home/missions/MissionItem.tsx b/src/views/home/missions/MissionItem.tsx index 8c679b2..2bdf6f8 100644 --- a/src/views/home/missions/MissionItem.tsx +++ b/src/views/home/missions/MissionItem.tsx @@ -1,5 +1,6 @@ import { cn } from "../../../lib/cn"; import { IconError, IconSuccess } from "../../../assets/icons/missions"; +import { useNavigate } from "react-router-dom"; export interface MissionItemProps { id: number; @@ -29,14 +30,18 @@ export function formatBytesToMB(bytes: number): string { const MissionItem: React.FC = ({ id, name, difficulty, timeLimit, memoryLimit, type, status }) => { - console.log(id); + const navigate = useNavigate(); + return (
+ "cursor-pointer brightness-100 hover:brightness-125 transition-all duration-300", + )} + onClick={() => {navigate(`/mission/${id}`)}} + >
#{id}
diff --git a/src/views/home/missions/Missions.tsx b/src/views/home/missions/Missions.tsx index 5969183..c62b0db 100644 --- a/src/views/home/missions/Missions.tsx +++ b/src/views/home/missions/Missions.tsx @@ -1,8 +1,10 @@ import MissionItem from "./MissionItem"; import { SecondaryButton } from "../../../components/button/SecondaryButton"; -import { useAppDispatch } from "../../../redux/hooks"; +import { useAppDispatch, useAppSelector } from "../../../redux/hooks"; import { useEffect } from "react"; import { setMenuActivePage } from "../../../redux/slices/store"; +import { useNavigate } from "react-router-dom"; +import { fetchMissions } from "../../../redux/slices/missions"; export interface Mission { @@ -17,457 +19,18 @@ export interface Mission { updatedAt: string; } - const Missions = () => { const dispatch = useAppDispatch(); + const naivgate = useNavigate(); - const missions: Mission[] = [ - { - "id": 1, - "authorId": 1, - "name": "Todo List App", - "difficulty": "Easy", - "tags": ["react", "state", "list"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:13.000Z", - "updatedAt": "2025-10-28T13:23:13.000Z" - }, - { - "id": 2, - "authorId": 1, - "name": "Search Filter Component", - "difficulty": "Medium", - "tags": ["filter", "props", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:14.000Z", - "updatedAt": "2025-10-28T13:23:14.000Z" - }, - { - "id": 3, - "authorId": 1, - "name": "User Card List", - "difficulty": "Easy", - "tags": ["components", "props", "array"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:15.000Z", - "updatedAt": "2025-10-28T13:23:15.000Z" - }, - { - "id": 4, - "authorId": 1, - "name": "Theme Switcher", - "difficulty": "Medium", - "tags": ["context", "theme", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:16.000Z", - "updatedAt": "2025-10-28T13:23:16.000Z" - }, - { - "id": 5, - "authorId": 1, - "name": "Debounced Input", - "difficulty": "Hard", - "tags": ["debounce", "hooks", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:17.000Z", - "updatedAt": "2025-10-28T13:23:17.000Z" - }, - { - "id": 6, - "authorId": 1, - "name": "Pagination Component", - "difficulty": "Medium", - "tags": ["pagination", "array", "state"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:18.000Z", - "updatedAt": "2025-10-28T13:23:18.000Z" - }, - { - "id": 7, - "authorId": 1, - "name": "Modal Window", - "difficulty": "Easy", - "tags": ["ui", "portal", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:19.000Z", - "updatedAt": "2025-10-28T13:23:19.000Z" - }, - { - "id": 8, - "authorId": 1, - "name": "Form Validation", - "difficulty": "Hard", - "tags": ["form", "validation", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:20.000Z", - "updatedAt": "2025-10-28T13:23:20.000Z" - }, - { - "id": 9, - "authorId": 1, - "name": "Countdown Timer", - "difficulty": "Medium", - "tags": ["timer", "hooks", "state"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:21.000Z", - "updatedAt": "2025-10-28T13:23:21.000Z" - }, - { - "id": 10, - "authorId": 1, - "name": "Drag And Drop List", - "difficulty": "Hard", - "tags": ["dragdrop", "array", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:22.000Z", - "updatedAt": "2025-10-28T13:23:22.000Z" - }, - { - "id": 11, - "authorId": 1, - "name": "Custom Hook Use Fetch", - "difficulty": "Medium", - "tags": ["hook", "fetch", "async"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:23.000Z", - "updatedAt": "2025-10-28T13:23:23.000Z" - }, - { - "id": 12, - "authorId": 1, - "name": "Infinite Scroll", - "difficulty": "Hard", - "tags": ["scroll", "pagination", "api"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:24.000Z", - "updatedAt": "2025-10-28T13:23:24.000Z" - }, - { - "id": 13, - "authorId": 1, - "name": "Responsive Navbar", - "difficulty": "Easy", - "tags": ["css", "layout", "responsive"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:25.000Z", - "updatedAt": "2025-10-28T13:23:25.000Z" - }, - { - "id": 14, - "authorId": 1, - "name": "Accordion Component", - "difficulty": "Easy", - "tags": ["ui", "state", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:26.000Z", - "updatedAt": "2025-10-28T13:23:26.000Z" - }, - { - "id": 15, - "authorId": 1, - "name": "File Upload Preview", - "difficulty": "Hard", - "tags": ["file", "events", "preview"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:27.000Z", - "updatedAt": "2025-10-28T13:23:27.000Z" - }, - { - "id": 16, - "authorId": 1, - "name": "Dark Mode Toggle", - "difficulty": "Easy", - "tags": ["theme", "context", "localStorage"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:28.000Z", - "updatedAt": "2025-10-28T13:23:28.000Z" - }, - { - "id": 17, - "authorId": 1, - "name": "Realtime Clock", - "difficulty": "Easy", - "tags": ["date", "state", "interval"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:29.000Z", - "updatedAt": "2025-10-28T13:23:29.000Z" - }, - { - "id": 18, - "authorId": 1, - "name": "Chart With Recharts", - "difficulty": "Medium", - "tags": ["chart", "data", "props"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:30.000Z", - "updatedAt": "2025-10-28T13:23:30.000Z" - }, - { - "id": 19, - "authorId": 1, - "name": "Router Navigation", - "difficulty": "Medium", - "tags": ["router", "navigation", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:31.000Z", - "updatedAt": "2025-10-28T13:23:31.000Z" - }, - { - "id": 20, - "authorId": 1, - "name": "Data Table Sortable", - "difficulty": "Hard", - "tags": ["table", "sort", "filter"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:32.000Z", - "updatedAt": "2025-10-28T13:23:32.000Z" - }, - { - "id": 1, - "authorId": 1, - "name": "Todo List App", - "difficulty": "Easy", - "tags": ["react", "state", "list"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:13.000Z", - "updatedAt": "2025-10-28T13:23:13.000Z" - }, - { - "id": 2, - "authorId": 1, - "name": "Search Filter Component", - "difficulty": "Medium", - "tags": ["filter", "props", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:14.000Z", - "updatedAt": "2025-10-28T13:23:14.000Z" - }, - { - "id": 3, - "authorId": 1, - "name": "User Card List", - "difficulty": "Easy", - "tags": ["components", "props", "array"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:15.000Z", - "updatedAt": "2025-10-28T13:23:15.000Z" - }, - { - "id": 4, - "authorId": 1, - "name": "Theme Switcher", - "difficulty": "Medium", - "tags": ["context", "theme", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:16.000Z", - "updatedAt": "2025-10-28T13:23:16.000Z" - }, - { - "id": 5, - "authorId": 1, - "name": "Debounced Input", - "difficulty": "Hard", - "tags": ["debounce", "hooks", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:17.000Z", - "updatedAt": "2025-10-28T13:23:17.000Z" - }, - { - "id": 6, - "authorId": 1, - "name": "Pagination Component", - "difficulty": "Medium", - "tags": ["pagination", "array", "state"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:18.000Z", - "updatedAt": "2025-10-28T13:23:18.000Z" - }, - { - "id": 7, - "authorId": 1, - "name": "Modal Window", - "difficulty": "Easy", - "tags": ["ui", "portal", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:19.000Z", - "updatedAt": "2025-10-28T13:23:19.000Z" - }, - { - "id": 8, - "authorId": 1, - "name": "Form Validation", - "difficulty": "Hard", - "tags": ["form", "validation", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:20.000Z", - "updatedAt": "2025-10-28T13:23:20.000Z" - }, - { - "id": 9, - "authorId": 1, - "name": "Countdown Timer", - "difficulty": "Medium", - "tags": ["timer", "hooks", "state"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:21.000Z", - "updatedAt": "2025-10-28T13:23:21.000Z" - }, - { - "id": 10, - "authorId": 1, - "name": "Drag And Drop List", - "difficulty": "Hard", - "tags": ["dragdrop", "array", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:22.000Z", - "updatedAt": "2025-10-28T13:23:22.000Z" - }, - { - "id": 11, - "authorId": 1, - "name": "Custom Hook Use Fetch", - "difficulty": "Medium", - "tags": ["hook", "fetch", "async"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:23.000Z", - "updatedAt": "2025-10-28T13:23:23.000Z" - }, - { - "id": 12, - "authorId": 1, - "name": "Infinite Scroll", - "difficulty": "Hard", - "tags": ["scroll", "pagination", "api"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:24.000Z", - "updatedAt": "2025-10-28T13:23:24.000Z" - }, - { - "id": 13, - "authorId": 1, - "name": "Responsive Navbar", - "difficulty": "Easy", - "tags": ["css", "layout", "responsive"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:25.000Z", - "updatedAt": "2025-10-28T13:23:25.000Z" - }, - { - "id": 14, - "authorId": 1, - "name": "Accordion Component", - "difficulty": "Easy", - "tags": ["ui", "state", "events"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:26.000Z", - "updatedAt": "2025-10-28T13:23:26.000Z" - }, - { - "id": 15, - "authorId": 1, - "name": "File Upload Preview", - "difficulty": "Hard", - "tags": ["file", "events", "preview"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:27.000Z", - "updatedAt": "2025-10-28T13:23:27.000Z" - }, - { - "id": 16, - "authorId": 1, - "name": "Dark Mode Toggle", - "difficulty": "Easy", - "tags": ["theme", "context", "localStorage"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:28.000Z", - "updatedAt": "2025-10-28T13:23:28.000Z" - }, - { - "id": 17, - "authorId": 1, - "name": "Realtime Clock", - "difficulty": "Easy", - "tags": ["date", "state", "interval"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:29.000Z", - "updatedAt": "2025-10-28T13:23:29.000Z" - }, - { - "id": 18, - "authorId": 1, - "name": "Chart With Recharts", - "difficulty": "Medium", - "tags": ["chart", "data", "props"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:30.000Z", - "updatedAt": "2025-10-28T13:23:30.000Z" - }, - { - "id": 19, - "authorId": 1, - "name": "Router Navigation", - "difficulty": "Medium", - "tags": ["router", "navigation", "hooks"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:31.000Z", - "updatedAt": "2025-10-28T13:23:31.000Z" - }, - { - "id": 20, - "authorId": 1, - "name": "Data Table Sortable", - "difficulty": "Hard", - "tags": ["table", "sort", "filter"], - "timeLimit": 1000, - "memoryLimit": 268435456, - "createdAt": "2025-10-28T13:23:32.000Z", - "updatedAt": "2025-10-28T13:23:32.000Z" - } - ]; + const missions = useAppSelector((state) => state.missions.missions); - useEffect(() => { + useEffect(() => { dispatch(setMenuActivePage("missions")) - }, []); + dispatch(fetchMissions({})) + }, []); + return (
@@ -478,7 +41,7 @@ const Missions = () => { Задачи
{}} + onClick={() => {naivgate("/upload")}} text="Создать задачу" className="absolute right-0" /> @@ -491,7 +54,19 @@ const Missions = () => {
{missions.map((v, i) => ( - + ))}
diff --git a/src/views/mission/codeeditor/CodeEditor.tsx b/src/views/mission/codeeditor/CodeEditor.tsx index 94974cd..cd048c4 100644 --- a/src/views/mission/codeeditor/CodeEditor.tsx +++ b/src/views/mission/codeeditor/CodeEditor.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import Editor from "@monaco-editor/react"; import { upload } from "../../../assets/icons/input"; import { cn } from "../../../lib/cn"; @@ -14,7 +14,12 @@ const languageMap: Record = { csharp: "csharp" }; -const CodeEditor: React.FC = () => { +export interface CodeEditorProps { + onChange: (value: string) => void; + onChangeLanguage: (value: string) => void; +} + +const CodeEditor: React.FC = ({onChange, onChangeLanguage}) => { const [language, setLanguage] = useState("cpp"); const [code, setCode] = useState(""); const [isDragging, setIsDragging] = useState(false); @@ -30,6 +35,13 @@ const CodeEditor: React.FC = () => { { value: "csharp", text: "C#" }, ]; + useEffect(() => { + onChange(code); + }, [code]) + useEffect(() => { + onChangeLanguage(language); + }, [language]) + const handleFileUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (!file) return; diff --git a/src/views/mission/statement/LaTextContainer.tsx b/src/views/mission/statement/LaTextContainer.tsx new file mode 100644 index 0000000..f36d00a --- /dev/null +++ b/src/views/mission/statement/LaTextContainer.tsx @@ -0,0 +1,14 @@ +import React, { useEffect, useRef } from "react"; + +interface LaTextContainerProps { + content: string; +} + +const LaTextContainer: React.FC = ({ content }) => { + + return
+ {content} +
; +}; + +export default LaTextContainer; diff --git a/src/views/mission/statement/Statement.tsx b/src/views/mission/statement/Statement.tsx index 16cd153..63588ad 100644 --- a/src/views/mission/statement/Statement.tsx +++ b/src/views/mission/statement/Statement.tsx @@ -1,58 +1,85 @@ import React, { useState } from "react"; import { cn } from "../../../lib/cn"; +import LaTextContainer from "./LaTextContainer"; // import FullLatexRenderer from "./FullLatexRenderer"; -const Statement: React.FC = () => { +export interface StatementData { + id?: number; + name?: string; + tags?: string[]; + timeLimit?: number; + memoryLimit?: number; + legend?: string; + input?: string; + output?: string; + sampleTests?: { input: string; output: string }[]; + notes?: string; +} + + + +const Statement: React.FC = ({ + id, + name, + tags, + timeLimit = 1000, + memoryLimit = 256 * 1024 * 1024, + legend = "", + input = "", + output = "", + sampleTests = [], + notes = "", +}) => { -const data = { - "extraResources": { - "example.01.mu": "TVXzATcgNg0KMSAyIDMgNCA1IDYgNw0KMSA4IDMNCjIgOQ0KMyAzDQoxIDMgOQ0KMiAxMA0KMyAxDQo=" - }, - "scoring": null, - "notes": "Изначально очередь выглядит следующим образом:\r\n\r\n\\includegraphics{o1.png}\r\n\r\nВ первую минуту приходит студент с номером 8 и встает перед студентом с номером 3.\r\n\r\n\\includegraphics{o2.png}\r\n\r\nПотом студент с номером 9 встает в конец очереди.\r\n\r\n\\includegraphics{o3.png}\r\n\r\nСтудент с номером 3 уходит из очереди.\r\n\r\n\\includegraphics{o4.png}\r\n\r\nПотом он возвращается и становится перед студентом с номером 9.\r\n\r\n\\includegraphics{o5.png}\r\n\r\nПосле в конец очереди становится студент с номером 10.\r\n\r\n\\includegraphics{o6.png}\r\n\r\nИ студент с номером 1 уходит из очереди.\r\n\r\n\\includegraphics{o7.png}\r\n\r\nПосле $m$ событий очередь имеет следующий вид:\r\n\r\n\\includegraphics{o8.png}", - "legend": "В честь юбилея ректорат ЮФУ решил запустить акцию <<Сто и десять кексов>>. \r\n\r\n $x$, $a_i^2 + b_i^2 \le a_{i+1}^2$ В каждом корпусе университета открылась лавка с кексами, в которой каждый студент может получить бесплатные кексы.\r\n\r\nНе прошло и пары минут после открытия, как к лавкам набежали студенты и образовалось много очередей. Но самая большая очередь образовалась в главном корпусе ЮФУ. Изначально в этой очереди стояло $n$ студентов, но потом в течение следующих $m$ минут какие-то студенты приходили и вставали в очередь, а какие-то уходили.\r\n\r\nЗа каждым студентом закреплен номер его зачетной книжки, будем называть это число номером студента. У каждого студента будет уникальный номер, по которому можно однозначно его идентифицировать. Будем считать, что каждую минуту происходило одно из следующих событий:\r\n\r\n\\begin{enumerate}\r\n \\item Студент с номером $x$ пришел и встал перед студентом с номером $y$;\r\n \\item Студент с номером $x$ пришел и встал в конец очереди;\r\n \\item Студент с номером $x$ ушел из очереди; возможно, он потом вернется.\r\n\\end{enumerate}\r\n\r\nАналитикам стало интересно, а какой будет очередь после $m$ минут? \r\n\r\nПомогите им и сообщите конечное состояние очереди.\r\n\r\n", - "authorLogin": "valavshonok", - "language": "russian", - "timeLimit": 1000, - "output": "В первой строке выведите одно число $|a|$~--- длину очереди после выполнения всех запросов изменения.\r\n\r\nВ следующей строке выведите $|a|$ чисел $a_1, a_2, \\cdots , a_{|a|}$, где $a_i$~--- номер студента, который стоит на $i$-й позиции в очереди.", - "inputFile": "stdin", - "outputFile": "stdout", - "input": "В первой строке заданы два целых числа $n$ и $m$ $(1 \\le n, m \\le 10^5)$~--- текущее число студентов в очереди и количество изменений.\r\n\r\nВ следующей строке задается $n$ целых \\textbf{различных} чисел $a_1, a_2, \\cdots , a_n$ $(1 \\le a_i \\le 10^9)$, где $a_i$~--- номер студента, который стоит на $i$-й позиции в очереди.\r\n\r\nВ следующих $m$ строках идет описание запросов изменения очереди.\r\n\r\nВ каждой строке в зависимости от типа запроса задается два или три числа. Первое число $t_j$ $(1 \\le t_j \\le 3)$~--- тип события, которое произошло в $j$-ю минуту.\r\n\r\nЕсли $t_j = \\textbf{1}$, то в строке задается еще 2 числа $x$ $(1 \\le x_j \\le 10^9)$ и $y$ $(1 \\le y_j \\le 10^9)$~--- номер студента, который пришел, и номер студента, перед которым он встанет в очереди. Гарантируется, что студент с номером $x$ ещё не занял очередь, а студент с номером $y$ уже стоит в ней. \r\n\r\nЕсли $t_j = \\textbf{2}$, то в строке задается еще 1 число $x$ $(1 \\le x_j \\le 10^9)$~--- номер студента, который пришел и встал в конец очереди. Гарантируется, что студент с номером $x$ ещё не занял очередь.\r\n\r\nЕсли $t_j = \\textbf{3}$, то в строке задается еще 1 число $x$ $(1 \\le x_j \\le 10^9)$~--- номер студента, который ушел из очереди. Гарантируется, что студент с номером $x$ стоит в очереди.", - "authorName": "Виталий Лавшонок", - "sampleTests": [ - { - "output": "9\r\n2 8 4 5 6 7 3 9 10 \r\n", - "input": "7 6\r\n1 2 3 4 5 6 7\r\n1 8 3\r\n2 9\r\n3 3\r\n1 3 9\r\n2 10\r\n3 1\r\n", - "inputFile": "example.01", - "outputFile": "example.01.a" - } - ], - "name": "Очередь за кексами", - "interaction": null, - "memoryLimit": 268435456, - "tutorial": "Давайте просто промоделируем все действия.\r\n\r\nЗаведем список элементов, а также сохраним по ключу $x$ указатель на элемент списка. Мы можем это сделать, так как все элементы различны. Например, в С++ можно просто завести коллекцию list, а также map::iterator> или реализовать свой список.\r\n\r\nТеперь мы можем легко обрабатывать все запросы, а в конце просто выведем весь список.\r\n\r\nЗапрос 1-го типа можно обработать так: просто берем по ключу указатель на нужный элемент и вставляем перед ним другой элемент, останется только по ключу $x$ записать указатель на новый элемент.\r\n\r\nЗапрос 2-го типа~--- просто добавить в список элемент в конец и сохранить на него указатель.\r\n\r\nЗапрос 3-го типа~--- удаляем из списка элемент по его указателю.\r\n\r\nВ конце просто выводим массив.\r\n\r\nИтоговая сложность $O(mlog(n))$" -}; return (
-

Грод на 2700

-

Задача #1234

+

{name}

+

Задача #{id}

-
- tags +
+ {tags && tags.map((v, i) =>
{v}
)}
-
-

ограничение по времени на тест: 1 секунда

-

ограничение по памяти на тест: 256 мегабайт

+
+

ограничение по времени на тест: {timeLimit / 1000} секунда

+

ограничение по памяти на тест: {memoryLimit / 1024 / 1024} мегабайт

ввод: стандартный ввод

вывод: стандартный вывод

+
+ +
+
+
Входные данные
+ +
+
+
Выходные данные
+ +
+
- {/* */} +
{sampleTests.length == 1 ? "Пример" : "Примеры"}
+
+ + {sampleTests.map((v, i) => +
+
Входные данные
+
{v.input}
+
Выходные данные
+
{v.output}
+
+ )} +
+
+
+
Примечание
+ +
Автор: Jacks