import React, { useState, useMemo } from 'react'; import { Link } from '@/spa/router'; import { Trophy, Search, ChevronDown, ChevronRight, X, ArrowRight } from 'lucide-react'; import { WINNERS, type Winner } from '@/spa/data/winners'; import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute'; import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField'; import { useIsMobile } from '@/spa/hooks/useIsMobile'; import Image from '@/spa/components/ui/UnoptimizedImage' import { preistraegerIndexContent } from '@/spa/preistraegerIndexContent'; const FF = '"IBM Plex Sans", sans-serif'; const NAVY = '#111D55'; const GOLD = '#EFBF04'; const BORDER = '#D0D5DD'; const GRAY = '#666666'; const BG_ALT = '#E4E2E3'; type PreistraegerIndexCms = Partial & { hero?: Partial & { image?: unknown } card?: Partial & { fallbackImage?: unknown } } const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback; const normalizeWinner = ( doc: CmsRouteDoc, fallbackImage: string, fallbackWinner: typeof preistraegerIndexContent.card.fallbackWinner, ): Winner => ({ id: String(doc.id), slug: String(doc.slug || ''), name: String(doc.title || fallbackWinner.name || preistraegerIndexContent.card.fallbackWinner.name), category: String(doc.category || fallbackWinner.category || preistraegerIndexContent.card.fallbackWinner.category), year: Number(doc.year || new Date().getFullYear()), type: String(doc.awardType || fallbackWinner.awardType || preistraegerIndexContent.card.fallbackWinner.awardType), img: docImageUrl(doc, fallbackImage), shortDesc: String(doc.shortDesc || doc.description || doc.meta?.description || ''), longDesc: String(doc.longDesc || doc.description || doc.meta?.description || ''), quote: String(doc.quote || ''), quotePerson: String(doc.quotePerson || ''), quoteRole: String(doc.quoteRole || ''), location: String(doc.location || fallbackWinner.location || preistraegerIndexContent.card.fallbackWinner.location), industry: String(doc.industry || fallbackWinner.industry || preistraegerIndexContent.card.fallbackWinner.industry), website: String(doc.website || '#'), hasMedia: Boolean(doc.hasMedia), }); export default function Preistraeger() { const isMobile = useIsMobile(); const [filterCategory, setFilterCategory] = useState(''); const [search, setSearch] = useState(''); const [mediaOnly, setMediaOnly] = useState(false); const cms = (useCmsRoute()?.doc?.preistraegerIndex || {}) as PreistraegerIndexCms; const cmsPreistraeger = useCmsCollection('preistraeger'); const hero = { ...preistraegerIndexContent.hero, ...(cms.hero || {}) }; const breadcrumb = { ...preistraegerIndexContent.breadcrumb, ...(cms.breadcrumb || {}) }; const filters = { ...preistraegerIndexContent.filters, ...(cms.filters || {}) }; const count = { ...preistraegerIndexContent.count, ...(cms.count || {}) }; const empty = { ...preistraegerIndexContent.empty, ...(cms.empty || {}) }; const card = useMemo(() => ({ ...preistraegerIndexContent.card, ...(cms.card || {}) }), [cms.card]); const cta = { ...preistraegerIndexContent.cta, ...(cms.cta || {}) }; const cardFallbackImage = useMemo( () => mediaUrl(card.fallbackImage, `/images/${preistraegerIndexContent.card.fallbackImageFilename}`), [card], ); const winners = useMemo(() => { const fallbackWinner = card.fallbackWinner || preistraegerIndexContent.card.fallbackWinner; if (cmsPreistraeger.length) return cmsPreistraeger.map((doc) => normalizeWinner(doc, cardFallbackImage, fallbackWinner)).filter((winner) => winner.slug); return WINNERS; }, [card, cardFallbackImage, cmsPreistraeger]); const years = useMemo(() => [...new Set(winners.map((winner) => winner.year))].sort((a, b) => b - a), [winners]); const categories = useMemo(() => [...new Set(winners.map((winner) => winner.category))].sort((a, b) => a.localeCompare(b, 'de')), [winners]); const [activeYear, setActiveYear] = useState(undefined); const selectedYear = activeYear ?? years[0] ?? new Date().getFullYear(); const filtered = useMemo(() => winners.filter(w => { if (w.year !== selectedYear) return false; if (filterCategory && w.category !== filterCategory) return false; if (search && !w.name.toLowerCase().includes(search.toLowerCase())) return false; if (mediaOnly && !w.hasMedia) return false; return true; }), [selectedYear, filterCategory, search, mediaOnly, winners]); const reset = () => { setFilterCategory(''); setSearch(''); setMediaOnly(false); }; const hasFilter = !!(filterCategory || search || mediaOnly); return (
{/* Hero */}
{mediaAlt(hero.image,
{/* Breadcrumb */}
{fallbackText(breadcrumb.label, preistraegerIndexContent.breadcrumb.label)}
{/* Filter Section */}
{/* #93: Headline entfernt – Seitenname steht bereits oben im Breadcrumb. */} {/* Year Tabs */}
{years.map(year => ( ))}
{/* Filter Row */}
({ label: c, value: c }))} width={isMobile ? undefined : 280} />
{/* Toggle row */}
{fallbackText(filters.storyHeading, preistraegerIndexContent.filters.storyHeading)}
{fallbackText(filters.mediaIcon, preistraegerIndexContent.filters.mediaIcon)} {fallbackText(filters.mediaLabel, preistraegerIndexContent.filters.mediaLabel)}
{hasFilter && ( )}
{/* Count bar */}
{selectedYear} – {filtered.length} {fallbackText(count.label, preistraegerIndexContent.count.label)}
{/* Grid */} {filtered.length === 0 ? (
{fallbackText(empty.message, preistraegerIndexContent.empty.message)}
) : (
{filtered.map(w => ( ))}
)} {/* Bottom CTA */}

{fallbackText(cta.text, preistraegerIndexContent.cta.text)}

{ (e.currentTarget as HTMLElement).style.background = '#FFD130'; (e.currentTarget as HTMLElement).style.boxShadow = '0 0 18px rgba(239,191,4,0.65), 0 0 40px rgba(239,191,4,0.3)'; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.background = GOLD; (e.currentTarget as HTMLElement).style.boxShadow = 'none'; }} > {fallbackText(cta.primaryCta?.label, preistraegerIndexContent.cta.primaryCta.label)}
{fallbackText(cta.secondaryPrefix, preistraegerIndexContent.cta.secondaryPrefix)} { (e.currentTarget as HTMLElement).style.color = '#EFBF04'; (e.currentTarget as HTMLElement).style.borderBottomColor = '#EFBF04'; }} onMouseLeave={e => { (e.currentTarget as HTMLElement).style.color = 'rgba(239,191,4,0.7)'; (e.currentTarget as HTMLElement).style.borderBottomColor = 'rgba(239,191,4,0.3)'; }} > {fallbackText(cta.secondaryCta?.label, preistraegerIndexContent.cta.secondaryCta.label)}
); } // ── WinnerCard ─────────────────────────────────────────────────────────────── function WinnerCard({ winner, hoverLabel }: { winner: Winner; hoverLabel: string }) { const [hovered, setHovered] = useState(false); const isMobile = useIsMobile(); return ( setHovered(true)} onMouseLeave={() => setHovered(false)} > {winner.name} {/* base gradient */}
{/* hover overlay */}
{/* Text */}
{winner.year} · {winner.type}

{winner.name}

{winner.category}

{/* Pill button */}
{hoverLabel}
); } // ── SelectControl ──────────────────────────────────────────────────────────── function SelectControl({ value, onChange, placeholder, options, width }: { value: string; onChange: (v: string) => void; placeholder: string; options: { label: string; value: string }[]; width?: number; }) { const active = !!value; return (
); } // ── SearchControl ──────────────────────────────────────────────────────────── function SearchControl({ value, onChange, placeholder }: { value: string; onChange: (v: string) => void; placeholder: string }) { return (
onChange(e.target.value)} placeholder={placeholder} style={{ width: '100%', height: 44, padding: '0 40px 0 14px', fontFamily: FF, fontSize: 16, border: `1px solid ${BORDER}`, borderRadius: 0, background: '#fff', outline: 'none', boxSizing: 'border-box', transition: 'border-color 0.15s', color: '#101828', }} onFocus={e => (e.currentTarget.style.borderColor = GOLD)} onBlur={e => (e.currentTarget.style.borderColor = BORDER)} />
); }