From 24d48a9409b03fe198cdcdb653c3c0f531e2b845 Mon Sep 17 00:00:00 2001 From: "diana.dolgolyova" Date: Wed, 25 Mar 2026 23:16:23 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20improve=20trainer=20bio=20UX=20?= =?UTF-8?q?=E2=80=94=20reorder=20sections,=20collapsible,=20scroll=20arrow?= =?UTF-8?q?s,=20card=20hover?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reorder: Groups → Description → Education → Achievements - Education and Achievements are collapsible (collapsed by default) - Section headings now gold instead of gray - Scroll arrows (left/right) replace fade indicators, always visible - Bigger cards (w-60), wider image thumbnails - Card hover: gold border glow, brighter text, subtle shadow (user side) - Admin cards: hover highlight + focus-within gold border for active editing - Auto-add draft item on blur to prevent data loss --- src/app/admin/_components/FormField.tsx | 4 +- src/components/sections/team/TeamProfile.tsx | 151 +++++++++++++------ 2 files changed, 109 insertions(+), 46 deletions(-) diff --git a/src/app/admin/_components/FormField.tsx b/src/app/admin/_components/FormField.tsx index 3c5119b..7178b7e 100644 --- a/src/app/admin/_components/FormField.tsx +++ b/src/app/admin/_components/FormField.tsx @@ -391,6 +391,7 @@ export function ListField({ label, items, onChange, placeholder }: ListFieldProp value={draft} onChange={(e) => setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); add(); } }} + onBlur={add} placeholder={placeholder || "Добавить..."} className={dashedInput} /> @@ -468,7 +469,7 @@ export function VictoryListField({ label, items, onChange, placeholder, onLinkVa
{items.map((item, i) => ( -
+
setDraft(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { e.preventDefault(); add(); } }} + onBlur={add} placeholder={placeholder || "Добавить..."} className={dashedInput} /> diff --git a/src/components/sections/team/TeamProfile.tsx b/src/components/sections/team/TeamProfile.tsx index a4d0953..4f1e3d0 100644 --- a/src/components/sections/team/TeamProfile.tsx +++ b/src/components/sections/team/TeamProfile.tsx @@ -1,6 +1,6 @@ import { useState, useEffect, useRef, useCallback } from "react"; import Image from "next/image"; -import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Clock, MapPin } from "lucide-react"; +import { ArrowLeft, Instagram, Trophy, GraduationCap, ExternalLink, X, Clock, MapPin, ChevronDown, ChevronLeft, ChevronRight } from "lucide-react"; import type { TeamMember, RichListItem, ScheduleLocation } from "@/types/content"; import { SignupModal } from "@/components/ui/SignupModal"; @@ -164,32 +164,17 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
-
- {/* Victories */} - {hasVictories && ( -
- - - Достижения - - - {member.victories!.map((item, i) => ( - - ))} - -
- )} - - {/* Groups */} +
+ {/* Groups — first, most actionable */} {hasGroups && ( -
- - +
+

+ Группы - +

{uniqueGroups.map((g, i) => ( -
+

{g.type}

{g.merged.map((m, mi) => ( @@ -224,26 +209,33 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) {
)} - {/* Education */} + {/* Description */} + {member.description && ( +

+ {member.description} +

+ )} + + {/* Education — collapsible */} {hasEducation && ( -
- - - Образование - + {member.education!.map((item, i) => ( ))} -
+ )} - {/* Description */} - {member.description && ( -

- {member.description} -

+ {/* Victories — collapsible */} + {hasVictories && ( + + + {member.victories!.map((item, i) => ( + + ))} + + )} {/* Empty state */} @@ -296,10 +288,61 @@ export function TeamProfile({ member, onBack, schedule }: TeamProfileProps) { ); } +function CollapsibleSection({ icon: Icon, title, count, children }: { icon: React.ComponentType<{ size: number }>; title: string; count: number; children: React.ReactNode }) { + const [open, setOpen] = useState(false); + + return ( +
+ +
+
+ {children} +
+
+
+ ); +} + function ScrollRow({ children }: { children: React.ReactNode }) { const scrollRef = useRef(null); const dragState = useRef<{ startX: number; scrollLeft: number } | null>(null); const wasDragged = useRef(false); + const [canScroll, setCanScroll] = useState({ left: false, right: false }); + + const updateScrollState = useCallback(() => { + const el = scrollRef.current; + if (!el) return; + setCanScroll({ + left: el.scrollLeft > 2, + right: el.scrollLeft < el.scrollWidth - el.clientWidth - 2, + }); + }, []); + + useEffect(() => { + updateScrollState(); + const el = scrollRef.current; + if (!el) return; + const ro = new ResizeObserver(updateScrollState); + ro.observe(el); + return () => ro.disconnect(); + }, [updateScrollState]); + + function scrollBy(dir: number) { + scrollRef.current?.scrollBy({ left: dir * 200, behavior: "smooth" }); + } const onPointerDown = useCallback((e: React.PointerEvent) => { const el = scrollRef.current; @@ -321,19 +364,37 @@ function ScrollRow({ children }: { children: React.ReactNode }) { }, []); return ( -
+
{children}
+ {/* Arrow buttons */} + {canScroll.left && ( + + )} + {canScroll.right && ( + + )}
); } @@ -344,27 +405,27 @@ function RichCard({ item, onImageClick }: { item: RichListItem; onImageClick: (s if (hasImage) { return ( -
+
-