mirror of
https://gitlab.com/megazordpobeda/DataRush.git
synced 2026-05-22 23:17:09 +00:00
Merge remote-tracking branch 'origin/master'
This commit is contained in:
@@ -2,40 +2,57 @@ import { useParams, Link, useNavigate } from "react-router-dom";
|
|||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import { mockTasks } from "@/shared/mocks/mocks";
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
import { getCompetition, startCompetition } from "@/shared/api/competitions";
|
||||||
import { getCompetition } from "@/shared/api/competitions";
|
import { getCompetitionTasks } from "@/shared/api/session";
|
||||||
import { Loading } from "@/components/ui/loading";
|
import { Loading } from "@/components/ui/loading";
|
||||||
|
|
||||||
const CompetitionPage = () => {
|
const CompetitionPage = () => {
|
||||||
const { id } = useParams<{ id: string }>();
|
const { id } = useParams<{ id: string }>();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const competitionId = id || "";
|
||||||
|
|
||||||
const { data: competition, isLoading } = useQuery({
|
const competitionQuery = useQuery({
|
||||||
queryKey: ["competition", id],
|
queryKey: ["competition", competitionId],
|
||||||
queryFn: async () => getCompetition(id || ""),
|
queryFn: () => getCompetition(competitionId),
|
||||||
|
enabled: !!competitionId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) {
|
const startMutation = useMutation({
|
||||||
|
mutationFn: () => startCompetition(competitionId),
|
||||||
|
onSuccess: async () => {
|
||||||
|
try {
|
||||||
|
const tasks = await getCompetitionTasks(competitionId);
|
||||||
|
|
||||||
|
if (tasks && tasks.length > 0) {
|
||||||
|
navigate(`/competition/${competitionId}/tasks/${tasks[0].id}`);
|
||||||
|
} else {
|
||||||
|
navigate(`/competition/${competitionId}/tasks`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to fetch tasks:", error);
|
||||||
|
navigate(`/competition/${competitionId}/tasks`);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onError: (error) => {
|
||||||
|
console.error("Failed to start competition:", error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleStart = () => {
|
||||||
|
startMutation.mutate();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (competitionQuery.isLoading) {
|
||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!id || !competition) {
|
if (!competitionId || !competitionQuery.data) {
|
||||||
return <></>;
|
return <></>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleContinue = () => {
|
const competition = competitionQuery.data;
|
||||||
if (competition?.id) {
|
|
||||||
if (mockTasks && mockTasks.length > 0) {
|
|
||||||
const firstTaskId = mockTasks[0].id;
|
|
||||||
navigate(`/competition/${competition.id}/tasks/${firstTaskId}`);
|
|
||||||
} else {
|
|
||||||
navigate(`/competition/${competition.id}/tasks`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log(competition)
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-4">
|
<div className="flex flex-col gap-4">
|
||||||
<Link
|
<Link
|
||||||
@@ -47,13 +64,15 @@ const CompetitionPage = () => {
|
|||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="flex flex-col gap-6">
|
<div className="flex flex-col gap-6">
|
||||||
<div className="aspect-2 h-auto w-full overflow-hidden rounded-xl">
|
{competition.image_url && (
|
||||||
<img
|
<div className="aspect-2 h-auto w-full overflow-hidden rounded-xl">
|
||||||
src={competition.image_url ? competition.image_url : '/DANO.png'}
|
<img
|
||||||
alt={competition.title}
|
src={competition.image_url}
|
||||||
className="h-full w-full object-cover object-center"
|
alt={competition.title}
|
||||||
/>
|
className="h-full w-full object-cover object-center"
|
||||||
</div>
|
/>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
<div className="flex flex-col-reverse gap-8 md:flex-row">
|
<div className="flex flex-col-reverse gap-8 md:flex-row">
|
||||||
<div className="flex flex-1 flex-col gap-5">
|
<div className="flex flex-1 flex-col gap-5">
|
||||||
@@ -65,8 +84,12 @@ const CompetitionPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full *:w-full md:w-96">
|
<div className="w-full *:w-full md:w-96">
|
||||||
<Button size={"lg"} onClick={handleContinue}>
|
<Button
|
||||||
Приступить к выполнению
|
size={"lg"}
|
||||||
|
onClick={handleStart}
|
||||||
|
disabled={startMutation.isPending}
|
||||||
|
>
|
||||||
|
{startMutation.isPending ? "Загрузка..." : "Начать"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -75,4 +98,4 @@ const CompetitionPage = () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default CompetitionPage;
|
export default CompetitionPage;
|
||||||
@@ -20,13 +20,11 @@ export function CompetitionCard({
|
|||||||
className={cn("aspect-square h-full w-auto overflow-hidden", className)}
|
className={cn("aspect-square h-full w-auto overflow-hidden", className)}
|
||||||
>
|
>
|
||||||
<div className="relative h-full overflow-hidden">
|
<div className="relative h-full overflow-hidden">
|
||||||
{competition.image_url && (
|
<img
|
||||||
<img
|
src={competition.image_url ? competition.image_url : '/DANO.png'}
|
||||||
src={competition.image_url ? competition.image_url : '/DANO.png'}
|
alt={competition.title}
|
||||||
alt={competition.title}
|
className="h-full w-full object-cover object-center"
|
||||||
className="h-full w-full object-cover object-center"
|
/>
|
||||||
/>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
|||||||
@@ -12,3 +12,9 @@ export const getCompetitions = async (participating?: boolean) => {
|
|||||||
export const getCompetition = async (id: string) => {
|
export const getCompetition = async (id: string) => {
|
||||||
return await userFetch<Competition>(`/competition/${id}`);
|
return await userFetch<Competition>(`/competition/${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const startCompetition = async (competitionId: string) => {
|
||||||
|
return await userFetch(`/competitions/${competitionId}/start`, {
|
||||||
|
method: 'POST'
|
||||||
|
});
|
||||||
|
};
|
||||||
@@ -1,82 +1,29 @@
|
|||||||
import { apiFetch } from './index';
|
import { userFetch } from ".";
|
||||||
import { Task, TaskStatus } from '@/shared/types';
|
import { Task } from "../types/task";
|
||||||
|
|
||||||
interface ApiTask {
|
export const getCompetitionTasks = async (competitionId: string) => {
|
||||||
id: string;
|
return await userFetch<Task[]>(`/competitions/${competitionId}/tasks`);
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
type: 'input' | 'file' | 'code';
|
|
||||||
in_competition_position: number;
|
|
||||||
points: number;
|
|
||||||
status?: TaskStatus;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetches tasks for a specific competition
|
|
||||||
* @param competitionId - The ID of the competition
|
|
||||||
* @returns Promise with an array of tasks in the application's format
|
|
||||||
*/
|
|
||||||
export const getCompetitionTasks = async (competitionId: string): Promise<Task[]> => {
|
|
||||||
try {
|
|
||||||
const apiTasks: ApiTask[] = await apiFetch(`/api/v1/competitions/${competitionId}/tasks`);
|
|
||||||
|
|
||||||
// Transform API tasks to application Task format
|
|
||||||
return apiTasks.map(apiTask => transformApiTask(apiTask));
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to fetch competition tasks:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
export const submitTaskSolution = async (
|
||||||
* Transforms an API task to the application's Task format
|
competitionId: string,
|
||||||
*/
|
taskId: string,
|
||||||
const transformApiTask = (apiTask: ApiTask): Task => {
|
solution: string | File
|
||||||
return {
|
) => {
|
||||||
id: apiTask.id,
|
const endpoint = `/competitions/${competitionId}/tasks/${taskId}/submit`;
|
||||||
number: String(apiTask.in_competition_position),
|
|
||||||
status: apiTask.status || TaskStatus.Uncleared,
|
|
||||||
solutionType: apiTask.type,
|
|
||||||
description: apiTask.description,
|
|
||||||
maxScore: apiTask.points
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// export const submitTaskSolution = async (
|
|
||||||
// competitionId: string,
|
|
||||||
// taskId: string,
|
|
||||||
// solution: string | File
|
|
||||||
// ): Promise<void> => {
|
|
||||||
// const endpoint = `/api/v1/competitions/${competitionId}/tasks/${taskId}/submit`;
|
|
||||||
|
|
||||||
// // Handle different solution types
|
if (typeof solution === 'string') {
|
||||||
// if (typeof solution === 'string') {
|
return await userFetch(endpoint, {
|
||||||
// // Text or code solution
|
method: 'POST',
|
||||||
// await apiFetch(endpoint, {
|
body: { answer: solution }
|
||||||
// method: 'POST',
|
});
|
||||||
// body: { answer: solution }
|
} else {
|
||||||
// });
|
const formData = new FormData();
|
||||||
// } else {
|
formData.append('file', solution);
|
||||||
// // File solution
|
|
||||||
// const formData = new FormData();
|
|
||||||
// formData.append('file', solution);
|
|
||||||
|
|
||||||
// await apiFetch(endpoint, {
|
return await userFetch(endpoint, {
|
||||||
// method: 'POST',
|
method: 'POST',
|
||||||
// body: formData
|
body: formData
|
||||||
// });
|
});
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the status of a task submission
|
|
||||||
* This would be used to poll for updates after submission
|
|
||||||
*/
|
|
||||||
// export const getTaskSubmissionStatus = async (
|
|
||||||
// competitionId: string,
|
|
||||||
// taskId: string
|
|
||||||
// ): Promise<TaskStatus> => {
|
|
||||||
// const response = await apiFetch(`/api/v1/competitions/${competitionId}/tasks/${taskId}/status`);
|
|
||||||
// return response.status;
|
|
||||||
// };
|
|
||||||
Reference in New Issue
Block a user