diff --git a/src/app/page.tsx b/src/app/page.tsx index c69df1e..6e415ad 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -2,6 +2,7 @@ import { Hero } from "@/components/sections/Hero"; import { Team } from "@/components/sections/Team"; import { About } from "@/components/sections/About"; import { Classes } from "@/components/sections/Classes"; +import { Schedule } from "@/components/sections/Schedule"; import { Pricing } from "@/components/sections/Pricing"; import { FAQ } from "@/components/sections/FAQ"; import { Contact } from "@/components/sections/Contact"; @@ -14,6 +15,7 @@ export default function HomePage() { + diff --git a/src/components/sections/Schedule.tsx b/src/components/sections/Schedule.tsx new file mode 100644 index 0000000..265fe63 --- /dev/null +++ b/src/components/sections/Schedule.tsx @@ -0,0 +1,270 @@ +"use client"; + +import { useState, useMemo } from "react"; +import { MapPin, Clock, User, X, ChevronDown } from "lucide-react"; +import { siteContent } from "@/data/content"; +import { SectionHeading } from "@/components/ui/SectionHeading"; +import { Reveal } from "@/components/ui/Reveal"; + +const TYPE_DOT: Record = { + "Exotic Pole Dance": "bg-[#c9a96e]", + "Pole Dance": "bg-rose-500", + "Body Plastic": "bg-purple-500", + "Трюковые комбинации с пилоном": "bg-amber-500", +}; + +type StatusFilter = "all" | "hasSlots" | "recruiting"; + +export function Schedule() { + const { schedule } = siteContent; + const [locationIndex, setLocationIndex] = useState(0); + const [filterTrainer, setFilterTrainer] = useState(null); + const [filterType, setFilterType] = useState(null); + const [filterStatus, setFilterStatus] = useState("all"); + const [showTrainers, setShowTrainers] = useState(false); + const location = schedule.locations[locationIndex]; + + const { trainers, types, hasAnySlots, hasAnyRecruiting } = useMemo(() => { + const trainerSet = new Set(); + const typeSet = new Set(); + let slots = false; + let recruiting = false; + for (const day of location.days) { + for (const cls of day.classes) { + trainerSet.add(cls.trainer); + typeSet.add(cls.type); + if (cls.hasSlots) slots = true; + if (cls.recruiting) recruiting = true; + } + } + return { + trainers: Array.from(trainerSet).sort(), + types: Array.from(typeSet).sort(), + hasAnySlots: slots, + hasAnyRecruiting: recruiting, + }; + }, [location]); + + const filteredDays = useMemo(() => { + const noFilter = !filterTrainer && !filterType && filterStatus === "all"; + if (noFilter) return location.days; + return location.days + .map((day) => ({ + ...day, + classes: day.classes.filter( + (cls) => + (!filterTrainer || cls.trainer === filterTrainer) && + (!filterType || cls.type === filterType) && + (filterStatus === "all" || + (filterStatus === "hasSlots" && cls.hasSlots) || + (filterStatus === "recruiting" && cls.recruiting)) + ), + })) + .filter((day) => day.classes.length > 0); + }, [location.days, filterTrainer, filterType, filterStatus]); + + const hasActiveFilter = filterTrainer || filterType || filterStatus !== "all"; + + function clearFilters() { + setFilterTrainer(null); + setFilterType(null); + setFilterStatus("all"); + } + + const pillBase = "inline-flex items-center gap-1.5 rounded-full px-3 py-1 text-[11px] font-medium transition-all duration-200 cursor-pointer"; + const pillActive = "bg-[#c9a96e]/20 text-[#a08050] border border-[#c9a96e]/40 dark:text-[#d4b87a] dark:border-[#c9a96e]/30"; + const pillInactive = "border border-neutral-200 text-neutral-500 hover:border-neutral-300 dark:border-white/[0.08] dark:text-white/35 dark:hover:border-white/15"; + + return ( +
+
+ +
+ + {schedule.title} + + + {/* Location tabs */} + +
+ {schedule.locations.map((loc, i) => ( + + ))} +
+
+ + {/* Compact filters */} + +
+ {/* Class types */} + {types.map((type) => ( + + ))} + + {/* Divider */} + + + {/* Status filters */} + {hasAnySlots && ( + + )} + {hasAnyRecruiting && ( + + )} + + {/* Divider */} + + + {/* Trainer dropdown toggle */} + + + {/* Clear */} + {hasActiveFilter && ( + + )} +
+ + {/* Trainer pills — expandable */} + {showTrainers && ( +
+ {trainers.map((trainer) => ( + + ))} +
+ )} +
+
+ + {/* Day columns — full width */} + +
= 7 ? "xl:grid-cols-7" : filteredDays.length >= 4 ? "xl:grid-cols-" + filteredDays.length : "xl:grid-cols-4"}`} + style={filteredDays.length < 4 && filteredDays.length > 0 ? { maxWidth: filteredDays.length * 320 + (filteredDays.length - 1) * 12, marginInline: "auto" } : undefined} + > + {filteredDays.map((day) => ( +
+ {/* Day header */} +
+
+ + {day.dayShort} + + + {day.day} + +
+
+ + {/* Classes */} +
+ {day.classes.map((cls, i) => ( +
+
+
+ + {cls.time} +
+ {cls.hasSlots && ( + + есть места + + )} + {cls.recruiting && ( + + набор + + )} +
+
+ + {cls.trainer} +
+
+
+ + {cls.type} +
+ {cls.level && ( + + {cls.level} + + )} +
+
+ ))} +
+
+ ))} + + {filteredDays.length === 0 && ( +
+ Нет занятий по выбранным фильтрам +
+ )} +
+
+
+ ); +} diff --git a/src/data/content.ts b/src/data/content.ts index fd2cf71..18f4c61 100644 --- a/src/data/content.ts +++ b/src/data/content.ts @@ -307,6 +307,137 @@ export const siteContent: SiteContent = { "В случае болезни, подтверждённой больничным листом, возможно продление срока действия абонемента.", ], }, + schedule: { + title: "Расписание", + locations: [ + { + name: "Притыцкого 62/М", + address: "г. Минск, Притыцкого, 62/М", + days: [ + { + day: "Понедельник", + dayShort: "ПН", + classes: [ + { time: "11:00–12:30", trainer: "Кристина Войтович", type: "Exotic Pole Dance" }, + { time: "18:00–19:30", trainer: "Надежда Сыч", type: "Exotic Pole Dance" }, + { time: "19:30–21:00", trainer: "Екатерина Матлахова", type: "Exotic Pole Dance" }, + { time: "21:00–22:30", trainer: "Кристина Войтович", type: "Exotic Pole Dance" }, + ], + }, + { + day: "Вторник", + dayShort: "ВТ", + classes: [ + { time: "10:00–11:30", trainer: "Анжела Бобко", type: "Pole Dance", recruiting: true }, + { time: "18:00–19:30", trainer: "Ирина Третьякович", type: "Exotic Pole Dance", hasSlots: true }, + { time: "19:30–21:00", trainer: "Ирина Третьякович", type: "Exotic Pole Dance", hasSlots: true }, + { time: "21:00–22:30", trainer: "Виктор Артёмов", type: "Трюковые комбинации с пилоном" }, + ], + }, + { + day: "Среда", + dayShort: "СР", + classes: [ + { time: "18:30–20:00", trainer: "Виктор Артёмов", type: "Трюковые комбинации с пилоном", level: "Продвинутый" }, + { time: "20:00–21:30", trainer: "Алёна Чигилейчик", type: "Exotic Pole Dance" }, + { time: "21:30–22:30", trainer: "Алёна Чигилейчик", type: "Pole Dance" }, + ], + }, + { + day: "Четверг", + dayShort: "ЧТ", + classes: [ + { time: "11:00–12:30", trainer: "Кристина Войтович", type: "Exotic Pole Dance" }, + { time: "18:00–19:30", trainer: "Надежда Сыч", type: "Exotic Pole Dance" }, + { time: "19:30–21:00", trainer: "Екатерина Матлахова", type: "Exotic Pole Dance" }, + { time: "21:00–22:30", trainer: "Кристина Войтович", type: "Exotic Pole Dance" }, + ], + }, + { + day: "Пятница", + dayShort: "ПТ", + classes: [ + { time: "10:00–11:30", trainer: "Анжела Бобко", type: "Pole Dance", recruiting: true }, + { time: "18:00–19:30", trainer: "Ирина Третьякович", type: "Exotic Pole Dance", hasSlots: true }, + { time: "19:30–21:00", trainer: "Ирина Третьякович", type: "Exotic Pole Dance", hasSlots: true }, + { time: "21:00–22:30", trainer: "Виктор Артёмов", type: "Трюковые комбинации с пилоном" }, + ], + }, + { + day: "Суббота", + dayShort: "СБ", + classes: [ + { time: "14:00–15:00", trainer: "Алёна Чигилейчик", type: "Pole Dance" }, + { time: "15:00–16:30", trainer: "Алёна Чигилейчик", type: "Exotic Pole Dance" }, + ], + }, + { + day: "Воскресенье", + dayShort: "ВС", + classes: [ + { time: "12:00–13:30", trainer: "Кристина Войтович", type: "Body Plastic" }, + ], + }, + ], + }, + { + name: "Машерова 17/4", + address: "г. Минск, Машерова, 17/4", + days: [ + { + day: "Понедельник", + dayShort: "ПН", + classes: [ + { time: "18:00–19:00", trainer: "Ирина Карпусь", type: "Exotic Pole Dance" }, + { time: "19:00–20:30", trainer: "Анна Тарыба", type: "Exotic Pole Dance" }, + { time: "20:30–22:00", trainer: "Анна Тарыба", type: "Exotic Pole Dance" }, + ], + }, + { + day: "Вторник", + dayShort: "ВТ", + classes: [ + { time: "18:30–20:00", trainer: "Анастасия Чалей", type: "Exotic Pole Dance" }, + { time: "21:30–23:00", trainer: "Лилия Огурцова", type: "Exotic Pole Dance", hasSlots: true }, + ], + }, + { + day: "Среда", + dayShort: "СР", + classes: [ + { time: "18:00–19:30", trainer: "Ольга Демидова", type: "Pole Dance" }, + { time: "19:30–21:00", trainer: "Ольга Демидова", type: "Body Plastic" }, + ], + }, + { + day: "Четверг", + dayShort: "ЧТ", + classes: [ + { time: "18:00–19:00", trainer: "Ирина Карпусь", type: "Exotic Pole Dance" }, + { time: "19:00–20:30", trainer: "Анна Тарыба", type: "Exotic Pole Dance" }, + { time: "20:30–22:00", trainer: "Анна Тарыба", type: "Exotic Pole Dance" }, + ], + }, + { + day: "Пятница", + dayShort: "ПТ", + classes: [ + { time: "18:30–20:00", trainer: "Анастасия Чалей", type: "Exotic Pole Dance" }, + { time: "21:30–23:00", trainer: "Лилия Огурцова", type: "Exotic Pole Dance", hasSlots: true }, + ], + }, + { + day: "Суббота", + dayShort: "СБ", + classes: [ + { time: "10:30–12:00", trainer: "Елена Тарасевич", type: "Body Plastic" }, + { time: "12:00–13:30", trainer: "Ольга Демидова", type: "Pole Dance" }, + ], + }, + ], + }, + ], + }, contact: { title: "Контакты", addresses: [ diff --git a/src/lib/constants.ts b/src/lib/constants.ts index 05a3977..3b612dc 100644 --- a/src/lib/constants.ts +++ b/src/lib/constants.ts @@ -11,6 +11,7 @@ export const NAV_LINKS: NavLink[] = [ { label: "О нас", href: "#about" }, { label: "Команда", href: "#team" }, { label: "Направления", href: "#classes" }, + { label: "Расписание", href: "#schedule" }, { label: "Стоимость", href: "#pricing" }, { label: "FAQ", href: "#faq" }, { label: "Контакты", href: "#contact" }, diff --git a/src/types/content.ts b/src/types/content.ts index adc25a2..338863f 100644 --- a/src/types/content.ts +++ b/src/types/content.ts @@ -25,6 +25,27 @@ export interface PricingItem { note?: string; } +export interface ScheduleClass { + time: string; + trainer: string; + type: string; + level?: string; + hasSlots?: boolean; + recruiting?: boolean; +} + +export interface ScheduleDay { + day: string; + dayShort: string; + classes: ScheduleClass[]; +} + +export interface ScheduleLocation { + name: string; + address: string; + days: ScheduleDay[]; +} + export interface ContactInfo { title: string; addresses: string[]; @@ -69,5 +90,9 @@ export interface SiteContent { rentalItems: PricingItem[]; rules: string[]; }; + schedule: { + title: string; + locations: ScheduleLocation[]; + }; contact: ContactInfo; }