Files
blackheart-website/src/components/sections/Classes.tsx
diana.dolgolyova 46ad10e8a0 feat: upgrade schedule with cross-location views, day/time filters, and clickable trainers
- Add "Все студии" tab merging all locations by weekday with location sub-headers
- Location tabs show hall name + address subtitle for clarity
- Add day multi-select and time-of-day preset filters (Утро/День/Вечер) behind collapsible "Когда" button
- Make trainer and type names clickable in day cards for inline filtering
- Add group view clustering classes by trainer+type+location
- Remove trainer dropdown from filter bar — filter by clicking names in schedule
- Add searchable icon picker and lucide-react icon rendering for classes admin/section

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 21:25:11 +03:00

116 lines
4.4 KiB
TypeScript

"use client";
import Image from "next/image";
import { icons } from "lucide-react";
import { SectionHeading } from "@/components/ui/SectionHeading";
import { Reveal } from "@/components/ui/Reveal";
import { ShowcaseLayout } from "@/components/ui/ShowcaseLayout";
import { useShowcaseRotation } from "@/hooks/useShowcaseRotation";
import type { ClassItem, SiteContent } from "@/types";
import { UI_CONFIG } from "@/lib/config";
// kebab "heart-pulse" → PascalCase "HeartPulse"
function toPascal(kebab: string) {
return kebab.split("-").map((s) => s.charAt(0).toUpperCase() + s.slice(1)).join("");
}
function getIcon(key: string) {
const Icon = icons[toPascal(key) as keyof typeof icons];
return Icon ? <Icon size={20} /> : null;
}
interface ClassesProps {
data: SiteContent["classes"];
}
export function Classes({ data: classes }: ClassesProps) {
const { activeIndex, select, setHovering } = useShowcaseRotation({
totalItems: classes.items.length,
autoPlayInterval: UI_CONFIG.showcase.autoPlayInterval,
});
return (
<section id="classes" className="section-glow relative section-padding bg-neutral-100 dark:bg-[#080808]">
<div className="section-divider absolute top-0 left-0 right-0" />
<div className="section-container">
<Reveal>
<SectionHeading centered>{classes.title}</SectionHeading>
</Reveal>
<div className="mt-14">
<Reveal>
<ShowcaseLayout<ClassItem>
items={classes.items}
activeIndex={activeIndex}
onSelect={select}
onHoverChange={setHovering}
renderDetail={(item) => (
<div>
{/* Hero image */}
{item.images && item.images[0] && (
<div className="team-card-glitter relative aspect-[16/9] w-full overflow-hidden rounded-2xl">
<Image
src={item.images[0]}
alt={item.name}
fill
className="object-cover"
/>
{/* Gradient overlay */}
<div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent" />
{/* Icon + name overlay */}
<div className="absolute bottom-0 left-0 right-0 p-6">
<div className="mb-2 inline-flex h-9 w-9 items-center justify-center rounded-lg bg-gold/20 text-gold-light backdrop-blur-sm">
{getIcon(item.icon)}
</div>
<h3 className="text-2xl font-bold text-white">
{item.name}
</h3>
</div>
</div>
)}
{/* Description */}
{item.detailedDescription && (
<div className="mt-5 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 whitespace-pre-line">
{item.detailedDescription}
</div>
)}
</div>
)}
renderSelectorItem={(item, _i, isActive) => (
<div className="flex items-center gap-2 px-3 py-2 lg:gap-3 lg:p-3">
{/* Icon */}
<div
className={`flex h-7 w-7 lg:h-9 lg:w-9 shrink-0 items-center justify-center rounded-lg transition-colors ${
isActive
? "bg-gold/20 text-gold-light"
: "bg-neutral-200/50 text-neutral-500 dark:bg-white/[0.06] dark:text-neutral-400"
}`}
>
{getIcon(item.icon)}
</div>
<div className="min-w-0">
<p
className={`text-xs lg:text-sm font-semibold truncate transition-colors ${
isActive
? "text-gold"
: "text-neutral-700 dark:text-neutral-300"
}`}
>
{item.name}
</p>
<p className="hidden lg:block text-xs text-neutral-500 dark:text-neutral-500 truncate">
{item.description}
</p>
</div>
</div>
)}
/>
</Reveal>
</div>
</div>
</section>
);
}