feat: clean up Preistraeger detail structure by removing unused fields and updating related migrations
This commit is contained in:
@@ -784,13 +784,13 @@ const Netzwerk: React.FC = () => {
|
||||
|
||||
{/* ── 7. SPONSORING-FORM ───────────────────────────────────────────────── */}
|
||||
<section id="sponsoring" style={{
|
||||
background: NAVY, overflow: 'hidden', position: 'relative',
|
||||
minHeight: isMobile ? 'auto' : 'calc(100vh - 60px)',
|
||||
background: '#24366A', overflow: 'hidden', position: 'relative',
|
||||
height: isMobile ? 'auto' : 'calc(100vh - 60px)',
|
||||
display: 'flex', flexDirection: 'column',
|
||||
}}>
|
||||
<div style={{ flex: 1, position: 'relative', zIndex: 1, display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', minHeight: 0, overflow: isMobile ? 'visible' : 'visible' }}>
|
||||
<div style={{ flex: 1, position: 'relative', zIndex: 1, display: 'grid', gridTemplateColumns: isMobile ? '1fr' : '4fr 1px 8fr', minHeight: 0, overflow: 'hidden' }}>
|
||||
{/* Left – pitch */}
|
||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'visible' }}>
|
||||
<div style={{ padding: isMobile ? '32px 24px 24px' : '36px 36px 32px 52px', display: 'flex', flexDirection: 'column', justifyContent: 'center', overflow: 'hidden' }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 10, textTransform: 'uppercase', letterSpacing: '0.32em', fontWeight: 700, color: GOLD, display: 'block', marginBottom: 8 }}>{fallbackText(sponsoring.eyebrow, netzwerkContent.sponsoring.eyebrow)}</span>
|
||||
<h2 className="type-section-title text-role-inverse" style={{ textTransform: 'uppercase', margin: '0 0 12px' }}><Lines text={fallbackText(sponsoring.heading, netzwerkContent.sponsoring.heading)} /></h2>
|
||||
<div style={{ width: 36, height: 2, background: GOLD, margin: '0 0 14px', flexShrink: 0 }} />
|
||||
@@ -810,7 +810,7 @@ const Netzwerk: React.FC = () => {
|
||||
{/* Center divider */}
|
||||
{!isMobile && <div style={{ background: 'rgba(255,255,255,0.07)' }} />}
|
||||
{/* Right – gold form panel */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: 'visible', minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', overflow: 'hidden', minHeight: 0, position: 'relative', background: 'linear-gradient(160deg,#DDB84A 0%,#C9A227 52%,#A87800 100%)' }}>
|
||||
<svg style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', pointerEvents: 'none', zIndex: 0 }} aria-hidden="true">
|
||||
<defs>
|
||||
<pattern id="sponsoringCross" width="18" height="18" patternUnits="userSpaceOnUse">
|
||||
@@ -836,7 +836,7 @@ const Netzwerk: React.FC = () => {
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div id="sponsoring-form" style={{ padding: isMobile ? '28px 20px' : '24px 48px 32px 40px', flex: 1, display: 'flex', flexDirection: 'column', minHeight: isMobile ? 560 : 0, position: 'relative', zIndex: 1, scrollMarginTop: 80 }}>
|
||||
<div id="sponsoring-form" style={{ padding: isMobile ? '32px 24px' : '24px 48px 32px 40px', flex: isMobile ? 'none' : 1, height: isMobile ? '82svh' : undefined, display: 'flex', flexDirection: 'column', minHeight: 0, position: 'relative', zIndex: 1, scrollMarginTop: 80 }}>
|
||||
<SponsoringForm theme="gold" content={cms.sponsoringForm} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -18,7 +18,8 @@ const BG_ALT = '#E4E2E3';
|
||||
const ARCHIVE_START_YEAR = 2007;
|
||||
const ARCHIVE_END_YEAR = 2023;
|
||||
const ARCHIVE_PERIOD = 'archive-2007-2023' as const;
|
||||
const ARCHIVE_LABEL = `${ARCHIVE_START_YEAR}–${ARCHIVE_END_YEAR}`;
|
||||
const ARCHIVE_RANGE_LABEL = `${ARCHIVE_START_YEAR}–${ARCHIVE_END_YEAR}`;
|
||||
const ARCHIVE_TAB_LABEL = 'Bis 2023';
|
||||
const GOLDENE_BAVARIA_GROUP = 'goldene-bavaria';
|
||||
const PREISTRAEGER_GROUP = 'preistraeger';
|
||||
|
||||
@@ -165,7 +166,7 @@ export default function Preistraeger() {
|
||||
const [activePeriod, setActivePeriod] = useState<WinnerPeriod | undefined>(undefined);
|
||||
const selectedPeriod = activePeriod ?? periods[0] ?? ARCHIVE_PERIOD;
|
||||
const isArchivePeriod = selectedPeriod === ARCHIVE_PERIOD;
|
||||
const selectedPeriodLabel = isArchivePeriod ? ARCHIVE_LABEL : String(selectedPeriod);
|
||||
const selectedPeriodLabel = isArchivePeriod ? ARCHIVE_RANGE_LABEL : String(selectedPeriod);
|
||||
|
||||
const filtered = useMemo(
|
||||
() => winners.filter((winner) => isArchivePeriod ? isArchiveWinner(winner) : winner.year === selectedPeriod),
|
||||
@@ -229,7 +230,7 @@ export default function Preistraeger() {
|
||||
transition: 'color 0.15s, border-color 0.15s',
|
||||
}}
|
||||
>
|
||||
{period === ARCHIVE_PERIOD ? ARCHIVE_LABEL : period}
|
||||
{period === ARCHIVE_PERIOD ? ARCHIVE_TAB_LABEL : period}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link, useParams, Navigate } from '@/spa/router';
|
||||
import { Trophy, ArrowLeft, MapPin, Building2, Globe, ChevronRight, Download, ChevronLeft, X } from 'lucide-react';
|
||||
import { ArrowLeft, Globe, ChevronRight, Download, ChevronLeft, X } from 'lucide-react';
|
||||
import { WINNERS } from '@/spa/data/winners';
|
||||
import { useCmsRoute } from '@/spa/cmsRoute';
|
||||
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||
@@ -37,6 +37,7 @@ type PreistraegerGalleryCms = {
|
||||
}
|
||||
|
||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||
const cmsText = (value: unknown) => typeof value === 'string' ? value.trim() : '';
|
||||
const optionalNumber = (value: unknown) => {
|
||||
if (value === null || value === undefined || value === '') return undefined;
|
||||
const parsed = Number(value);
|
||||
@@ -70,9 +71,9 @@ export default function PreistraegerDetail() {
|
||||
img: docImageUrl(cmsWinner, placeholderImage),
|
||||
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
||||
longDesc: String(cmsWinner.longDesc || cmsWinner.description || cmsWinner.meta?.description || fallbackWinner.longDesc || preistraegerDetailContent.fallbackWinner.longDesc),
|
||||
quote: String(cmsWinner.quote || fallbackWinner.quote || preistraegerDetailContent.fallbackWinner.quote),
|
||||
quotePerson: String(cmsWinner.quotePerson || cmsWinner.title || fallbackWinner.quotePerson || preistraegerDetailContent.fallbackWinner.quotePerson),
|
||||
quoteRole: String(cmsWinner.quoteRole || fallbackWinner.quoteRole || preistraegerDetailContent.fallbackWinner.quoteRole),
|
||||
quote: cmsText(cmsWinner.quote),
|
||||
quotePerson: cmsText(cmsWinner.quotePerson),
|
||||
quoteRole: cmsText(cmsWinner.quoteRole),
|
||||
location: String(cmsWinner.location || fallbackWinner.location || preistraegerDetailContent.fallbackWinner.location),
|
||||
industry: String(cmsWinner.industry || fallbackWinner.industry || preistraegerDetailContent.fallbackWinner.industry),
|
||||
website: String(cmsWinner.website || '#'),
|
||||
@@ -85,9 +86,12 @@ export default function PreistraegerDetail() {
|
||||
const breadcrumb = { ...preistraegerDetailContent.breadcrumb, ...(detail.breadcrumb || {}) };
|
||||
const sections = { ...preistraegerDetailContent.sections, ...(detail.sections || {}) };
|
||||
const sideImage = { ...preistraegerDetailContent.sideImage, ...(detail.sideImage || {}) };
|
||||
const facts = { ...preistraegerDetailContent.facts, ...(detail.facts || {}) };
|
||||
const websiteCta = { ...preistraegerDetailContent.websiteCta, ...(detail.websiteCta || {}) };
|
||||
const backLink = { ...preistraegerDetailContent.backLink, ...(detail.backLink || {}) };
|
||||
const juryTextBeforeName = cmsText(sections.juryTextBeforeName);
|
||||
const juryTextAfterNameBeforeCategory = cmsText(sections.juryTextAfterNameBeforeCategory);
|
||||
const juryTextAfterCategory = cmsText(sections.juryTextAfterCategory);
|
||||
const showJuryText = Boolean(juryTextBeforeName || juryTextAfterNameBeforeCategory || juryTextAfterCategory);
|
||||
const downloadHref = mediaUrl(cmsWinner?.downloadZip, '');
|
||||
const downloadFilename = mediaFilename(cmsWinner?.downloadZip);
|
||||
const downloadLabel = fallbackText(cmsWinner?.downloadLabel, 'Pressepaket herunterladen');
|
||||
@@ -170,51 +174,42 @@ export default function PreistraegerDetail() {
|
||||
<h2 className="type-subtitle text-role-heading" style={{ textTransform: 'uppercase', marginBottom: 20 }}>
|
||||
{fallbackText(sections.juryHeading, preistraegerDetailContent.sections.juryHeading)}
|
||||
</h2>
|
||||
<p className="type-body text-role-body" style={{ marginBottom: 40 }}>
|
||||
{fallbackText(sections.juryTextBeforeName, preistraegerDetailContent.sections.juryTextBeforeName)} {winner.name} {fallbackText(sections.juryTextAfterNameBeforeCategory, preistraegerDetailContent.sections.juryTextAfterNameBeforeCategory)} <strong>{winner.category}</strong>. {fallbackText(sections.juryTextAfterCategory, preistraegerDetailContent.sections.juryTextAfterCategory)}
|
||||
</p>
|
||||
{showJuryText ? (
|
||||
<p className="type-body text-role-body" style={{ marginBottom: 40 }}>
|
||||
{juryTextBeforeName}
|
||||
{juryTextAfterNameBeforeCategory ? (
|
||||
<>{juryTextBeforeName ? ' ' : null}{winner.name} {juryTextAfterNameBeforeCategory} <strong>{winner.category}</strong>.</>
|
||||
) : null}
|
||||
{juryTextAfterCategory ? <>{juryTextBeforeName || juryTextAfterNameBeforeCategory ? ' ' : null}{juryTextAfterCategory}</> : null}
|
||||
</p>
|
||||
) : null}
|
||||
|
||||
{/* Quote */}
|
||||
<div style={{ borderLeft: `4px solid ${GOLD}`, paddingLeft: 24, marginBottom: 40 }}>
|
||||
<p className="type-body text-role-heading" style={{ fontStyle: 'italic', marginBottom: 12 }}>
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginRight: 4, fontFamily: '"IBM Plex Sans"' }}>“</span>
|
||||
{winner.quote}
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginLeft: 2, fontFamily: '"IBM Plex Sans"' }}>”</span>
|
||||
</p>
|
||||
<p className="type-small text-role-secondary" style={{ fontWeight: 700, margin: 0, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
||||
– {winner.quotePerson}, {winner.quoteRole}
|
||||
</p>
|
||||
</div>
|
||||
{winner.quote ? (
|
||||
<div style={{ borderLeft: `4px solid ${GOLD}`, paddingLeft: 24, marginBottom: 40 }}>
|
||||
<p className="type-body text-role-heading" style={{ fontStyle: 'italic', marginBottom: winner.quotePerson || winner.quoteRole ? 12 : 0 }}>
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginRight: 4, fontFamily: '"IBM Plex Sans"' }}>“</span>
|
||||
{winner.quote}
|
||||
<span style={{ color: GOLD, fontSize: 28, lineHeight: 1, marginLeft: 2, fontFamily: '"IBM Plex Sans"' }}>”</span>
|
||||
</p>
|
||||
{winner.quotePerson || winner.quoteRole ? (
|
||||
<p className="type-small text-role-secondary" style={{ fontWeight: 700, margin: 0, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
||||
{winner.quotePerson ? <>– {winner.quotePerson}</> : null}
|
||||
{winner.quotePerson && winner.quoteRole ? ', ' : null}
|
||||
{winner.quoteRole}
|
||||
</p>
|
||||
) : null}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
|
||||
{/* Right: Photo + Info box */}
|
||||
{/* Right: Photo + actions */}
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 32 }}>
|
||||
{/* Photo */}
|
||||
<div style={{ borderRadius: 0, overflow: 'hidden', aspectRatio: '4/3' }}>
|
||||
<Image unoptimized src={mediaUrl(sideImage.image, `/${preistraegerDetailContent.sideImage.imageFilename}`)} alt={mediaAlt(sideImage.image, fallbackText(sideImage.imageAlt, preistraegerDetailContent.sideImage.imageAlt))} style={{ width: '100%', height: '100%', objectFit: 'cover', display: 'block', filter: 'brightness(0.95)' }} />
|
||||
</div>
|
||||
|
||||
{/* Kenndaten */}
|
||||
<div style={{ background: BG_ALT, borderLeft: `3px solid ${GOLD}`, padding: '20px 24px' }}>
|
||||
<h3 className="type-small text-role-secondary" style={{ fontWeight: 700, textTransform: 'uppercase', letterSpacing: '0.1em', marginBottom: 20 }}>
|
||||
{fallbackText(facts.heading, preistraegerDetailContent.facts.heading)}
|
||||
</h3>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: 16 }}>
|
||||
{[
|
||||
{ label: fallbackText(facts.categoryLabel, preistraegerDetailContent.facts.categoryLabel), value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.awardLabel, preistraegerDetailContent.facts.awardLabel), value: winner.type, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||
...(typeof winner.year === 'number' ? [{ label: fallbackText(facts.yearLabel, preistraegerDetailContent.facts.yearLabel), value: String(winner.year), icon: null }] : []),
|
||||
{ label: fallbackText(facts.locationLabel, preistraegerDetailContent.facts.locationLabel), value: winner.location, icon: <MapPin size={14} style={{ color: GOLD }} /> },
|
||||
{ label: fallbackText(facts.industryLabel, preistraegerDetailContent.facts.industryLabel), value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||
].map(row => (
|
||||
<div key={row.label} style={{ display: 'flex', flexDirection: 'column', gap: 2 }}>
|
||||
<span style={{ fontFamily: FF, fontSize: 11, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>{row.label}</span>
|
||||
<span style={{ fontFamily: FF, fontSize: 17, fontWeight: 700, color: '#101828', overflowWrap: 'anywhere' }}>{row.value}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Website button */}
|
||||
<a
|
||||
href={`https://${winner.website}`}
|
||||
|
||||
Reference in New Issue
Block a user