feat: show trainer's groups on profile from schedule data
- Extract classes from schedule matching trainer name - Group by type+time+location, combine days (e.g. ПН, СР) - Display as horizontal scroll cards with time, location, level - Show recruiting badge and address (without city prefix) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -27,7 +27,7 @@ export default function HomePage() {
|
|||||||
locations: content.schedule.locations.length,
|
locations: content.schedule.locations.length,
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<Team data={content.team} />
|
<Team data={content.team} schedule={content.schedule.locations} />
|
||||||
<Classes data={content.classes} />
|
<Classes data={content.classes} />
|
||||||
<Schedule data={content.schedule} classItems={content.classes.items} />
|
<Schedule data={content.schedule} classItems={content.classes.items} />
|
||||||
<Pricing data={content.pricing} />
|
<Pricing data={content.pricing} />
|
||||||
|
|||||||
@@ -6,13 +6,14 @@ import { Reveal } from "@/components/ui/Reveal";
|
|||||||
import { TeamCarousel } from "@/components/sections/team/TeamCarousel";
|
import { TeamCarousel } from "@/components/sections/team/TeamCarousel";
|
||||||
import { TeamMemberInfo } from "@/components/sections/team/TeamMemberInfo";
|
import { TeamMemberInfo } from "@/components/sections/team/TeamMemberInfo";
|
||||||
import { TeamProfile } from "@/components/sections/team/TeamProfile";
|
import { TeamProfile } from "@/components/sections/team/TeamProfile";
|
||||||
import type { SiteContent } from "@/types/content";
|
import type { SiteContent, ScheduleLocation } from "@/types/content";
|
||||||
|
|
||||||
interface TeamProps {
|
interface TeamProps {
|
||||||
data: SiteContent["team"];
|
data: SiteContent["team"];
|
||||||
|
schedule?: ScheduleLocation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function Team({ data: team }: TeamProps) {
|
export function Team({ data: team, schedule }: TeamProps) {
|
||||||
const [activeIndex, setActiveIndex] = useState(0);
|
const [activeIndex, setActiveIndex] = useState(0);
|
||||||
const [showProfile, setShowProfile] = useState(false);
|
const [showProfile, setShowProfile] = useState(false);
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ export function Team({ data: team }: TeamProps) {
|
|||||||
<TeamProfile
|
<TeamProfile
|
||||||
member={team.members[activeIndex]}
|
member={team.members[activeIndex]}
|
||||||
onBack={() => setShowProfile(false)}
|
onBack={() => setShowProfile(false)}
|
||||||
|
schedule={schedule}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { useState, useEffect, useRef, useCallback } from "react";
|
import { useState, useEffect, useRef, useCallback } from "react";
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Award, Scale } from "lucide-react";
|
import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Award, Scale, Clock, MapPin } from "lucide-react";
|
||||||
import type { TeamMember, RichListItem, VictoryItem } from "@/types/content";
|
import type { TeamMember, RichListItem, VictoryItem, ScheduleLocation } from "@/types/content";
|
||||||
|
|
||||||
interface TeamProfileProps {
|
interface TeamProfileProps {
|
||||||
member: TeamMember;
|
member: TeamMember;
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
|
schedule?: ScheduleLocation[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
|
||||||
const [lightbox, setLightbox] = useState<string | null>(null);
|
const [lightbox, setLightbox] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -33,7 +34,28 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
|||||||
const [activeTab, setActiveTab] = useState(victoryTabs[0]?.key ?? 'place');
|
const [activeTab, setActiveTab] = useState(victoryTabs[0]?.key ?? 'place');
|
||||||
const hasExperience = member.experience && member.experience.length > 0;
|
const hasExperience = member.experience && member.experience.length > 0;
|
||||||
const hasEducation = member.education && member.education.length > 0;
|
const hasEducation = member.education && member.education.length > 0;
|
||||||
const hasBio = hasVictories || hasExperience || hasEducation;
|
|
||||||
|
// Extract trainer's groups from schedule, grouped by type + time + location
|
||||||
|
const groupMap = new Map<string, { type: string; time: string; location: string; address: string; days: string[]; level?: string; recruiting?: boolean }>();
|
||||||
|
schedule?.forEach(location => {
|
||||||
|
location.days.forEach(day => {
|
||||||
|
day.classes
|
||||||
|
.filter(c => c.trainer === member.name)
|
||||||
|
.forEach(c => {
|
||||||
|
const key = `${c.type}|${c.time}|${location.name}`;
|
||||||
|
const existing = groupMap.get(key);
|
||||||
|
if (existing) {
|
||||||
|
if (!existing.days.includes(day.dayShort)) existing.days.push(day.dayShort);
|
||||||
|
} else {
|
||||||
|
groupMap.set(key, { type: c.type, time: c.time, location: location.name, address: location.address, days: [day.dayShort], level: c.level, recruiting: c.recruiting });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
const uniqueGroups = Array.from(groupMap.values());
|
||||||
|
const hasGroups = uniqueGroups.length > 0;
|
||||||
|
|
||||||
|
const hasBio = hasVictories || hasExperience || hasEducation || hasGroups;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@@ -146,9 +168,42 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* Groups */}
|
||||||
|
{hasGroups && (
|
||||||
|
<div className={hasVictories ? "mt-8" : ""}>
|
||||||
|
<span className="inline-flex items-center gap-1.5 rounded-full border border-gold/20 bg-gold/5 px-4 py-1.5 text-sm font-medium text-gold">
|
||||||
|
<Clock size={14} />
|
||||||
|
Группы
|
||||||
|
</span>
|
||||||
|
<ScrollRow>
|
||||||
|
{uniqueGroups.map((g, i) => (
|
||||||
|
<div key={i} className="w-44 shrink-0 rounded-xl border border-white/[0.08] bg-white/[0.03] p-3 space-y-1.5">
|
||||||
|
<p className="text-xs font-semibold uppercase tracking-wider text-white/80">{g.type}</p>
|
||||||
|
<div className="flex items-center gap-1.5 text-xs text-white/50">
|
||||||
|
<Clock size={11} />
|
||||||
|
{g.days.join(", ")} · {g.time}
|
||||||
|
</div>
|
||||||
|
<div className="flex items-start gap-1.5 text-xs text-white/40">
|
||||||
|
<MapPin size={11} className="mt-0.5 shrink-0" />
|
||||||
|
<span>{g.location} · {g.address.replace(/^г\.\s*\S+,\s*/, "")}</span>
|
||||||
|
</div>
|
||||||
|
{g.level && (
|
||||||
|
<p className="text-[10px] text-gold/60">{g.level}</p>
|
||||||
|
)}
|
||||||
|
{g.recruiting && (
|
||||||
|
<span className="inline-block rounded-full bg-green-500/15 border border-green-500/30 px-2 py-0.5 text-[10px] text-green-400">
|
||||||
|
Набор открыт
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</ScrollRow>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
{/* Education */}
|
{/* Education */}
|
||||||
{hasEducation && (
|
{hasEducation && (
|
||||||
<div className={hasVictories ? "mt-8" : ""}>
|
<div className={hasVictories || hasGroups ? "mt-8" : ""}>
|
||||||
<span className="inline-flex items-center gap-1.5 rounded-full border border-gold/20 bg-gold/5 px-4 py-1.5 text-sm font-medium text-gold">
|
<span className="inline-flex items-center gap-1.5 rounded-full border border-gold/20 bg-gold/5 px-4 py-1.5 text-sm font-medium text-gold">
|
||||||
<GraduationCap size={14} />
|
<GraduationCap size={14} />
|
||||||
Образование
|
Образование
|
||||||
@@ -163,7 +218,7 @@ export function TeamProfile({ member, onBack }: TeamProfileProps) {
|
|||||||
|
|
||||||
{/* Experience */}
|
{/* Experience */}
|
||||||
{hasExperience && (
|
{hasExperience && (
|
||||||
<div className={hasVictories || hasEducation ? "mt-8" : ""}>
|
<div className={hasVictories || hasGroups || hasEducation ? "mt-8" : ""}>
|
||||||
<span className="inline-flex items-center gap-1.5 rounded-full border border-gold/20 bg-gold/5 px-4 py-1.5 text-sm font-medium text-gold">
|
<span className="inline-flex items-center gap-1.5 rounded-full border border-gold/20 bg-gold/5 px-4 py-1.5 text-sm font-medium text-gold">
|
||||||
<Trophy size={15} />
|
<Trophy size={15} />
|
||||||
Опыт
|
Опыт
|
||||||
|
|||||||
Reference in New Issue
Block a user