feat: enhance Preistraeger and PreistraegerDetail components with optional year handling and archive support
This commit is contained in:
@@ -68,6 +68,11 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
{
|
{
|
||||||
name: 'year',
|
name: 'year',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
label: 'Jahr (optional)',
|
||||||
|
admin: {
|
||||||
|
description:
|
||||||
|
'Einträge von 2007 bis 2023 werden gemeinsam im Archiv-Reiter angezeigt. Wenn das genaue Gewinnjahr unbekannt ist, das Feld leer lassen; der Eintrag wird ebenfalls diesem Archiv zugeordnet.',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'awardGroup',
|
name: 'awardGroup',
|
||||||
@@ -77,7 +82,7 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
options: awardGroupOptions,
|
options: awardGroupOptions,
|
||||||
admin: {
|
admin: {
|
||||||
description:
|
description:
|
||||||
'Steuert die Ausgabe auf der Preisträger-Übersicht. Pro Jahrgang werden 15 Gewinner angezeigt, davon maximal 3 in der Goldene-Bavaria-Section.',
|
'Steuert die Ausgabe für einzelne Jahrgänge ab 2024. Im Archiv 2007–2023 werden alle Einträge als einfache Liste angezeigt.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -87,7 +92,7 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
min: 1,
|
min: 1,
|
||||||
admin: {
|
admin: {
|
||||||
description:
|
description:
|
||||||
'1 bis 3 sind die besten Gewinner und sollten als Goldene Bavaria markiert werden. Niedrigere Zahlen erscheinen zuerst.',
|
'Für einzelne Jahrgänge ab 2024: 1 bis 3 sind die besten Gewinner und sollten als Goldene Bavaria markiert werden. Niedrigere Zahlen erscheinen zuerst.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -96,7 +101,7 @@ export const Preistraeger: CollectionConfig = {
|
|||||||
label: 'Sortierung',
|
label: 'Sortierung',
|
||||||
defaultValue: 100,
|
defaultValue: 100,
|
||||||
admin: {
|
admin: {
|
||||||
description: 'Fallback-Sortierung innerhalb eines Jahrgangs, wenn kein Rang gesetzt ist.',
|
description: 'Sortierung innerhalb eines Jahrgangs beziehungsweise innerhalb des Archivs 2007–2023.',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -2495,17 +2495,20 @@ export interface Preistraeger {
|
|||||||
title: string;
|
title: string;
|
||||||
image?: (number | null) | Media;
|
image?: (number | null) | Media;
|
||||||
category?: string | null;
|
category?: string | null;
|
||||||
|
/**
|
||||||
|
* Einträge von 2007 bis 2023 werden gemeinsam im Archiv-Reiter angezeigt. Wenn das genaue Gewinnjahr unbekannt ist, das Feld leer lassen; der Eintrag wird ebenfalls diesem Archiv zugeordnet.
|
||||||
|
*/
|
||||||
year?: number | null;
|
year?: number | null;
|
||||||
/**
|
/**
|
||||||
* Steuert die Ausgabe auf der Preisträger-Übersicht. Pro Jahrgang werden 15 Gewinner angezeigt, davon maximal 3 in der Goldene-Bavaria-Section.
|
* Steuert die Ausgabe für einzelne Jahrgänge ab 2024. Im Archiv 2007–2023 werden alle Einträge als einfache Liste angezeigt.
|
||||||
*/
|
*/
|
||||||
awardGroup?: ('preistraeger' | 'goldene-bavaria') | null;
|
awardGroup?: ('preistraeger' | 'goldene-bavaria') | null;
|
||||||
/**
|
/**
|
||||||
* 1 bis 3 sind die besten Gewinner und sollten als Goldene Bavaria markiert werden. Niedrigere Zahlen erscheinen zuerst.
|
* Für einzelne Jahrgänge ab 2024: 1 bis 3 sind die besten Gewinner und sollten als Goldene Bavaria markiert werden. Niedrigere Zahlen erscheinen zuerst.
|
||||||
*/
|
*/
|
||||||
winnerRank?: number | null;
|
winnerRank?: number | null;
|
||||||
/**
|
/**
|
||||||
* Fallback-Sortierung innerhalb eines Jahrgangs, wenn kein Rang gesetzt ist.
|
* Sortierung innerhalb eines Jahrgangs beziehungsweise innerhalb des Archivs 2007–2023.
|
||||||
*/
|
*/
|
||||||
sortOrder?: number | null;
|
sortOrder?: number | null;
|
||||||
showOnIndex?: boolean | null;
|
showOnIndex?: boolean | null;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ export interface Winner {
|
|||||||
slug: string;
|
slug: string;
|
||||||
name: string;
|
name: string;
|
||||||
category: string;
|
category: string;
|
||||||
year: number;
|
year?: number;
|
||||||
type: string;
|
type: string;
|
||||||
awardGroup?: 'preistraeger' | 'goldene-bavaria';
|
awardGroup?: 'preistraeger' | 'goldene-bavaria';
|
||||||
winnerRank?: number;
|
winnerRank?: number;
|
||||||
@@ -21,7 +21,9 @@ export interface Winner {
|
|||||||
hasMedia: boolean;
|
hasMedia: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const winnerEntries: Omit<Winner, 'awardGroup' | 'winnerRank' | 'sortOrder' | 'showOnIndex'>[] = [
|
type StaticWinner = Omit<Winner, 'awardGroup' | 'winnerRank' | 'sortOrder' | 'showOnIndex'> & { year: number };
|
||||||
|
|
||||||
|
const winnerEntries: StaticWinner[] = [
|
||||||
// ── 2025 ──────────────────────────────────────────────────────────────────
|
// ── 2025 ──────────────────────────────────────────────────────────────────
|
||||||
{
|
{
|
||||||
id: '1', slug: 'techvision-muenchen',
|
id: '1', slug: 'techvision-muenchen',
|
||||||
@@ -326,4 +328,6 @@ export const WINNERS: Winner[] = winnerEntries.map((winner) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const CATEGORIES = [...new Set(WINNERS.map(w => w.category))].sort();
|
export const CATEGORIES = [...new Set(WINNERS.map(w => w.category))].sort();
|
||||||
export const YEARS = [...new Set(WINNERS.map(w => w.year))].sort((a, b) => b - a);
|
export const YEARS = [...new Set(WINNERS.map(w => w.year))]
|
||||||
|
.filter((year): year is number => typeof year === 'number')
|
||||||
|
.sort((a, b) => b - a);
|
||||||
|
|||||||
@@ -15,10 +15,15 @@ const GOLD = '#EFBF04';
|
|||||||
const BORDER = '#D0D5DD';
|
const BORDER = '#D0D5DD';
|
||||||
const GRAY = '#666666';
|
const GRAY = '#666666';
|
||||||
const BG_ALT = '#E4E2E3';
|
const BG_ALT = '#E4E2E3';
|
||||||
const SIMPLE_LIST_YEAR = 2023;
|
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 GOLDENE_BAVARIA_GROUP = 'goldene-bavaria';
|
const GOLDENE_BAVARIA_GROUP = 'goldene-bavaria';
|
||||||
const PREISTRAEGER_GROUP = 'preistraeger';
|
const PREISTRAEGER_GROUP = 'preistraeger';
|
||||||
|
|
||||||
|
type WinnerPeriod = number | typeof ARCHIVE_PERIOD;
|
||||||
|
|
||||||
type PreistraegerIndexCms = Partial<typeof preistraegerIndexContent> & {
|
type PreistraegerIndexCms = Partial<typeof preistraegerIndexContent> & {
|
||||||
hero?: Partial<typeof preistraegerIndexContent.hero> & { image?: unknown }
|
hero?: Partial<typeof preistraegerIndexContent.hero> & { image?: unknown }
|
||||||
card?: Partial<typeof preistraegerIndexContent.card> & { fallbackImage?: unknown }
|
card?: Partial<typeof preistraegerIndexContent.card> & { fallbackImage?: unknown }
|
||||||
@@ -26,6 +31,7 @@ type PreistraegerIndexCms = Partial<typeof preistraegerIndexContent> & {
|
|||||||
|
|
||||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||||
const optionalNumber = (value: unknown) => {
|
const optionalNumber = (value: unknown) => {
|
||||||
|
if (value === null || value === undefined || value === '') return undefined;
|
||||||
const parsed = Number(value);
|
const parsed = Number(value);
|
||||||
return Number.isFinite(parsed) ? parsed : undefined;
|
return Number.isFinite(parsed) ? parsed : undefined;
|
||||||
};
|
};
|
||||||
@@ -53,6 +59,18 @@ const sortWinnersForDisplay = (winners: Winner[]) =>
|
|||||||
})
|
})
|
||||||
.map(({ winner }) => winner);
|
.map(({ winner }) => winner);
|
||||||
|
|
||||||
|
const isArchiveWinner = (winner: Winner) =>
|
||||||
|
winner.year === undefined || (winner.year >= ARCHIVE_START_YEAR && winner.year <= ARCHIVE_END_YEAR);
|
||||||
|
|
||||||
|
const sortArchiveWinners = (winners: Winner[]) =>
|
||||||
|
winners
|
||||||
|
.map((winner, index) => ({ winner, index }))
|
||||||
|
.sort((a, b) => {
|
||||||
|
const sortDiff = (a.winner.sortOrder ?? Number.POSITIVE_INFINITY) - (b.winner.sortOrder ?? Number.POSITIVE_INFINITY);
|
||||||
|
return sortDiff !== 0 ? sortDiff : a.index - b.index;
|
||||||
|
})
|
||||||
|
.map(({ winner }) => winner);
|
||||||
|
|
||||||
const splitWinnersForYear = (winners: Winner[], totalWinners: number, goldeneBavariaCount: number) => {
|
const splitWinnersForYear = (winners: Winner[], totalWinners: number, goldeneBavariaCount: number) => {
|
||||||
const visibleWinners = sortWinnersForDisplay(winners.filter((winner) => winner.showOnIndex !== false));
|
const visibleWinners = sortWinnersForDisplay(winners.filter((winner) => winner.showOnIndex !== false));
|
||||||
const maxGoldeneBavaria = Math.min(goldeneBavariaCount, totalWinners);
|
const maxGoldeneBavaria = Math.min(goldeneBavariaCount, totalWinners);
|
||||||
@@ -89,7 +107,7 @@ const normalizeWinner = (
|
|||||||
slug: String(doc.slug || ''),
|
slug: String(doc.slug || ''),
|
||||||
name: String(doc.title || fallbackWinner.name || preistraegerIndexContent.card.fallbackWinner.name),
|
name: String(doc.title || fallbackWinner.name || preistraegerIndexContent.card.fallbackWinner.name),
|
||||||
category: String(doc.category || fallbackWinner.category || preistraegerIndexContent.card.fallbackWinner.category),
|
category: String(doc.category || fallbackWinner.category || preistraegerIndexContent.card.fallbackWinner.category),
|
||||||
year: Number(doc.year || new Date().getFullYear()),
|
year: optionalNumber(doc.year),
|
||||||
type: String(
|
type: String(
|
||||||
doc.awardType ||
|
doc.awardType ||
|
||||||
(normalizeAwardGroup(doc.awardGroup) === GOLDENE_BAVARIA_GROUP ? 'Goldene Bavaria' : fallbackWinner.awardType) ||
|
(normalizeAwardGroup(doc.awardGroup) === GOLDENE_BAVARIA_GROUP ? 'Goldene Bavaria' : fallbackWinner.awardType) ||
|
||||||
@@ -135,16 +153,39 @@ export default function Preistraeger() {
|
|||||||
if (cmsPreistraeger.length) return cmsPreistraeger.map((doc) => normalizeWinner(doc, placeholderImage, fallbackWinner)).filter((winner) => winner.slug);
|
if (cmsPreistraeger.length) return cmsPreistraeger.map((doc) => normalizeWinner(doc, placeholderImage, fallbackWinner)).filter((winner) => winner.slug);
|
||||||
return WINNERS;
|
return WINNERS;
|
||||||
}, [card, cmsPreistraeger, placeholderImage]);
|
}, [card, cmsPreistraeger, placeholderImage]);
|
||||||
const years = useMemo(() => [...new Set(winners.map((winner) => winner.year))].sort((a, b) => b - a), [winners]);
|
const periods = useMemo<WinnerPeriod[]>(() => {
|
||||||
const [activeYear, setActiveYear] = useState<number | undefined>(undefined);
|
const annualYears = [...new Set(
|
||||||
const selectedYear = activeYear ?? years[0] ?? new Date().getFullYear();
|
winners
|
||||||
|
.map((winner) => winner.year)
|
||||||
|
.filter((year): year is number => typeof year === 'number' && year > ARCHIVE_END_YEAR),
|
||||||
|
)].sort((a, b) => b - a);
|
||||||
|
|
||||||
const filtered = useMemo(() => winners.filter(w => w.year === selectedYear), [selectedYear, winners]);
|
return winners.some(isArchiveWinner) ? [...annualYears, ARCHIVE_PERIOD] : annualYears;
|
||||||
const winnerGroups = useMemo(
|
}, [winners]);
|
||||||
() => splitWinnersForYear(filtered, totalWinners, goldeneBavariaCount),
|
const [activePeriod, setActivePeriod] = useState<WinnerPeriod | undefined>(undefined);
|
||||||
[filtered, goldeneBavariaCount, totalWinners],
|
const selectedPeriod = activePeriod ?? periods[0] ?? ARCHIVE_PERIOD;
|
||||||
|
const isArchivePeriod = selectedPeriod === ARCHIVE_PERIOD;
|
||||||
|
const selectedPeriodLabel = isArchivePeriod ? ARCHIVE_LABEL : String(selectedPeriod);
|
||||||
|
|
||||||
|
const filtered = useMemo(
|
||||||
|
() => winners.filter((winner) => isArchivePeriod ? isArchiveWinner(winner) : winner.year === selectedPeriod),
|
||||||
|
[isArchivePeriod, selectedPeriod, winners],
|
||||||
|
);
|
||||||
|
const winnerGroups = useMemo(
|
||||||
|
() => {
|
||||||
|
if (isArchivePeriod) {
|
||||||
|
const archiveWinners = sortArchiveWinners(filtered.filter((winner) => winner.showOnIndex !== false));
|
||||||
|
return {
|
||||||
|
goldeneBavariaWinners: [],
|
||||||
|
remainingWinners: archiveWinners,
|
||||||
|
displayedWinners: archiveWinners,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return splitWinnersForYear(filtered, totalWinners, goldeneBavariaCount);
|
||||||
|
},
|
||||||
|
[filtered, goldeneBavariaCount, isArchivePeriod, totalWinners],
|
||||||
);
|
);
|
||||||
const usesSimpleWinnerGrid = selectedYear === SIMPLE_LIST_YEAR;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
<div style={{ background: '#fff', minHeight: '100vh' }}>
|
||||||
@@ -170,23 +211,23 @@ export default function Preistraeger() {
|
|||||||
|
|
||||||
{/* Year Tabs */}
|
{/* Year Tabs */}
|
||||||
<div style={{ display: 'flex', flexWrap: isMobile ? 'nowrap' : 'wrap', gap: 0, borderBottom: `2px solid ${BORDER}`, overflowX: isMobile ? 'auto' : 'visible', WebkitOverflowScrolling: 'touch', maxWidth: '100%' }}>
|
<div style={{ display: 'flex', flexWrap: isMobile ? 'nowrap' : 'wrap', gap: 0, borderBottom: `2px solid ${BORDER}`, overflowX: isMobile ? 'auto' : 'visible', WebkitOverflowScrolling: 'touch', maxWidth: '100%' }}>
|
||||||
{years.map(year => (
|
{periods.map(period => (
|
||||||
<button
|
<button
|
||||||
key={year}
|
key={period}
|
||||||
onClick={() => setActiveYear(year)}
|
onClick={() => setActivePeriod(period)}
|
||||||
style={{
|
style={{
|
||||||
fontFamily: FF, fontSize: 16, fontWeight: 700,
|
fontFamily: FF, fontSize: 16, fontWeight: 700,
|
||||||
padding: isMobile ? '12px 20px' : '12px 32px',
|
padding: isMobile ? '12px 20px' : '12px 32px',
|
||||||
flexShrink: 0,
|
flexShrink: 0,
|
||||||
background: 'none', border: 'none', cursor: 'pointer',
|
background: 'none', border: 'none', cursor: 'pointer',
|
||||||
color: selectedYear === year ? NAVY : GRAY,
|
color: selectedPeriod === period ? NAVY : GRAY,
|
||||||
borderBottom: selectedYear === year ? `2px solid ${GOLD}` : '2px solid transparent',
|
borderBottom: selectedPeriod === period ? `2px solid ${GOLD}` : '2px solid transparent',
|
||||||
marginBottom: -2,
|
marginBottom: -2,
|
||||||
letterSpacing: '0.04em',
|
letterSpacing: '0.04em',
|
||||||
transition: 'color 0.15s, border-color 0.15s',
|
transition: 'color 0.15s, border-color 0.15s',
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{year}
|
{period === ARCHIVE_PERIOD ? ARCHIVE_LABEL : period}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -196,7 +237,7 @@ export default function Preistraeger() {
|
|||||||
<div style={{ padding: isMobile ? '12px 24px' : '12px 80px', background: BG_ALT, borderTop: `1px solid ${BORDER}`, borderBottom: `1px solid ${BORDER}`, display: 'flex', alignItems: 'center', gap: 12 }}>
|
<div style={{ padding: isMobile ? '12px 24px' : '12px 80px', background: BG_ALT, borderTop: `1px solid ${BORDER}`, borderBottom: `1px solid ${BORDER}`, display: 'flex', alignItems: 'center', gap: 12 }}>
|
||||||
<Trophy size={13} style={{ color: GOLD }} fill={GOLD} />
|
<Trophy size={13} style={{ color: GOLD }} fill={GOLD} />
|
||||||
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY }}>
|
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY }}>
|
||||||
<strong style={{ color: '#101828' }}>{selectedYear}</strong> – {winnerGroups.displayedWinners.length} {fallbackText(count.label, preistraegerIndexContent.count.label)}
|
<strong style={{ color: '#101828' }}>{selectedPeriodLabel}</strong> – {winnerGroups.displayedWinners.length} {fallbackText(count.label, preistraegerIndexContent.count.label)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -204,6 +245,8 @@ export default function Preistraeger() {
|
|||||||
<div style={{ padding: isMobile ? '56px 24px' : '80px', textAlign: 'center', color: GRAY, fontFamily: FF }}>
|
<div style={{ padding: isMobile ? '56px 24px' : '80px', textAlign: 'center', color: GRAY, fontFamily: FF }}>
|
||||||
{fallbackText(empty.message, preistraegerIndexContent.empty.message)}
|
{fallbackText(empty.message, preistraegerIndexContent.empty.message)}
|
||||||
</div>
|
</div>
|
||||||
|
) : isArchivePeriod ? (
|
||||||
|
<SimpleWinnerGrid winners={winnerGroups.displayedWinners} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{winnerGroups.goldeneBavariaWinners.length > 0 ? (
|
{winnerGroups.goldeneBavariaWinners.length > 0 ? (
|
||||||
@@ -217,27 +260,23 @@ export default function Preistraeger() {
|
|||||||
{winnerGroups.remainingWinners.length > 0 ? (
|
{winnerGroups.remainingWinners.length > 0 ? (
|
||||||
<section style={{ background: '#fff' }}>
|
<section style={{ background: '#fff' }}>
|
||||||
<SectionIntro copy={winnersSection} isMobile={isMobile} />
|
<SectionIntro copy={winnersSection} isMobile={isMobile} />
|
||||||
{usesSimpleWinnerGrid ? (
|
<div style={{
|
||||||
<SimpleWinnerGrid winners={winnerGroups.remainingWinners} />
|
display: 'grid',
|
||||||
) : (
|
gridTemplateColumns: isMobile ? undefined : 'repeat(4, 1fr)',
|
||||||
<div style={{
|
gridAutoFlow: isMobile ? 'column' : undefined,
|
||||||
display: 'grid',
|
gridTemplateRows: isMobile ? 'repeat(2, 1fr)' : undefined,
|
||||||
gridTemplateColumns: isMobile ? undefined : 'repeat(4, 1fr)',
|
gridAutoColumns: isMobile ? '74vw' : undefined,
|
||||||
gridAutoFlow: isMobile ? 'column' : undefined,
|
overflowX: isMobile ? 'auto' : undefined,
|
||||||
gridTemplateRows: isMobile ? 'repeat(2, 1fr)' : undefined,
|
scrollSnapType: isMobile ? 'x mandatory' : undefined,
|
||||||
gridAutoColumns: isMobile ? '74vw' : undefined,
|
gap: isMobile ? 8 : 0,
|
||||||
overflowX: isMobile ? 'auto' : undefined,
|
padding: isMobile ? '0 16px 8px' : 0,
|
||||||
scrollSnapType: isMobile ? 'x mandatory' : undefined,
|
WebkitOverflowScrolling: 'touch',
|
||||||
gap: isMobile ? 8 : 0,
|
scrollbarWidth: 'none',
|
||||||
padding: isMobile ? '0 16px 8px' : 0,
|
}}>
|
||||||
WebkitOverflowScrolling: 'touch',
|
{winnerGroups.remainingWinners.map(w => (
|
||||||
scrollbarWidth: 'none',
|
<WinnerCard key={w.id} winner={w} hoverLabel={fallbackText(card.hoverLabel, preistraegerIndexContent.card.hoverLabel)} />
|
||||||
}}>
|
))}
|
||||||
{winnerGroups.remainingWinners.map(w => (
|
</div>
|
||||||
<WinnerCard key={w.id} winner={w} hoverLabel={fallbackText(card.hoverLabel, preistraegerIndexContent.card.hoverLabel)} />
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</section>
|
</section>
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
@@ -436,6 +475,7 @@ function SimpleWinnerGrid({ winners }: { winners: Winner[] }) {
|
|||||||
function SimpleWinnerCard({ winner }: { winner: Winner }) {
|
function SimpleWinnerCard({ winner }: { winner: Winner }) {
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
const hasDescription = winner.shortDesc.trim().length > 0;
|
const hasDescription = winner.shortDesc.trim().length > 0;
|
||||||
|
const winnerMeta = [winner.year, winner.type].filter(Boolean).join(' · ');
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<article
|
<article
|
||||||
@@ -450,16 +490,18 @@ function SimpleWinnerCard({ winner }: { winner: Winner }) {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 18 }}>
|
<div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap', marginBottom: 18 }}>
|
||||||
<span style={{
|
{winnerMeta ? (
|
||||||
color: GOLD,
|
<span style={{
|
||||||
fontFamily: FF,
|
color: GOLD,
|
||||||
fontSize: 12,
|
fontFamily: FF,
|
||||||
fontWeight: 700,
|
fontSize: 12,
|
||||||
letterSpacing: '0.12em',
|
fontWeight: 700,
|
||||||
textTransform: 'uppercase',
|
letterSpacing: '0.12em',
|
||||||
}}>
|
textTransform: 'uppercase',
|
||||||
{winner.year} · {winner.type}
|
}}>
|
||||||
</span>
|
{winnerMeta}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
{winner.category ? (
|
{winner.category ? (
|
||||||
<span style={{
|
<span style={{
|
||||||
border: `1px solid ${BORDER}`,
|
border: `1px solid ${BORDER}`,
|
||||||
|
|||||||
@@ -37,6 +37,11 @@ type PreistraegerGalleryCms = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||||
|
const optionalNumber = (value: unknown) => {
|
||||||
|
if (value === null || value === undefined || value === '') return undefined;
|
||||||
|
const parsed = Number(value);
|
||||||
|
return Number.isFinite(parsed) ? parsed : undefined;
|
||||||
|
};
|
||||||
|
|
||||||
const mediaFilename = (media: unknown) => {
|
const mediaFilename = (media: unknown) => {
|
||||||
if (!media || typeof media !== 'object') return undefined;
|
if (!media || typeof media !== 'object') return undefined;
|
||||||
@@ -60,7 +65,7 @@ export default function PreistraegerDetail() {
|
|||||||
slug: String(cmsWinner.slug || slug),
|
slug: String(cmsWinner.slug || slug),
|
||||||
name: String(cmsWinner.title || fallbackWinner.name || preistraegerDetailContent.fallbackWinner.name),
|
name: String(cmsWinner.title || fallbackWinner.name || preistraegerDetailContent.fallbackWinner.name),
|
||||||
category: String(cmsWinner.category || fallbackWinner.category || preistraegerDetailContent.fallbackWinner.category),
|
category: String(cmsWinner.category || fallbackWinner.category || preistraegerDetailContent.fallbackWinner.category),
|
||||||
year: Number(cmsWinner.year || new Date().getFullYear()),
|
year: optionalNumber(cmsWinner.year),
|
||||||
type: String(cmsWinner.awardType || (cmsWinnerIsGoldeneBavaria ? 'Goldene Bavaria' : fallbackWinner.awardType) || preistraegerDetailContent.fallbackWinner.awardType),
|
type: String(cmsWinner.awardType || (cmsWinnerIsGoldeneBavaria ? 'Goldene Bavaria' : fallbackWinner.awardType) || preistraegerDetailContent.fallbackWinner.awardType),
|
||||||
img: docImageUrl(cmsWinner, placeholderImage),
|
img: docImageUrl(cmsWinner, placeholderImage),
|
||||||
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
shortDesc: String(cmsWinner.shortDesc || cmsWinner.description || cmsWinner.meta?.description || ''),
|
||||||
@@ -134,9 +139,11 @@ export default function PreistraegerDetail() {
|
|||||||
}}>
|
}}>
|
||||||
{winner.category}
|
{winner.category}
|
||||||
</span>
|
</span>
|
||||||
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: GOLD }}>
|
{typeof winner.year === 'number' ? (
|
||||||
{winner.year}
|
<span style={{ fontFamily: FF, fontSize: 15, fontWeight: 700, color: GOLD }}>
|
||||||
</span>
|
{winner.year}
|
||||||
|
</span>
|
||||||
|
) : null}
|
||||||
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
<span style={{ fontFamily: FF, fontSize: 15, color: GRAY, textTransform: 'uppercase', letterSpacing: '0.08em' }}>
|
||||||
{winner.type}
|
{winner.type}
|
||||||
</span>
|
</span>
|
||||||
@@ -196,7 +203,7 @@ export default function PreistraegerDetail() {
|
|||||||
{[
|
{[
|
||||||
{ label: fallbackText(facts.categoryLabel, preistraegerDetailContent.facts.categoryLabel), value: winner.category, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
{ 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 }} /> },
|
{ label: fallbackText(facts.awardLabel, preistraegerDetailContent.facts.awardLabel), value: winner.type, icon: <Trophy size={14} style={{ color: GOLD }} /> },
|
||||||
{ label: fallbackText(facts.yearLabel, preistraegerDetailContent.facts.yearLabel), value: String(winner.year), icon: null },
|
...(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.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 }} /> },
|
{ label: fallbackText(facts.industryLabel, preistraegerDetailContent.facts.industryLabel), value: winner.industry, icon: <Building2 size={14} style={{ color: GOLD }} /> },
|
||||||
].map(row => (
|
].map(row => (
|
||||||
|
|||||||
Reference in New Issue
Block a user