diff --git a/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx b/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx index d912f1a..9042c91 100644 --- a/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx @@ -1,8 +1,9 @@ import React, { useState, useEffect } from 'react'; -import { Link, useNavigate } from 'react-router-dom'; +import { Link, useNavigate, useParams } from 'react-router-dom'; import { Task } from '@/shared/types/task'; -import { ArrowLeft, Clock } from 'lucide-react'; +import { ArrowLeft } from 'lucide-react'; import { CompetitionType } from '@/shared/types/competition'; +import { CompetitionResult } from '@/shared/types/competition'; interface CompetitionHeaderProps { title: string; @@ -11,8 +12,9 @@ interface CompetitionHeaderProps { setAnswer: (value: string) => void; setSelectedFile: (file: File | null) => void; competitionType?: CompetitionType; - startDate?: Date; - endDate?: Date; + startDate?: Date | string; + endDate?: Date | string; + taskResults?: CompetitionResult[]; } const CompetitionHeader: React.FC = ({ @@ -23,9 +25,11 @@ const CompetitionHeader: React.FC = ({ setSelectedFile, competitionType, startDate, - endDate + endDate, + taskResults = [] }) => { const navigate = useNavigate(); + const { taskId } = useParams<{ taskId?: string }>(); const [timeLeft, setTimeLeft] = useState(''); const handleTaskSelect = (taskId: string) => { @@ -34,7 +38,7 @@ const CompetitionHeader: React.FC = ({ navigate(`/competition/${competitionId}/tasks/${taskId}`); } - const formatDate = (date?: Date) => { + const formatDate = (date?: Date | string) => { if (!date) return ''; const dateObj = typeof date === 'string' ? new Date(date) : date; @@ -74,6 +78,42 @@ const CompetitionHeader: React.FC = ({ return () => clearInterval(timerInterval); }, [endDate, competitionId, navigate, competitionType]); + const getTaskStatus = (task: Task) => { + const result = taskResults.find(r => r.task_name === task.title); + + let bgColor = 'var(--color-task-uncleared)'; + let textColor = 'var(--color-task-text-uncleared)'; + + if (result) { + if (result.result === -1) { + bgColor = 'var(--color-task-checking)'; + textColor = 'var(--color-task-text-checking)'; + } else if (result.result === -2) { + bgColor = 'var(--color-task-uncleared)'; + textColor = 'var(--color-task-text-uncleared)'; + } else if (result.result === 0) { + bgColor = 'var(--color-task-wrong)'; + textColor = 'var(--color-task-text-wrong)'; + } else if (result.result < result.max_points) { + bgColor = 'var(--color-task-partial)'; + textColor = 'var(--color-task-text-partial)'; + } else if (result.result === result.max_points) { + bgColor = 'var(--color-task-correct)'; + textColor = 'var(--color-task-text-correct)'; + } + } + + const isActive = task.id === taskId; + const activeBorder = isActive ? 'border-2 border-blue-500' : ''; + + return { + backgroundColor: bgColor, + color: textColor, + className: `rounded-lg px-3 py-1.5 font-medium text-sm font-hse-sans cursor-pointer + transition-all hover:brightness-95 flex-shrink-0 ${activeBorder}` + }; + }; + const showTimeSection = competitionType === CompetitionType.COMPETITIVE && (startDate || endDate); return ( @@ -120,18 +160,19 @@ const CompetitionHeader: React.FC = ({
- {tasks.map((task) => ( - - ))} + {tasks.map((task) => { + const { backgroundColor, color, className } = getTaskStatus(task); + return ( + + ); + })}
diff --git a/services/frontend/src/pages/CompetitionSession/index.tsx b/services/frontend/src/pages/CompetitionSession/index.tsx index 42ea9c4..0726903 100644 --- a/services/frontend/src/pages/CompetitionSession/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/index.tsx @@ -1,10 +1,10 @@ import { useState, useEffect } from "react"; -import { useParams, Navigate, useNavigate } from "react-router-dom"; +import { useParams, Navigate } from "react-router-dom"; import CompetitionHeader from "./components/CompetitionHeader"; import TaskContent from "./components/TaskContent"; import TaskSolution from "./modules/TaskSolution"; import { getCompetitionTasks, submitTaskSolution } from "@/shared/api/session"; -import { getCompetition } from "@/shared/api/competitions"; +import { getCompetition, getCompetitionResults } from "@/shared/api/competitions"; import { Loader2 } from "lucide-react"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { TaskType } from "@/shared/types/task"; @@ -16,7 +16,6 @@ const CompetitionSession = () => { const [isReloading, setIsReloading] = useState(false); const competitionId = id || ""; const queryClient = useQueryClient(); - const navigate = useNavigate(); const competitionQuery = useQuery({ queryKey: ["competition", competitionId], @@ -30,6 +29,12 @@ const CompetitionSession = () => { enabled: !!competitionId, }); + const resultsQuery = useQuery({ + queryKey: ["competitionResults", competitionId], + queryFn: () => getCompetitionResults(competitionId), + enabled: !!competitionId, + }); + const submitMutation = useMutation({ mutationFn: () => { if (!currentTask || !competitionId) throw new Error("Missing task or competition ID"); @@ -47,10 +52,14 @@ const CompetitionSession = () => { queryKey: ['solutionHistory', competitionId, taskId] }); + queryClient.invalidateQueries({ + queryKey: ['competitionResults', competitionId] + }); + setIsReloading(true); setTimeout(() => { - window.location.reload() + window.location.reload(); setIsReloading(false); }, 2500); }, @@ -61,6 +70,7 @@ const CompetitionSession = () => { const competition = competitionQuery.data; const tasks = tasksQuery.data || []; + const results = resultsQuery.data || []; const isLoading = tasksQuery.isLoading || competitionQuery.isLoading; const error = tasksQuery.error || competitionQuery.error ? "Не удалось загрузить данные. Пожалуйста, попробуйте позже." @@ -113,6 +123,7 @@ const CompetitionSession = () => { competitionType={competition?.type} startDate={competition?.start_date} endDate={competition?.end_date} + taskResults={results} />
@@ -138,6 +149,16 @@ const CompetitionSession = () => { onSubmit={handleSubmit} isSubmitting={isSubmitting} /> + {isReloading && ( +
+
+ +

+ Решение отправлено! Страница обновится через несколько секунд... +

+
+
+ )} ) : (