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 { 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 { Link } from "react-router";
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetClose
} from "@/components/ui/sheet";
const Header = () => { const Header = () => {
const [isProfileOpen, setIsProfileOpen] = useState(false);
return ( return (
<header className="bg-card sticky top-0 z-30 flex h-[72px] w-full items-center justify-center"> <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"> <div className="flex w-full max-w-5xl items-center justify-between">
<Link to="/"> <Link to="/">
<DataRush /> <DataRush />
</Link> </Link>
<div className="flex items-center gap-1"> <div
<span className="text-lg font-semibold">itqdev</span> 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} /> <ChevronDown size={20} />
</div> </div>
</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> </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 }; export { Header };
@@ -9,15 +9,15 @@ interface SolutionStatusProps {
const SolutionStatus: React.FC<SolutionStatusProps> = ({ solution }) => { const SolutionStatus: React.FC<SolutionStatusProps> = ({ solution }) => {
const getStatusText = (status: TaskStatus, score?: number, maxScore?: number) => { const getStatusText = (status: TaskStatus, score?: number, maxScore?: number) => {
switch (status) { switch (status) {
case 'checking': case TaskStatus.Checking:
return 'На проверке'; return 'На проверке';
case 'wrong': case TaskStatus.Wrong:
return 'Неверный ответ'; return 'Неверный ответ';
case 'correct': case TaskStatus.Correct:
return `Зачтено ${maxScore}/${maxScore} баллов`; return `Зачтено ${maxScore}/${maxScore} баллов`;
case 'partial': case TaskStatus.Partial:
return `Зачтено ${score}/${maxScore} баллов`; return `Зачтено ${score}/${maxScore} баллов`;
case 'uncleared': case TaskStatus.Uncleared:
return 'Не решено'; return 'Не решено';
default: default:
return ''; return '';
@@ -1,21 +1,21 @@
import { TaskStatus } from "@/shared/types"; import { TaskStatus } from "@/shared/types";
const getTaskBgColor = (status: TaskStatus): string => { const getTaskBgColor = (status: TaskStatus): string => {
switch (status) { switch (status) {
case "uncleared": return "bg-[var(--color-task-uncleared)]"; case TaskStatus.Uncleared: return "bg-[var(--color-task-uncleared)]";
case "checking": return "bg-[var(--color-task-checking)]"; case TaskStatus.Checking: return "bg-[var(--color-task-checking)]";
case "correct": return "bg-[var(--color-task-correct)]"; case TaskStatus.Correct: return "bg-[var(--color-task-correct)]";
case "partial": return "bg-[var(--color-task-partial)]"; case TaskStatus.Partial: return "bg-[var(--color-task-partial)]";
case "wrong": return "bg-[var(--color-task-wrong)]"; case TaskStatus.Wrong: return "bg-[var(--color-task-wrong)]";
} }
}; };
const getTaskTextColor = (status: TaskStatus): string => { const getTaskTextColor = (status: TaskStatus): string => {
switch (status) { switch (status) {
case "uncleared": return "text-[var(--color-task-text-uncleared)]"; case TaskStatus.Uncleared: return "text-[var(--color-task-text-uncleared)]";
case "checking": return "text-[var(--color-task-text-checking)]"; case TaskStatus.Checking: return "text-[var(--color-task-text-checking)]";
case "correct": return "text-[var(--color-task-text-correct)]"; case TaskStatus.Correct: return "text-[var(--color-task-text-correct)]";
case "partial": return "text-[var(--color-task-text-partial)]"; case TaskStatus.Partial: return "text-[var(--color-task-text-partial)]";
case "wrong": return "text-[var(--color-task-text-wrong)]"; 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[] = [ const mockCompetitions: Competition[] = [
{ {
@@ -56,49 +56,49 @@ const mockTasks: Task[] = [
{ {
id: "1", id: "1",
number: "1.1", number: "1.1",
status: "uncleared", status: TaskStatus.Uncleared,
solutionType: "input" solutionType: "input"
}, },
{ {
id: "2", id: "2",
number: "1.2", number: "1.2",
status: "checking", status: TaskStatus.Checking,
solutionType: "file" solutionType: "file"
}, },
{ {
id: "3", id: "3",
number: "1.3", number: "1.3",
status: "correct", status: TaskStatus.Correct,
solutionType: "code" solutionType: "code"
}, },
{ {
id: "4", id: "4",
number: "2.1", number: "2.1",
status: "partial", status: TaskStatus.Partial,
solutionType: "input" solutionType: "input"
}, },
{ {
id: "5", id: "5",
number: "2.2", number: "2.2",
status: "wrong", status: TaskStatus.Wrong,
solutionType: "file" solutionType: "file"
}, },
{ {
id: "6", id: "6",
number: "2.3", number: "2.3",
status: "uncleared", status: TaskStatus.Uncleared,
solutionType: "code" solutionType: "code"
}, },
{ {
id: "7", id: "7",
number: "3.1", number: "3.1",
status: "checking", status: TaskStatus.Checking,
solutionType: "file" solutionType: "file"
}, },
{ {
id: "8", id: "8",
number: "3.2", number: "3.2",
status: "correct", status: TaskStatus.Correct,
solutionType: "input" solutionType: "input"
}, },
]; ];
@@ -107,26 +107,26 @@ const mockTasks: Task[] = [
const mockSolutions: Solution[] = [ const mockSolutions: Solution[] = [
{ {
id: '1', id: '1',
status: 'wrong', status: TaskStatus.Wrong,
date: '1 марта, 08:41', date: '1 марта, 08:41',
}, },
{ {
id: '2', id: '2',
status: 'partial', status: TaskStatus.Partial,
score: 5, score: 5,
maxScore: 10, maxScore: 10,
date: '28 февраля, 15:22', date: '28 февраля, 15:22',
}, },
{ {
id: '3', id: '3',
status: 'correct', status: TaskStatus.Correct,
score: 0, score: 0,
maxScore: 10, maxScore: 10,
date: '27 февраля, 12:10', date: '27 февраля, 12:10',
}, },
{ {
id: '4', id: '4',
status: 'checking', status: TaskStatus.Checking,
date: '1 марта, 08:41', date: '1 марта, 08:41',
}, },
+10 -3
View File
@@ -4,6 +4,14 @@ enum CompetitionStatus {
Completed = "Завершено", Completed = "Завершено",
} }
enum TaskStatus {
Uncleared = "uncleared",
Checking = "checking",
Correct = "correct",
Partial = "partial",
Wrong = "wrong"
}
interface Competition { interface Competition {
id: string; id: string;
name: string; name: string;
@@ -13,7 +21,6 @@ interface Competition {
description?: string; description?: string;
} }
type TaskStatus = "uncleared" | "checking" | "correct" | "partial" | "wrong";
type SolutionType = "input" | "file" | "code"; type SolutionType = "input" | "file" | "code";
interface Solution { interface Solution {
@@ -30,5 +37,5 @@ interface Task {
solutionType: SolutionType; solutionType: SolutionType;
} }
export { CompetitionStatus }; export { CompetitionStatus, TaskStatus };
export type { Solution, Competition, TaskStatus, Task }; 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