diff --git a/src/app/page.tsx b/src/app/page.tsx index efcd918..01152f9 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -44,7 +44,7 @@ export default function HomePage() { - + diff --git a/src/components/sections/Schedule.tsx b/src/components/sections/Schedule.tsx index d1b7751..00b195b 100644 --- a/src/components/sections/Schedule.tsx +++ b/src/components/sections/Schedule.tsx @@ -79,9 +79,10 @@ function scheduleReducer(state: ScheduleState, action: ScheduleAction): Schedule interface ScheduleProps { data: SiteContent["schedule"]; classItems?: { name: string; color?: string }[]; + teamMembers?: { name: string; image: string }[]; } -export function Schedule({ data: schedule, classItems }: ScheduleProps) { +export function Schedule({ data: schedule, classItems, teamMembers }: ScheduleProps) { const [state, dispatch] = useReducer(scheduleReducer, initialState); const { locationMode, viewMode, filterTrainer, filterType, filterStatus, filterTime, filterDaySet, bookingGroup } = state; @@ -109,6 +110,16 @@ export function Schedule({ data: schedule, classItems }: ScheduleProps) { const typeDots = useMemo(() => buildTypeDots(classItems), [classItems]); + const trainerPhotos = useMemo(() => { + const map: Record = {}; + if (teamMembers) { + for (const m of teamMembers) { + if (m.image) map[m.name] = m.image; + } + } + return map; + }, [teamMembers]); + // Build days: either from one location or merged from all const activeDays: ScheduleDayMerged[] = useMemo(() => { if (locationMode !== "all") { @@ -395,6 +406,7 @@ export function Schedule({ data: schedule, classItems }: ScheduleProps) { setFilterTrainer={setFilterTrainerFromCard} showLocation={isAllMode} onBook={(v) => dispatch({ type: "SET_BOOKING", value: v })} + trainerPhotos={trainerPhotos} /> )} diff --git a/src/components/sections/Team.tsx b/src/components/sections/Team.tsx index 3f1319c..40cc790 100644 --- a/src/components/sections/Team.tsx +++ b/src/components/sections/Team.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect, useCallback } from "react"; import { SectionHeading } from "@/components/ui/SectionHeading"; import { Reveal } from "@/components/ui/Reveal"; import { TeamCarousel } from "@/components/sections/team/TeamCarousel"; @@ -17,6 +17,27 @@ export function Team({ data: team, schedule }: TeamProps) { const [activeIndex, setActiveIndex] = useState(0); const [showProfile, setShowProfile] = useState(false); + const openTrainerByName = useCallback((name: string) => { + const idx = team.members.findIndex((m) => m.name === name); + if (idx >= 0) { + setActiveIndex(idx); + setShowProfile(true); + setTimeout(() => { + const el = document.getElementById("team"); + if (el) el.scrollIntoView({ behavior: "smooth", block: "start" }); + }, 50); + } + }, [team.members]); + + useEffect(() => { + function handler(e: Event) { + const name = (e as CustomEvent).detail; + if (name) openTrainerByName(name); + } + window.addEventListener("openTrainerProfile", handler); + return () => window.removeEventListener("openTrainerProfile", handler); + }, [openTrainerByName]); + return (
void; showLocation?: boolean; onBook?: (groupInfo: string) => void; + trainerPhotos?: Record; } +const WEEKDAY_NAMES = ["Воскресенье", "Понедельник", "Вторник", "Среда", "Четверг", "Пятница", "Суббота"]; + export function GroupView({ typeDots, filteredDays, @@ -134,10 +139,13 @@ export function GroupView({ setFilterTrainer, showLocation, onBook, + trainerPhotos = {}, }: GroupViewProps) { const groups = buildGroups(filteredDays); const byTrainer = groupByTrainer(groups); + const todayName = useMemo(() => WEEKDAY_NAMES[new Date().getDay()], []); + if (groups.length === 0) { return (
@@ -147,119 +155,144 @@ export function GroupView({ } return ( -
+
{Array.from(byTrainer.entries()).map(([trainer, trainerGroups]) => { const byType = groupByType(trainerGroups); - const totalGroups = trainerGroups.length; + const isActive = filterTrainer === trainer; return ( -
+
{/* Trainer header */} - +
+ {/* Photo — clicks to open trainer bio */} + + {/* Name — clicks to filter */} + +
- {/* Type → Groups */} -
+ {/* Groups */} +
{byType.map(({ type, groups: typeGroups }) => { const dotColor = typeDots[type] ?? "bg-white/30"; - return ( -
- {/* Class type row */} - - - {/* Group rows under this type */} -
- {typeGroups.map((group, gi) => { - const merged = mergeSlotsByDay(group.slots); - - return ( -
- {/* Datetimes */} -
- {merged.map((m, i) => ( - - {i > 0 && ·} - - {m.days.join(", ")} - - - {m.times.join(", ")} - - - ))} -
- - {/* Badges */} +
+ {/* Left: type dot + info */} +
+ {/* Type name */} +
+ {group.level && ( - + {group.level} )} + {hasToday && ( + + + Сегодня + + )} +
+ + {/* Schedule rows */} +
+ {merged.map((m, i) => ( +
+ + {m.days.join(", ")} + + + {m.times.join(", ")} + +
+ ))} +
+ + {/* Bottom badges */} +
{group.hasSlots && ( - + есть места )} {group.recruiting && ( - + набор )} - - {/* Location */} {showLocation && group.location && ( - + {shortAddress(group.locationAddress || group.location)} )} - - {/* Book button */} - {onBook && ( - - )}
- ); - })} +
+ + {/* Right: book button */} + {onBook && ( + + )} +
-
- ); + ); + }); })}