"use client"; import { User, MapPin } from "lucide-react"; import { shortAddress } from "./constants"; import type { ScheduleDayMerged, ScheduleClassWithLocation } from "./constants"; interface ScheduleGroup { trainer: string; type: string; level?: string; hasSlots: boolean; recruiting: boolean; location?: string; locationAddress?: string; slots: { day: string; dayShort: string; time: string }[]; } function buildGroups(days: ScheduleDayMerged[]): ScheduleGroup[] { const map = new Map(); for (const day of days) { for (const cls of day.classes as ScheduleClassWithLocation[]) { // Use groupId if available, otherwise fall back to trainer+type+location const locPart = cls.locationName ?? ""; const key = cls.groupId ? `${cls.groupId}||${locPart}` : `${cls.trainer}||${cls.type}||${locPart}`; const existing = map.get(key); if (existing) { existing.slots.push({ day: day.day, dayShort: day.dayShort, time: cls.time }); if (cls.hasSlots) existing.hasSlots = true; if (cls.recruiting) existing.recruiting = true; if (cls.level && !existing.level) existing.level = cls.level; } else { map.set(key, { trainer: cls.trainer, type: cls.type, level: cls.level, hasSlots: !!cls.hasSlots, recruiting: !!cls.recruiting, location: cls.locationName, locationAddress: cls.locationAddress, slots: [{ day: day.day, dayShort: day.dayShort, time: cls.time }], }); } } } return Array.from(map.values()); } /** Group slots by day, then merge days that share identical time sets */ function mergeSlotsByDay(slots: { day: string; dayShort: string; time: string }[]): { days: string[]; times: string[] }[] { // Step 1: collect times per day const dayMap = new Map(); const dayOrder: string[] = []; for (const s of slots) { const existing = dayMap.get(s.day); if (existing) { if (!existing.times.includes(s.time)) existing.times.push(s.time); } else { dayMap.set(s.day, { dayShort: s.dayShort, times: [s.time] }); dayOrder.push(s.day); } } // Sort times within each day for (const entry of dayMap.values()) entry.times.sort(); // Step 2: merge days with identical time sets const result: { days: string[]; times: string[] }[] = []; const used = new Set(); for (const day of dayOrder) { if (used.has(day)) continue; const entry = dayMap.get(day)!; const timeKey = entry.times.join("|"); const days = [entry.dayShort]; used.add(day); for (const other of dayOrder) { if (used.has(other)) continue; const o = dayMap.get(other)!; if (o.times.join("|") === timeKey) { days.push(o.dayShort); used.add(other); } } result.push({ days, times: entry.times }); } return result; } /** Group schedule groups by trainer for compact display */ function groupByTrainer(groups: ScheduleGroup[]): Map { const map = new Map(); for (const g of groups) { const existing = map.get(g.trainer); if (existing) existing.push(g); else map.set(g.trainer, [g]); } return map; } /** Within a trainer's groups, cluster by class type preserving order */ function groupByType(groups: ScheduleGroup[]): { type: string; groups: ScheduleGroup[] }[] { const result: { type: string; groups: ScheduleGroup[] }[] = []; const map = new Map(); for (const g of groups) { const existing = map.get(g.type); if (existing) { existing.push(g); } else { const arr = [g]; map.set(g.type, arr); result.push({ type: g.type, groups: arr }); } } return result; } interface GroupViewProps { typeDots: Record; filteredDays: ScheduleDayMerged[]; filterType: string | null; setFilterType: (type: string | null) => void; filterTrainer: string | null; setFilterTrainer: (trainer: string | null) => void; showLocation?: boolean; onBook?: (groupInfo: string) => void; } export function GroupView({ typeDots, filteredDays, filterType, setFilterType, filterTrainer, setFilterTrainer, showLocation, onBook, }: GroupViewProps) { const groups = buildGroups(filteredDays); const byTrainer = groupByTrainer(groups); if (groups.length === 0) { return (
Нет занятий по выбранным фильтрам
); } return (
{Array.from(byTrainer.entries()).map(([trainer, trainerGroups]) => { const byType = groupByType(trainerGroups); const totalGroups = trainerGroups.length; return (
{/* Trainer header */} {/* Type → 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 */} {group.level && ( {group.level} )} {group.hasSlots && ( есть места )} {group.recruiting && ( набор )} {/* Location */} {showLocation && group.location && ( {group.location} )} {/* Book button */} {onBook && ( )}
); })}
); })}
); })}
); }