"use client"; import { useState, useMemo, useCallback } from "react"; import { BookingModal } from "@/components/ui/BookingModal"; import { CalendarDays, Users, LayoutGrid } from "lucide-react"; import { SectionHeading } from "@/components/ui/SectionHeading"; import { Reveal } from "@/components/ui/Reveal"; import { DayCard } from "./schedule/DayCard"; import { ScheduleFilters } from "./schedule/ScheduleFilters"; import { MobileSchedule } from "./schedule/MobileSchedule"; import { GroupView } from "./schedule/GroupView"; import { buildTypeDots, shortAddress, startTimeMinutes, TIME_PRESETS } from "./schedule/constants"; import type { StatusFilter, TimeFilter, ScheduleDayMerged, ScheduleClassWithLocation } from "./schedule/constants"; import type { SiteContent } from "@/types/content"; type ViewMode = "days" | "groups"; type LocationMode = "all" | number; interface ScheduleProps { data: SiteContent["schedule"]; classItems?: { name: string; color?: string }[]; } export function Schedule({ data: schedule, classItems }: ScheduleProps) { const [locationMode, setLocationMode] = useState("all"); const [viewMode, setViewMode] = useState("days"); const [filterTrainer, setFilterTrainer] = useState(null); const [filterType, setFilterType] = useState(null); const [filterStatus, setFilterStatus] = useState("all"); const [filterTime, setFilterTime] = useState("all"); const [filterDaySet, setFilterDaySet] = useState>(new Set()); const [bookingGroup, setBookingGroup] = useState(null); const isAllMode = locationMode === "all"; const scrollToSchedule = useCallback(() => { const el = document.getElementById("schedule"); if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); }, []); const setFilterTrainerFromCard = useCallback((trainer: string | null) => { setFilterTrainer(trainer); if (trainer) scrollToSchedule(); }, [scrollToSchedule]); const setFilterTypeFromCard = useCallback((type: string | null) => { setFilterType(type); if (type) scrollToSchedule(); }, [scrollToSchedule]); const typeDots = useMemo(() => buildTypeDots(classItems), [classItems]); // Build days: either from one location or merged from all const activeDays: ScheduleDayMerged[] = useMemo(() => { if (locationMode !== "all") { const loc = schedule.locations[locationMode]; if (!loc) return []; return loc.days.map((day) => ({ ...day, classes: day.classes.map((cls) => ({ ...cls })), })); } // Merge all locations by weekday const dayOrder = ["Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота", "Воскресенье"]; const dayMap = new Map(); for (const loc of schedule.locations) { for (const day of loc.days) { const existing = dayMap.get(day.day); const taggedClasses: ScheduleClassWithLocation[] = day.classes.map((cls) => ({ ...cls, locationName: loc.name, locationAddress: loc.address, })); if (existing) { existing.classes = [...existing.classes, ...taggedClasses]; } else { dayMap.set(day.day, { day: day.day, dayShort: day.dayShort, classes: taggedClasses, }); } } } // Sort by weekday order return dayOrder .filter((d) => dayMap.has(d)) .map((d) => dayMap.get(d)!); }, [locationMode, schedule.locations]); const { types, hasAnySlots, hasAnyRecruiting } = useMemo(() => { const typeSet = new Set(); let slots = false; let recruiting = false; for (const day of activeDays) { for (const cls of day.classes) { typeSet.add(cls.type); if (cls.hasSlots) slots = true; if (cls.recruiting) recruiting = true; } } return { types: Array.from(typeSet).sort(), hasAnySlots: slots, hasAnyRecruiting: recruiting, }; }, [activeDays]); // Get the time range for the active time filter const activeTimeRange = filterTime !== "all" ? TIME_PRESETS.find((p) => p.value === filterTime)?.range : null; const filteredDays: ScheduleDayMerged[] = useMemo(() => { const noFilter = !filterTrainer && !filterType && filterStatus === "all" && filterTime === "all" && filterDaySet.size === 0; if (noFilter) return activeDays; // First filter by day names if any selected const dayFiltered = filterDaySet.size > 0 ? activeDays.filter((day) => filterDaySet.has(day.day)) : activeDays; return dayFiltered .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)) && (!activeTimeRange || (() => { const m = startTimeMinutes(cls.time); return m >= activeTimeRange[0] && m < activeTimeRange[1]; })()) ), })) .filter((day) => day.classes.length > 0); }, [activeDays, filterTrainer, filterType, filterStatus, filterTime, activeTimeRange, filterDaySet]); const hasActiveFilter = !!(filterTrainer || filterType || filterStatus !== "all" || filterTime !== "all" || filterDaySet.size > 0); function clearFilters() { setFilterTrainer(null); setFilterType(null); setFilterStatus("all"); setFilterTime("all"); setFilterDaySet(new Set()); } // Available days for the day filter const availableDays = useMemo(() => activeDays.map((d) => ({ day: d.day, dayShort: d.dayShort })), [activeDays] ); function toggleDay(day: string) { setFilterDaySet((prev) => { const next = new Set(prev); if (next.has(day)) next.delete(day); else next.add(day); return next; }); } function switchLocation(mode: LocationMode) { setLocationMode(mode); clearFilters(); } const activeTabClass = "bg-gold text-black shadow-[0_0_20px_rgba(201,169,110,0.3)]"; const inactiveTabClass = "border border-neutral-300 text-neutral-500 hover:border-neutral-400 hover:text-neutral-700 dark:border-white/10 dark:text-neutral-400 dark:hover:text-white dark:hover:border-white/20"; return (
{schedule.title} {/* Location tabs */}
{/* "All studios" tab */} {/* Per-location tabs */} {schedule.locations.map((loc, i) => ( ))}
{/* View mode toggle */}
{/* Compact filters — desktop only */}
{viewMode === "days" ? ( <> {/* Mobile: compact agenda list with tap-to-filter */} {/* Desktop: grid layout */} ) : ( /* Group view: classes clustered by trainer+type */ )} setBookingGroup(null)} groupInfo={bookingGroup ?? undefined} />
); }