fix: competition page

This commit is contained in:
moolcoov
2025-03-01 13:39:47 +03:00
parent 65f73fb4a0
commit 4202ad5776
12 changed files with 72 additions and 125 deletions
@@ -0,0 +1,47 @@
import { Competition, CompetitionStatus } from "@/shared/types";
import { cn } from "@/shared/lib/utils";
import { Card, CardContent } from "@/components/ui/card";
interface CompetitionCardProps {
competition: Competition;
className?: string;
}
export function CompetitionCard({
competition,
className,
}: CompetitionCardProps) {
return (
<Card
className={cn(
"aspect-square h-full max-h-80 w-auto overflow-hidden",
className,
)}
>
<div className="relative h-full overflow-hidden">
<img
src={competition.imageUrl}
alt={competition.name}
className="h-full w-full object-cover object-center"
/>
</div>
<CardContent>
<div className="flex flex-col gap-2.5">
<div className="text-muted-foreground flex items-center gap-2 *:text-sm *:font-semibold">
<span>{competition.isOlympics ? "Олимпиада" : "Тренировка"}</span>
{competition.status != CompetitionStatus.NotParticipating && (
<>
<span></span>
<span className="text-primary-foreground">
{competition.status}
</span>
</>
)}
</div>
<h3 className="text-xl font-semibold">{competition.name}</h3>
</div>
</CardContent>
</Card>
);
}
@@ -0,0 +1,20 @@
import { Card, CardContent, CardFooter } from "@/components/ui/card";
import { Skeleton } from "@/components/ui/skeleton";
const CompetitionSkeleton = () => {
return (
<Card className="overflow-hidden">
<Skeleton className="h-48 w-full" />
<CardContent className="p-4 pt-5">
<Skeleton className="h-6 w-3/4 mb-2" />
<Skeleton className="h-6 w-1/2" />
</CardContent>
<CardFooter className="p-4 pt-0 flex gap-2">
<Skeleton className="h-6 w-20" />
<Skeleton className="h-6 w-24" />
</CardFooter>
</Card>
);
}
export default CompetitionSkeleton
@@ -0,0 +1,26 @@
import { cn } from "@/shared/lib/utils";
import { Badge } from "@/components/ui/badge";
interface CompetitionTagProps {
label: string;
variant: 'olympics' | 'status';
className?: string;
}
const CompetitionTag = ({ label, variant, className }: CompetitionTagProps) => {
return (
<Badge
variant="secondary"
className={cn(
"text-xs font-medium",
variant === 'olympics' && "bg-yellow-400 text-yellow-800 hover:bg-yellow-500 font-hse-sans",
variant === 'status' && "bg-black text-white hover:bg-gray-800 font-hse-sans",
className
)}
>
{label}
</Badge>
);
}
export default CompetitionTag
@@ -0,0 +1,122 @@
import { useState, useEffect } from "react";
import { Competition, CompetitionStatus } from "@/shared/types";
import { CompetitionGrid } from "./modules/CompetitionGrid";
import { Tabs, TabsList, TabsTrigger } from "@/components/ui/tabs";
const mockCompetitions: Competition[] = [
{
id: "1",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: true,
status: CompetitionStatus.InProgress,
},
{
id: "2",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: false,
status: CompetitionStatus.NotParticipating,
},
{
id: "3",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: false,
status: CompetitionStatus.InProgress,
},
{
id: "4",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: true,
status: CompetitionStatus.Completed,
},
{
id: "5",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: false,
status: CompetitionStatus.Completed,
},
{
id: "6",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: true,
status: CompetitionStatus.NotParticipating,
},
{
id: "6",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: true,
status: CompetitionStatus.NotParticipating,
},
{
id: "6",
name: "Олимпиада DANO 2025. Индивидуальный этап",
imageUrl: "/DANO.png",
isOlympics: true,
status: CompetitionStatus.NotParticipating,
},
];
const CompetitionsPage = () => {
const [competitions] = useState<Competition[]>(mockCompetitions);
const [activeTab, setActiveTab] = useState("ongoing");
const myCompetitions = competitions.filter(
(comp) =>
comp.status === CompetitionStatus.InProgress ||
comp.status === CompetitionStatus.Completed,
);
const filteredMyCompetitions = myCompetitions.filter((comp) =>
activeTab === "ongoing"
? comp.status === CompetitionStatus.InProgress
: comp.status === CompetitionStatus.Completed,
);
const availableCompetitions = competitions.filter(
(comp) => comp.status === "Не участвую",
);
return (
<div className="flex flex-col gap-8">
<Section>
<SectionHeader>
<SectionTitle>Мои события</SectionTitle>
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList>
<TabsTrigger value="ongoing">В процессе</TabsTrigger>
<TabsTrigger value="completed">Завершенные</TabsTrigger>
</TabsList>
</Tabs>
</SectionHeader>
<CompetitionGrid competitions={filteredMyCompetitions} />
</Section>
<Section>
<SectionHeader>
<SectionTitle>События</SectionTitle>
</SectionHeader>
<CompetitionGrid competitions={availableCompetitions} />
</Section>
</div>
);
};
const Section = ({ children }: { children: React.ReactNode }) => {
return <div className="flex flex-col gap-8">{children}</div>;
};
const SectionHeader = ({ children }: { children: React.ReactNode }) => {
return <div className="flex h-[58px] items-center gap-2">{children}</div>;
};
const SectionTitle = ({ children }: { children: React.ReactNode }) => {
return <h1 className="w-full text-3xl font-semibold">{children}</h1>;
};
export default CompetitionsPage;
@@ -0,0 +1,19 @@
import { Competition } from "@/shared/types";
import { CompetitionCard } from "../../components/CompetitionCard";
import { Link } from "react-router";
interface CompetitionGridProps {
competitions: Competition[];
}
export function CompetitionGrid({ competitions }: CompetitionGridProps) {
return (
<div className="grid grid-cols-3 gap-9">
{competitions.map((competition) => (
<Link key={competition.id} to={`/competitions/${competition.id}`}>
<CompetitionCard competition={competition} />
</Link>
))}
</div>
);
}