Merge branch 'master' of gitlab.prodcontest.ru:team-15/project

This commit is contained in:
ITQ
2025-03-03 16:50:28 +03:00
3 changed files with 106 additions and 23 deletions
@@ -1,8 +1,8 @@
import React from 'react'; import React, { useState, useEffect } from 'react';
import { Link } from 'react-router-dom'; import { Link, useNavigate } from 'react-router-dom';
import { Task } from '@/shared/types/task'; import { Task } from '@/shared/types/task';
import { ArrowLeft } from 'lucide-react'; import { ArrowLeft, Clock } from 'lucide-react';
import { useNavigate } from 'react-router-dom'; import { CompetitionType } from '@/shared/types/competition';
interface CompetitionHeaderProps { interface CompetitionHeaderProps {
title: string; title: string;
@@ -10,6 +10,9 @@ interface CompetitionHeaderProps {
competitionId: string; competitionId: string;
setAnswer: (value: string) => void; setAnswer: (value: string) => void;
setSelectedFile: (file: File | null) => void; setSelectedFile: (file: File | null) => void;
competitionType?: CompetitionType;
startDate?: Date;
endDate?: Date;
} }
const CompetitionHeader: React.FC<CompetitionHeaderProps> = ({ const CompetitionHeader: React.FC<CompetitionHeaderProps> = ({
@@ -17,33 +20,103 @@ const CompetitionHeader: React.FC<CompetitionHeaderProps> = ({
tasks, tasks,
competitionId, competitionId,
setAnswer, setAnswer,
setSelectedFile setSelectedFile,
competitionType,
startDate,
endDate
}) => { }) => {
const navigate = useNavigate(); const navigate = useNavigate();
const [timeLeft, setTimeLeft] = useState<string>('');
const handleTaskSelect = (taskId: string) => { const handleTaskSelect = (taskId: string) => {
setAnswer(""); setAnswer("");
setSelectedFile(null); setSelectedFile(null);
console.log("SETTER ERROR")
navigate(`/competition/${competitionId}/tasks/${taskId}`); navigate(`/competition/${competitionId}/tasks/${taskId}`);
} }
const formatDate = (date?: Date) => {
if (!date) return '';
const dateObj = typeof date === 'string' ? new Date(date) : date;
return dateObj.toLocaleString('ru-RU', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit'
});
};
useEffect(() => {
if (!endDate || competitionType !== CompetitionType.COMPETITIVE) return;
const endDateObj = typeof endDate === 'string' ? new Date(endDate) : endDate;
const updateTimer = () => {
const now = new Date();
const diff = endDateObj.getTime() - now.getTime();
if (diff <= 0) {
navigate(`/competition/${competitionId}`);
return;
}
const hours = Math.floor(diff / (1000 * 60 * 60));
const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((diff % (1000 * 60)) / 1000);
setTimeLeft(`${hours.toString().padStart(2, '0')}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`);
};
updateTimer();
const timerInterval = setInterval(updateTimer, 1000);
return () => clearInterval(timerInterval);
}, [endDate, competitionId, navigate, competitionType]);
const showTimeSection = competitionType === CompetitionType.COMPETITIVE && (startDate || endDate);
return ( return (
<header className="bg-white shadow-sm sticky top-0 z-30 w-full"> <header className="bg-white shadow-sm sticky top-0 z-30 w-full">
<div className="mx-auto max-w-6xl px-4"> <div className="mx-auto max-w-6xl px-4">
<div className="flex items-center justify-between py-4"> <div className="flex items-center justify-between py-4">
<Link <div>
to={`/competition/${competitionId}`} <Link
className="flex items-center text-gray-600 hover:text-gray-900 transition-colors font-hse-sans text-sm" to={`/competition/${competitionId}`}
> className="flex items-center text-gray-600 hover:text-gray-900 transition-colors font-hse-sans text-sm"
<ArrowLeft className="h-4 w-4 mr-1" /> >
</Link> <ArrowLeft className="h-4 w-4 mr-1" />
</Link>
<h1 className="font-hse-sans text-xl font-semibold text-center flex-1">
{title}
</h1>
</div>
<h1 className="font-hse-sans text-xl font-semibold text-center flex-1"> {showTimeSection ? (
{title} <div className="flex items-center text-gray-600 font-hse-sans text-sm">
</h1> <div className="flex flex-col items-end">
{startDate && (
<div className="w-[70px]"></div> <span className="text-xs text-gray-500">
Начало: {formatDate(startDate)}
</span>
)}
{endDate && (
<span className="text-xs text-gray-500">
Конец: {formatDate(endDate)}
</span>
)}
{timeLeft && (
<span className="font-medium text-red-600">
Осталось: {timeLeft}
</span>
)}
</div>
</div>
) : (
<div className="w-[70px]"></div>
)}
</div> </div>
<div className="flex items-center justify-center gap-4 pb-4 overflow-x-auto no-scrollbar"> <div className="flex items-center justify-center gap-4 pb-4 overflow-x-auto no-scrollbar">
@@ -8,6 +8,7 @@ import { getCompetition } from "@/shared/api/competitions";
import { Loader2 } from "lucide-react"; import { Loader2 } from "lucide-react";
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"; import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { TaskType } from "@/shared/types/task"; import { TaskType } from "@/shared/types/task";
import { CompetitionType } from "@/shared/types/task";
const CompetitionSession = () => { const CompetitionSession = () => {
const { id, taskId } = useParams<{ id: string; taskId?: string }>(); const { id, taskId } = useParams<{ id: string; taskId?: string }>();
@@ -97,6 +98,9 @@ const CompetitionSession = () => {
competitionId={competitionId} competitionId={competitionId}
setAnswer={setAnswer} setAnswer={setAnswer}
setSelectedFile={setSelectedFile} setSelectedFile={setSelectedFile}
competitionType={competition?.type}
startDate={competition?.start_date}
endDate={competition?.end_date}
/> />
<main className="flex-1 bg-[#F8F8F8] pb-8"> <main className="flex-1 bg-[#F8F8F8] pb-8">
@@ -120,6 +124,7 @@ const CompetitionSession = () => {
selectedFile={selectedFile} selectedFile={selectedFile}
setSelectedFile={setSelectedFile} setSelectedFile={setSelectedFile}
onSubmit={handleSubmit} onSubmit={handleSubmit}
isSubmitting={submitMutation.isPending}
/> />
</div> </div>
) : ( ) : (
@@ -82,26 +82,31 @@ const TaskSolution: React.FC<TaskSolutionProps> = ({
useEffect(() => { useEffect(() => {
const loadSolutionContent = async () => { const loadSolutionContent = async () => {
if (!displayedSolution || !displayedSolution.content) return; if (!displayedSolution || !displayedSolution.content) return;
console.log(displayedSolution, solutionHistory, "CHECK")
try { try {
if (task.type === TaskType.FILE) { if (task.type === TaskType.FILE) {
setAnswer("");
setSelectedFile(null); setSelectedFile(null);
setSelectedSolutionUrl(displayedSolution.content); setSelectedSolutionUrl(displayedSolution.content);
} else { }
else {
setSelectedFile(null);
setSelectedSolutionUrl(null);
const response = await fetch(displayedSolution.content); const response = await fetch(displayedSolution.content);
if (!response.ok) { if (!response.ok) {
throw new Error(`Failed to fetch solution content: ${response.status}`); throw new Error(`Failed to fetch solution content: ${response.status}`);
} }
const text = await response.text(); const text = await response.text();
setAnswer(text); setAnswer(text);
} }
} catch (error) { } catch (error) {
console.error('Error loading solution content:', error); console.error('Error loading solution content:', error);
} }
}; };
loadSolutionContent(); loadSolutionContent();
}, [displayedSolution, task.type, setAnswer, setSelectedFile]); }, [displayedSolution, setAnswer, setSelectedFile]);
const handleOpenHistory = () => { const handleOpenHistory = () => {
setIsHistoryOpen(true); setIsHistoryOpen(true);