Merge remote-tracking branch 'origin/master'

This commit is contained in:
Андрей Сумин
2025-03-01 19:51:08 +03:00
6 changed files with 120 additions and 59 deletions
@@ -1,21 +1,99 @@
import React, { useState } from 'react';
import { DataRush } from "@/components/ui/icons/datarush";
import { ChevronDown } from "lucide-react";
import { ChevronDown, User, Settings, BarChart2, LogOut } from "lucide-react";
import { Link } from "react-router";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetClose
} from "@/components/ui/sheet";
const Header = () => {
const [isProfileOpen, setIsProfileOpen] = useState(false);
return (
<header className="bg-card sticky top-0 z-30 flex h-[72px] w-full items-center justify-center">
<div className="flex w-full max-w-5xl items-center justify-between">
<Link to="/">
<DataRush />
</Link>
<div className="flex items-center gap-1">
<span className="text-lg font-semibold">itqdev</span>
<div
className="flex items-center gap-1 cursor-pointer hover:opacity-80 transition-opacity px-2 py-1 rounded-md"
onClick={() => setIsProfileOpen(true)}
>
<span className="text-lg font-semibold font-hse-sans">itqdev</span>
<ChevronDown size={20} />
</div>
</div>
<Sheet open={isProfileOpen} onOpenChange={setIsProfileOpen}>
<SheetContent className="w-[300px] sm:w-[350px] p-0">
<SheetHeader className="border-b py-4 px-5">
<SheetTitle className="font-hse-sans text-lg font-medium">Профиль</SheetTitle>
</SheetHeader>
<div className="py-4 px-2">
<ProfileOption
icon={<User size={18} />}
label="Ваш профиль"
onClick={() => {
setIsProfileOpen(false);
}}
/>
<ProfileOption
icon={<Settings size={18} />}
label="Настройки"
onClick={() => {
setIsProfileOpen(false);
}}
/>
<ProfileOption
icon={<BarChart2 size={18} />}
label="Статистика"
onClick={() => {
setIsProfileOpen(false);
}}
/>
<div className="border-t mt-2 pt-2">
<ProfileOption
icon={<LogOut size={18} />}
label="Выйти"
onClick={() => {
setIsProfileOpen(false);
}}
/>
</div>
</div>
</SheetContent>
</Sheet>
</header>
);
};
export { Header };
interface ProfileOptionProps {
icon: React.ReactNode;
label: string;
onClick: () => void;
className?: string;
}
const ProfileOption: React.FC<ProfileOptionProps> = ({ icon, label, onClick, className }) => {
return (
<SheetClose asChild>
<button
className={`flex items-center gap-3 w-full px-3 py-2.5 rounded-md text-left transition-colors hover:bg-gray-100 ${className || ''}`}
onClick={onClick}
>
<span className="text-gray-600">{icon}</span>
<span className="font-hse-sans">{label}</span>
</button>
</SheetClose>
);
};
export { Header };
@@ -9,15 +9,15 @@ interface SolutionStatusProps {
const SolutionStatus: React.FC<SolutionStatusProps> = ({ solution }) => {
const getStatusText = (status: TaskStatus, score?: number, maxScore?: number) => {
switch (status) {
case 'checking':
case TaskStatus.Checking:
return 'На проверке';
case 'wrong':
case TaskStatus.Wrong:
return 'Неверный ответ';
case 'correct':
case TaskStatus.Correct:
return `Зачтено ${maxScore}/${maxScore} баллов`;
case 'partial':
case TaskStatus.Partial:
return `Зачтено ${score}/${maxScore} баллов`;
case 'uncleared':
case TaskStatus.Uncleared:
return 'Не решено';
default:
return '';
@@ -1,21 +1,21 @@
import { TaskStatus } from "@/shared/types";
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)]";
case TaskStatus.Uncleared: return "bg-[var(--color-task-uncleared)]";
case TaskStatus.Checking: return "bg-[var(--color-task-checking)]";
case TaskStatus.Correct: return "bg-[var(--color-task-correct)]";
case TaskStatus.Partial: return "bg-[var(--color-task-partial)]";
case TaskStatus.Wrong: return "bg-[var(--color-task-wrong)]";
}
};
const getTaskTextColor = (status: TaskStatus): string => {
switch (status) {
case "uncleared": return "text-[var(--color-task-text-uncleared)]";
case "checking": return "text-[var(--color-task-text-checking)]";
case "correct": return "text-[var(--color-task-text-correct)]";
case "partial": return "text-[var(--color-task-text-partial)]";
case "wrong": return "text-[var(--color-task-text-wrong)]";
case TaskStatus.Uncleared: return "text-[var(--color-task-text-uncleared)]";
case TaskStatus.Checking: return "text-[var(--color-task-text-checking)]";
case TaskStatus.Correct: return "text-[var(--color-task-text-correct)]";
case TaskStatus.Partial: return "text-[var(--color-task-text-partial)]";
case TaskStatus.Wrong: return "text-[var(--color-task-text-wrong)]";
}
};
+13 -13
View File
@@ -1,4 +1,4 @@
import { Competition, CompetitionStatus, Solution, Task } from "../types";
import { Competition, CompetitionStatus, Solution, Task, TaskStatus } from "../types";
const mockCompetitions: Competition[] = [
{
@@ -56,49 +56,49 @@ const mockTasks: Task[] = [
{
id: "1",
number: "1.1",
status: "uncleared",
status: TaskStatus.Uncleared,
solutionType: "input"
},
{
id: "2",
number: "1.2",
status: "checking",
status: TaskStatus.Checking,
solutionType: "file"
},
{
id: "3",
number: "1.3",
status: "correct",
status: TaskStatus.Correct,
solutionType: "code"
},
{
id: "4",
number: "2.1",
status: "partial",
status: TaskStatus.Partial,
solutionType: "input"
},
{
id: "5",
number: "2.2",
status: "wrong",
status: TaskStatus.Wrong,
solutionType: "file"
},
{
id: "6",
number: "2.3",
status: "uncleared",
status: TaskStatus.Uncleared,
solutionType: "code"
},
{
id: "7",
number: "3.1",
status: "checking",
status: TaskStatus.Checking,
solutionType: "file"
},
{
id: "8",
number: "3.2",
status: "correct",
status: TaskStatus.Correct,
solutionType: "input"
},
];
@@ -107,26 +107,26 @@ const mockTasks: Task[] = [
const mockSolutions: Solution[] = [
{
id: '1',
status: 'wrong',
status: TaskStatus.Wrong,
date: '1 марта, 08:41',
},
{
id: '2',
status: 'partial',
status: TaskStatus.Partial,
score: 5,
maxScore: 10,
date: '28 февраля, 15:22',
},
{
id: '3',
status: 'correct',
status: TaskStatus.Correct,
score: 0,
maxScore: 10,
date: '27 февраля, 12:10',
},
{
id: '4',
status: 'checking',
status: TaskStatus.Checking,
date: '1 марта, 08:41',
},
+10 -3
View File
@@ -4,6 +4,14 @@ enum CompetitionStatus {
Completed = "Завершено",
}
enum TaskStatus {
Uncleared = "uncleared",
Checking = "checking",
Correct = "correct",
Partial = "partial",
Wrong = "wrong"
}
interface Competition {
id: string;
name: string;
@@ -13,7 +21,6 @@ interface Competition {
description?: string;
}
type TaskStatus = "uncleared" | "checking" | "correct" | "partial" | "wrong";
type SolutionType = "input" | "file" | "code";
interface Solution {
@@ -30,5 +37,5 @@ interface Task {
solutionType: SolutionType;
}
export { CompetitionStatus };
export type { Solution, Competition, TaskStatus, Task };
export { CompetitionStatus, TaskStatus };
export type { Solution, Competition, Task };
@@ -1,24 +0,0 @@
import { ChevronDown } from "lucide-react";
const Navbar = () => {
return (
<nav className="bg-white border-b border-gray-200 py-3 px-4 fixed top-0 left-0 right-0 z-10">
<div className="container mx-auto flex justify-between items-center">
<div className="flex items-center">
<div className="bg-black px-3 py-2 rounded font-hse-sans">
<span className="font-bold text-yellow-400">DATA</span>
<span className="font-bold text-white">RUSH</span>
</div>
</div>
<div className="flex items-center cursor-pointer">
<span className="mr-2 font-semibold font-hse-sans">itqdev</span>
<ChevronDown size={16} />
</div>
</div>
</nav>
);
};
export default Navbar