From ae04e453f6309770fe6e76fb0db8ef1f92509d32 Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 18:56:01 +0900 Subject: [PATCH 1/5] test fix --- .../components/SolutionHistorySheet/index.tsx | 27 ++--- .../modules/TaskSolution/index.tsx | 107 +++++------------- 2 files changed, 41 insertions(+), 93 deletions(-) diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx index 83d8cda..4a21731 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; -import { X, Check } from "lucide-react"; +import { X } from "lucide-react"; import SolutionStatus from '../SolutionStatus'; -import { Solution } from '@/shared/types/task'; +import { Solution, TaskType } from '@/shared/types/task'; interface SolutionHistorySheetProps { isOpen: boolean; @@ -11,7 +11,6 @@ interface SolutionHistorySheetProps { solutions: Solution[]; maxPoints: number; onSolutionSelect: (solution: Solution) => void; - currentSolutionId?: string | null; } const SolutionHistorySheet: React.FC = ({ @@ -19,10 +18,8 @@ const SolutionHistorySheet: React.FC = ({ onOpenChange, solutions, maxPoints, - onSolutionSelect, - currentSolutionId + onSolutionSelect }) => { - return ( @@ -39,17 +36,15 @@ const SolutionHistorySheet: React.FC = ({
{solutions.length > 0 ? ( - solutions.map((solution) => ( + solutions.map((solution, index) => (
onSolutionSelect(solution)} + key={solution.id || index} + className="w-full cursor-pointer transition-transform hover:scale-[1.01]" + onClick={() => { + onSolutionSelect(solution); + onOpenChange(false); + }} > - {solution.id === currentSolutionId && ( -
- -
- )}
)) @@ -64,4 +59,4 @@ const SolutionHistorySheet: React.FC = ({ ); }; -export default SolutionHistorySheet; \ No newline at end of file +export default SolutionHistorySheet; diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index 06cebdd..6c7899a 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef, useEffect } from 'react'; +import React, { useState, useRef } from 'react'; import { useParams } from 'react-router-dom'; import { Task, TaskType, Solution } from '@/shared/types/task'; import { useQuery } from '@tanstack/react-query'; @@ -30,9 +30,7 @@ const TaskSolution: React.FC = ({ const fileInputRef = useRef(null); const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [selectedSolutionUrl, setSelectedSolutionUrl] = useState(null); - const [currentSolution, setCurrentSolution] = useState(null); const { id: competitionId } = useParams<{ id: string }>(); - const taskIdRef = useRef(null); const solutionsQuery = useQuery({ queryKey: ['solutionHistory', competitionId, task.id], @@ -41,83 +39,45 @@ const TaskSolution: React.FC = ({ }); const solutionHistory = solutionsQuery.data || []; - // Handle task changes - useEffect(() => { - if (taskIdRef.current !== task.id) { - setCurrentSolution(null); - setSelectedSolutionUrl(null); - setAnswer(""); - setSelectedFile(null); - taskIdRef.current = task.id; - - // Wait for the query to complete - if (!solutionsQuery.isLoading && solutionHistory.length > 0) { - // Get the most recent solution (last in the array) - const latestSolution = solutionHistory[solutionHistory.length - 1]; - setCurrentSolution(latestSolution); - } - } - }, [task.id, solutionHistory, solutionsQuery.isLoading, setAnswer, setSelectedFile]); - - // Refresh current solution when the solution history changes (after a new submission) - useEffect(() => { - if (!solutionsQuery.isLoading && solutionHistory.length > 0) { - // If we don't have a current solution or there's a new submission - // (which would be the last item in the array) - if (!currentSolution || - currentSolution.id !== solutionHistory[solutionHistory.length - 1].id) { - // Set to the latest solution (last in the array) - setCurrentSolution(solutionHistory[solutionHistory.length - 1]); - } - } - }, [solutionHistory, currentSolution, solutionsQuery.isLoading]); - - // Load solution content when current solution changes - useEffect(() => { - const loadSolutionContent = async () => { - if (!currentSolution || !currentSolution.content) return; - - try { - if (task.type === TaskType.FILE) { - setSelectedFile(null); - setSelectedSolutionUrl(currentSolution.content); - } else { - const response = await fetch(currentSolution.content); - if (!response.ok) { - throw new Error(`Failed to fetch solution content: ${response.status}`); - } - const text = await response.text(); - setAnswer(text); - } - } catch (error) { - console.error('Error loading solution content:', error); - } - }; - - loadSolutionContent(); - }, [currentSolution, task.type, setAnswer, setSelectedFile]); const handleOpenHistory = () => { setIsHistoryOpen(true); }; - const handleSolutionSelect = (solution: Solution) => { - setCurrentSolution(solution); - setIsHistoryOpen(false); + const latestSolution = solutionHistory && solutionHistory.length > 0 ? solutionHistory[0] : null; + + const handleSolutionSelect = async (solution: Solution) => { + if (!solution.content) return; + + try { + if (task.type === TaskType.FILE) { + // For file tasks, just store the URL + setSelectedFile(null); // Clear any selected file first + setSelectedSolutionUrl(solution.content); + } else { + // For INPUT and CODE tasks, fetch the content and set as answer + const response = await fetch(solution.content); + if (!response.ok) { + throw new Error(`Failed to fetch solution content: ${response.status}`); + } + const text = await response.text(); + setAnswer(text); + } + } catch (error) { + console.error('Error loading solution content:', error); + } }; + // Function to clear the existing file URL const handleClearExistingFile = () => { setSelectedSolutionUrl(null); }; - const handleSubmitWrapper = () => { - onSubmit(); - }; return (
- {currentSolution ? ( - + {latestSolution ? ( + ) : (
Решение еще не отправлено @@ -125,10 +85,7 @@ const TaskSolution: React.FC = ({ )} {task.type === TaskType.INPUT && ( - + )} {task.type === TaskType.FILE && ( @@ -142,14 +99,11 @@ const TaskSolution: React.FC = ({ )} {task.type === TaskType.CODE && ( - + )} @@ -159,10 +113,9 @@ const TaskSolution: React.FC = ({ solutions={solutionHistory} maxPoints={task.points} onSolutionSelect={handleSolutionSelect} - currentSolutionId={currentSolution?.id} />
); }; -export default TaskSolution; \ No newline at end of file +export default TaskSolution; From a44e2f51f201d2e203377ce7a611698501bbddb3 Mon Sep 17 00:00:00 2001 From: ITQ Date: Mon, 3 Mar 2025 13:08:35 +0300 Subject: [PATCH 2/5] (scope): [body] [footer(s)] --- services/backend/apps/task/tasks.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/services/backend/apps/task/tasks.py b/services/backend/apps/task/tasks.py index bb909c2..86588f4 100644 --- a/services/backend/apps/task/tasks.py +++ b/services/backend/apps/task/tasks.py @@ -12,9 +12,7 @@ from apps.task.models import CompetitionTaskSubmission def analyze_data_task(self, submission_id): submission = CompetitionTaskSubmission.objects.get(id=submission_id) try: - code_url = ( - f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{submission.path}" - ) + code_url = f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{submission.content.path}" files = [ { "url": f"{settings.MINIO_DEFAULT_CUSTOM_ENDPOINT_URL}{attachment.path}", From 209f454b3aa1a0b80c5d2176f2f41173baa461fb Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 19:43:04 +0900 Subject: [PATCH 3/5] solution status update --- .../components/SolutionHistorySheet/index.tsx | 20 +++- .../modules/TaskSolution/index.tsx | 109 +++++++++++++----- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx index 4a21731..1abcabb 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionHistorySheet/index.tsx @@ -1,9 +1,9 @@ import React from 'react'; import { Sheet, SheetClose, SheetContent, SheetHeader, SheetTitle } from "@/components/ui/sheet"; import { Button } from "@/components/ui/button"; -import { X } from "lucide-react"; +import { X, Check } from "lucide-react"; import SolutionStatus from '../SolutionStatus'; -import { Solution, TaskType } from '@/shared/types/task'; +import { Solution } from '@/shared/types/task'; interface SolutionHistorySheetProps { isOpen: boolean; @@ -11,6 +11,7 @@ interface SolutionHistorySheetProps { solutions: Solution[]; maxPoints: number; onSolutionSelect: (solution: Solution) => void; + currentSolutionId?: string; } const SolutionHistorySheet: React.FC = ({ @@ -18,7 +19,8 @@ const SolutionHistorySheet: React.FC = ({ onOpenChange, solutions, maxPoints, - onSolutionSelect + onSolutionSelect, + currentSolutionId }) => { return ( @@ -39,12 +41,18 @@ const SolutionHistorySheet: React.FC = ({ solutions.map((solution, index) => (
{ onSolutionSelect(solution); - onOpenChange(false); + onOpenChange(false); }} > + {solution.id === currentSolutionId && ( +
+ +
+ )}
)) @@ -59,4 +67,4 @@ const SolutionHistorySheet: React.FC = ({ ); }; -export default SolutionHistorySheet; +export default SolutionHistorySheet; \ No newline at end of file diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index 6c7899a..3d6352a 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -1,4 +1,4 @@ -import React, { useState, useRef } from 'react'; +import React, { useState, useRef, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { Task, TaskType, Solution } from '@/shared/types/task'; import { useQuery } from '@tanstack/react-query'; @@ -30,7 +30,9 @@ const TaskSolution: React.FC = ({ const fileInputRef = useRef(null); const [isHistoryOpen, setIsHistoryOpen] = useState(false); const [selectedSolutionUrl, setSelectedSolutionUrl] = useState(null); + const [displayedSolution, setDisplayedSolution] = useState(null); const { id: competitionId } = useParams<{ id: string }>(); + const prevTaskIdRef = useRef(null); const solutionsQuery = useQuery({ queryKey: ['solutionHistory', competitionId, task.id], @@ -40,44 +42,85 @@ const TaskSolution: React.FC = ({ const solutionHistory = solutionsQuery.data || []; + // Set initial solution to the last one (most recent) when solutions are loaded + useEffect(() => { + if (solutionHistory.length > 0 && !displayedSolution) { + const latestSolution = solutionHistory[solutionHistory.length - 1]; + setDisplayedSolution(latestSolution); + } + }, [solutionHistory, displayedSolution]); + + // When task changes, reset everything and load the latest solution for the new task + useEffect(() => { + if (prevTaskIdRef.current !== task.id) { + // Reset states for new task + setDisplayedSolution(null); + setSelectedSolutionUrl(null); + + // If solutions are already loaded for the new task, set the latest one + if (solutionHistory.length > 0) { + const latestSolution = solutionHistory[solutionHistory.length - 1]; + setDisplayedSolution(latestSolution); + } + + prevTaskIdRef.current = task.id; + } + }, [task.id, solutionHistory]); + + // Check if a new solution was submitted (latest solution ID changed) + useEffect(() => { + if (solutionHistory.length > 0 && displayedSolution) { + const latestSolution = solutionHistory[solutionHistory.length - 1]; + + // If the latest solution ID is different from the displayed one, + // a new solution was submitted - update to show the latest + if (latestSolution.id !== displayedSolution.id) { + setDisplayedSolution(latestSolution); + } + } + }, [solutionHistory, displayedSolution]); + + // Load solution content when the displayed solution changes + useEffect(() => { + const loadSolutionContent = async () => { + if (!displayedSolution || !displayedSolution.content) return; + + try { + if (task.type === TaskType.FILE) { + setSelectedFile(null); + setSelectedSolutionUrl(displayedSolution.content); + } else { + const response = await fetch(displayedSolution.content); + if (!response.ok) { + throw new Error(`Failed to fetch solution content: ${response.status}`); + } + const text = await response.text(); + setAnswer(text); + } + } catch (error) { + console.error('Error loading solution content:', error); + } + }; + + loadSolutionContent(); + }, [displayedSolution, task.type, setAnswer, setSelectedFile]); + const handleOpenHistory = () => { setIsHistoryOpen(true); }; - const latestSolution = solutionHistory && solutionHistory.length > 0 ? solutionHistory[0] : null; - - const handleSolutionSelect = async (solution: Solution) => { - if (!solution.content) return; - - try { - if (task.type === TaskType.FILE) { - // For file tasks, just store the URL - setSelectedFile(null); // Clear any selected file first - setSelectedSolutionUrl(solution.content); - } else { - // For INPUT and CODE tasks, fetch the content and set as answer - const response = await fetch(solution.content); - if (!response.ok) { - throw new Error(`Failed to fetch solution content: ${response.status}`); - } - const text = await response.text(); - setAnswer(text); - } - } catch (error) { - console.error('Error loading solution content:', error); - } + const handleSolutionSelect = (solution: Solution) => { + setDisplayedSolution(solution); }; - // Function to clear the existing file URL const handleClearExistingFile = () => { setSelectedSolutionUrl(null); }; - return (
- {latestSolution ? ( - + {displayedSolution ? ( + ) : (
Решение еще не отправлено @@ -85,7 +128,10 @@ const TaskSolution: React.FC = ({ )} {task.type === TaskType.INPUT && ( - + )} {task.type === TaskType.FILE && ( @@ -99,7 +145,10 @@ const TaskSolution: React.FC = ({ )} {task.type === TaskType.CODE && ( - + )} = ({ ); }; -export default TaskSolution; +export default TaskSolution; \ No newline at end of file From 284f30b9c088f661f563ad280f04b8a903e61f73 Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 19:58:56 +0900 Subject: [PATCH 4/5] minor fixes --- .../components/CompetitionHeader/index.tsx | 13 ++++++++++++- .../frontend/src/pages/CompetitionSession/index.tsx | 2 ++ .../components/SolutionStatus/index.tsx | 1 + .../modules/TaskSolution/index.tsx | 1 + 4 files changed, 16 insertions(+), 1 deletion(-) diff --git a/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx b/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx index b4eba1b..6d36a6b 100644 --- a/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/components/CompetitionHeader/index.tsx @@ -7,13 +7,23 @@ interface CompetitionHeaderProps { title: string; tasks: Task[]; competitionId: string; + setAnswer: (value: string) => void; + setSelectedFile: (file: File | null) => void; // заглушка } const CompetitionHeader: React.FC = ({ title, tasks, - competitionId + competitionId, + setAnswer, + setSelectedFile }) => { + + const handleTaskSelect = () => { + setAnswer("") + setSelectedFile(null) + } + return (
@@ -21,6 +31,7 @@ const CompetitionHeader: React.FC = ({ diff --git a/services/frontend/src/pages/CompetitionSession/index.tsx b/services/frontend/src/pages/CompetitionSession/index.tsx index 4d0c5f8..eb80913 100644 --- a/services/frontend/src/pages/CompetitionSession/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/index.tsx @@ -95,6 +95,8 @@ const CompetitionSession = () => { title={competitionTitle} tasks={tasks} competitionId={competitionId} + setAnswer={setAnswer} + setSelectedFile={setSelectedFile} />
diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx index b67c5ac..8596c97 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/components/SolutionStatus/index.tsx @@ -11,6 +11,7 @@ const SolutionStatus: React.FC = ({ solution, maxPoints }) => { const formattedDate = solution.timestamp ? format(parseISO(solution.timestamp), "d MMMM, HH:mm", { locale: ru }) : ''; + console.log(solution, "SOLUTION STATUS") return (
diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index 3d6352a..2d0df9e 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -111,6 +111,7 @@ const TaskSolution: React.FC = ({ const handleSolutionSelect = (solution: Solution) => { setDisplayedSolution(solution); + console.log(displayedSolution) }; const handleClearExistingFile = () => { From 61d6f538d8bfdda47c935c2d204093d5516eb030 Mon Sep 17 00:00:00 2001 From: rngsurrounded Date: Mon, 3 Mar 2025 20:02:40 +0900 Subject: [PATCH 5/5] added expired solution bar --- .../modules/TaskSolution/index.tsx | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx index 2d0df9e..87cd253 100644 --- a/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx +++ b/services/frontend/src/pages/CompetitionSession/modules/TaskSolution/index.tsx @@ -9,6 +9,8 @@ import FileSolution from './components/FileSolution'; import CodeSolution from './components/CodeSolution'; import ActionButtons from './components/ActionButtons'; import SolutionHistorySheet from './components/SolutionHistorySheet'; +import { AlertTriangle, ArrowRight } from 'lucide-react'; +import { Button } from '@/components/ui/button'; interface TaskSolutionProps { task: Task; @@ -17,6 +19,7 @@ interface TaskSolutionProps { selectedFile: File | null; setSelectedFile: (file: File | null) => void; onSubmit: () => void; + isSubmitting?: boolean; } const TaskSolution: React.FC = ({ @@ -26,6 +29,7 @@ const TaskSolution: React.FC = ({ selectedFile, setSelectedFile, onSubmit, + isSubmitting = false }) => { const fileInputRef = useRef(null); const [isHistoryOpen, setIsHistoryOpen] = useState(false); @@ -41,6 +45,16 @@ const TaskSolution: React.FC = ({ }); const solutionHistory = solutionsQuery.data || []; + + const getLatestSolution = () => { + return solutionHistory.length > 0 ? solutionHistory[solutionHistory.length - 1] : null; + }; + + const isOutdatedSolution = () => { + if (!displayedSolution || solutionHistory.length === 0) return false; + const latestSolution = getLatestSolution(); + return latestSolution?.id !== displayedSolution.id; + }; // Set initial solution to the last one (most recent) when solutions are loaded useEffect(() => { @@ -111,12 +125,19 @@ const TaskSolution: React.FC = ({ const handleSolutionSelect = (solution: Solution) => { setDisplayedSolution(solution); - console.log(displayedSolution) }; const handleClearExistingFile = () => { setSelectedSolutionUrl(null); }; + + // Function to switch to the latest solution + const goToLatestSolution = () => { + const latestSolution = getLatestSolution(); + if (latestSolution) { + setDisplayedSolution(latestSolution); + } + }; return (
@@ -128,6 +149,25 @@ const TaskSolution: React.FC = ({
)} + {/* Outdated solution warning */} + {isOutdatedSolution() && ( +
+
+ + Устаревшая посылка +
+ +
+ )} + {task.type === TaskType.INPUT && ( = ({ fileInputRef={fileInputRef} existingFileUrl={selectedSolutionUrl} onClearExistingFile={handleClearExistingFile} + isLoading={isSubmitting} /> )} @@ -163,6 +204,7 @@ const TaskSolution: React.FC = ({ solutions={solutionHistory} maxPoints={task.points} onSolutionSelect={handleSolutionSelect} + currentSolutionId={displayedSolution?.id} />
);