diff --git a/services/frontend/src/App.tsx b/services/frontend/src/App.tsx index d7bd1eb..9c7f555 100644 --- a/services/frontend/src/App.tsx +++ b/services/frontend/src/App.tsx @@ -1,8 +1,8 @@ import { Routes, Route } from "react-router"; import "./styles/globals.css"; -import CompetitionsPage from "./pages/CompetitionsPage"; -import CompetitionPreviewPage from "./pages/CompetitionPreviewPage"; -import CompetitionRunnerPage from "./pages/CompetitionRunnerPage"; +import CompetitionsPage from "./pages/Competitions"; +import CompetitionPage from "./pages/Competition"; +import CompetitionRunnerPage from "./pages/CompetitionSession"; import { NavbarLayout } from "./widgets/navbar-layout"; const App = () => { @@ -10,12 +10,12 @@ const App = () => { }> } /> + } /> + } + /> - } /> - } - /> ); }; diff --git a/services/frontend/src/components/ui/button.tsx b/services/frontend/src/components/ui/button.tsx index 49dcad9..7558e04 100644 --- a/services/frontend/src/components/ui/button.tsx +++ b/services/frontend/src/components/ui/button.tsx @@ -9,8 +9,7 @@ const buttonVariants = cva( { variants: { variant: { - default: - "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", + default: "bg-primary text-foreground hover:bg-primary/80", destructive: "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40", outline: @@ -21,7 +20,7 @@ const buttonVariants = cva( link: "text-primary underline-offset-4 hover:underline", }, size: { - default: "h-9 px-4 py-2 has-[>svg]:px-3", + default: "h-12 px-5 py-3 has-[>svg]:px-3 text-lg font-semibold", sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", lg: "h-10 rounded-md px-6 has-[>svg]:px-4", icon: "size-9", diff --git a/services/frontend/src/pages/.gitkeep b/services/frontend/src/pages/.gitkeep deleted file mode 100644 index e69de29..0000000 diff --git a/services/frontend/src/pages/Competition/index.tsx b/services/frontend/src/pages/Competition/index.tsx new file mode 100644 index 0000000..bdddd5a --- /dev/null +++ b/services/frontend/src/pages/Competition/index.tsx @@ -0,0 +1,53 @@ +import { useState } from "react"; +import { useParams, Link } from "react-router-dom"; +import { Button } from "@/components/ui/button"; +import { ArrowLeft } from "lucide-react"; +import { Competition } from "@/shared/types"; +import { mockCompetitions } from "@/shared/mocks/mocks"; + +const CompetitionPage = () => { + const { id } = useParams<{ id: string }>(); + const [competition] = useState( + mockCompetitions.find((comp) => comp.id === id)!, + ); + + return ( +
+ + + Назад к соревнованиям + + +
+
+ {competition.name} +
+ +
+
+

+ {competition.name} +

+
+ {competition.description + ?.split("\n") + .map((line, i) =>

{line}

)} +
+
+
+ +
+
+
+
+ ); +}; + +export default CompetitionPage; diff --git a/services/frontend/src/pages/CompetitionPreviewPage/index.tsx b/services/frontend/src/pages/CompetitionPreviewPage/index.tsx deleted file mode 100644 index e6261f7..0000000 --- a/services/frontend/src/pages/CompetitionPreviewPage/index.tsx +++ /dev/null @@ -1,108 +0,0 @@ -import { useEffect, useState } from "react"; -import { useParams, useNavigate } from "react-router-dom"; -import Navbar from "@/widgets/Navbar"; -import { Button } from "@/components/ui/button"; -import { ArrowLeft } from "lucide-react"; -import { Competition } from "@/shared/types"; -import { mockCompetitions, mockTasks } from "@/shared/mocks/mocks"; - -const CompetitionPreview = () => { - const { id } = useParams<{ id: string }>(); - const navigate = useNavigate(); - const [competition, setCompetition] = useState(null); - const [isLoading, setIsLoading] = useState(true); - - useEffect(() => { - const fetchCompetition = async () => { - try { - setTimeout(() => { - const found = mockCompetitions.find((comp) => comp.id === id); - setCompetition(found || null); - setIsLoading(false); - }, 500); - } catch (error) { - console.error("Error fetching competition:", error); - setIsLoading(false); - } - }; - - fetchCompetition(); - }, [id]); - - const handleBack = () => { - navigate(-1); - }; - - const handleContinue = () => { - if (competition?.id) { - const competitionTasks = mockTasks[competition.id]; - - if (competitionTasks && competitionTasks.length > 0) { - const firstTaskId = competitionTasks[0].id; - navigate(`/competition/${competition.id}/tasks/${firstTaskId}`); - } else { - navigate(`/competition/${competition.id}/tasks`); - } - } - }; - - return ( - <> - -
- - - {isLoading ? ( -
-

Загрузка...

-
- ) : competition ? ( -
-
- {competition.name} -
- -
-
-

- {competition.name} -

- -
- -
-

{competition.description}

-
-
-
- ) : ( -
-

- Соревнование не найдено -

-

- Запрошенное соревнование не существует или было удалено. -

-
- )} -
- - ); -}; - -export default CompetitionPreview; diff --git a/services/frontend/src/pages/CompetitionRunnerPage/index.tsx b/services/frontend/src/pages/CompetitionRunnerPage/index.tsx index 73b8797..dc12b58 100644 --- a/services/frontend/src/pages/CompetitionRunnerPage/index.tsx +++ b/services/frontend/src/pages/CompetitionRunnerPage/index.tsx @@ -1,78 +1,61 @@ -import { useState } from "react"; -import { useParams } from "react-router-dom"; -import Navbar from "@/widgets/Navbar"; -import { Task, TaskStatus } from "@/shared/types"; - -const sampleTasks: Task[] = [ - { id: "1", number: "1.1", status: "uncleared" }, - { id: "2", number: "1.2", status: "checking" }, - { id: "3", number: "1.3", status: "correct" }, - { id: "4", number: "2.1", status: "partial" }, - { id: "5", number: "2.2", status: "wrong" }, - { id: "6", number: "2.3", status: "uncleared" }, - { id: "7", number: "3.1", status: "checking" }, - { id: "8", number: "3.2", status: "correct" }, -]; +import { useState, useEffect } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { Task } from "@/shared/types"; +import { getTaskBgColor, getTaskTextColor } from "./utils/utils"; +import { mockTasks } from "@/shared/mocks/mocks"; +import { Button } from "@/components/ui/button"; +import { Calendar } from "lucide-react"; const CompetitionRunnerPage = () => { - const { id } = useParams<{ id: string }>(); - const [competitionTitle, setCompetitionTitle] = useState( - "Олимпиада DANO 2025. Индивидуальный этап", - ); - const [tasks, setTasks] = useState(sampleTasks); - const [selectedTaskId, setSelectedTaskId] = useState(null); + const { id, taskId } = useParams<{ id: string; taskId?: string }>(); + const navigate = useNavigate(); + const [competitionTitle, setCompetitionTitle] = useState("Олимпиада DANO 2025. Индивидуальный этап"); + const [tasks] = useState(mockTasks); + const [selectedTaskId, setSelectedTaskId] = useState(taskId || null); + const [answer, setAnswer] = useState(""); - const getTaskBgColor = (status: TaskStatus): string => { - switch (status) { - case "uncleared": - return "bg-[var(--color-task-uncleared)]"; - case "checking": - return "bg-[var(--color-task-checking)]"; - case "correct": - return "bg-[var(--color-task-correct)]"; - case "partial": - return "bg-[var(--color-task-partial)]"; - case "wrong": - return "bg-[var(--color-task-wrong)]"; + useEffect(() => { + if (taskId) { + setSelectedTaskId(taskId); + } else if (tasks.length > 0) { + navigate(`/competition/${id}/tasks/${tasks[0].id}`, { replace: true }); } - }; - - const getTaskTextColor = (status: TaskStatus): string => { - switch (status) { - case "uncleared": - return "text-gray-600"; - case "checking": - return "text-gray-800"; - case "correct": - return "text-green-800"; - case "partial": - return "text-green-700"; - case "wrong": - return "text-red-800"; - } - }; + }, [taskId, tasks, id, navigate]); const handleTaskClick = (taskId: string) => { - setSelectedTaskId(taskId); + if (selectedTaskId !== taskId) { + setSelectedTaskId(taskId); + navigate(`/competition/${id}/tasks/${taskId}`); + } + }; + + const currentTask = tasks.find(t => t.id === selectedTaskId); + + const handleSubmit = () => { + console.log("Submitting answer:", answer); + // Submit logic here + }; + + const handleHistoryClick = () => { + console.log("View history"); }; return ( - <> - - -
-
-
-

- {competitionTitle} -

+ <> +
+
+
+

{competitionTitle}

- -
+ +
{tasks.map((task) => ( -
handleTaskClick(task.id)} > {task.number} @@ -81,22 +64,82 @@ const CompetitionRunnerPage = () => {
- -
-
- {selectedTaskId ? ( -
-

- Задание {tasks.find((t) => t.id === selectedTaskId)?.number} -

-

- Содержание задания будет отображаться здесь. -

+ +
+
+ {currentTask ? ( +
+ {/* Left Container - Task Description */} +
+

+ Задача {currentTask.number} +

+ +
+

+ Рассмотрим последовательность чисел 2, 3, 5, 9, 17, 33, 65, 129, ... + Каждый член этой последовательности, начиная с третьего, равен сумме двух предыдущих членов. +

+

+ Найдите сумму первых 15 членов этой последовательности. +

+

+ В ответе укажите целое число. +

+
+
+ + {/* Right Container - Solution Area */} +
+ {/* Solution Status Card */} +
+
+ + Решение 12345 + + + Зачтено 5/10 баллов + +
+
+ 1 марта, 08:41 +
+
+ + {/* Answer Input */} +
+