feat: manage press fallback labels via payload
This commit is contained in:
@@ -40,6 +40,11 @@ const eventFields: Field[] = [
|
||||
text('slug', 'Route'),
|
||||
]
|
||||
|
||||
const statusLabelFields: Field[] = [
|
||||
text('value', 'Raw status value'),
|
||||
text('label', 'Display label'),
|
||||
]
|
||||
|
||||
const newsFields: Field[] = [
|
||||
text('title', 'Title'),
|
||||
textarea('excerpt', 'Excerpt'),
|
||||
@@ -81,7 +86,20 @@ export const pressIndexFields: Field[] = [
|
||||
textarea('heading', 'Heading', pressIndexContent.events.heading),
|
||||
textarea('description', 'Description', pressIndexContent.events.description),
|
||||
text('detailLabel', 'Detail link label', pressIndexContent.events.detailLabel),
|
||||
text('defaultStatusLabel', 'Missing status fallback label', pressIndexContent.events.defaultStatusLabel),
|
||||
text('openStatusLabel', 'Open status label', pressIndexContent.events.openStatusLabel),
|
||||
text('missingTitleLabel', 'Missing event title label', pressIndexContent.events.missingTitleLabel),
|
||||
text('missingDateLabel', 'Missing event date label', pressIndexContent.events.missingDateLabel),
|
||||
text('missingLocationLabel', 'Missing event location label', pressIndexContent.events.missingLocationLabel),
|
||||
text('missingCategoryLabel', 'Missing event category label', pressIndexContent.events.missingCategoryLabel),
|
||||
uploadField('collectionFallbackImage', 'Missing event image fallback', `Current frontend image: /images/${pressIndexContent.events.collectionFallbackImageFilename}`),
|
||||
{
|
||||
name: 'statusLabels',
|
||||
label: 'Status display labels',
|
||||
type: 'array',
|
||||
defaultValue: pressIndexContent.events.statusLabels,
|
||||
fields: statusLabelFields,
|
||||
},
|
||||
{
|
||||
name: 'fallbackItems',
|
||||
label: 'Fallback event rows',
|
||||
@@ -102,6 +120,9 @@ export const pressIndexFields: Field[] = [
|
||||
textarea('heading', 'Heading', pressIndexContent.news.heading),
|
||||
textarea('description', 'Description', pressIndexContent.news.description),
|
||||
text('readMoreLabel', 'Read more label', pressIndexContent.news.readMoreLabel),
|
||||
text('missingTitleLabel', 'Missing news title label', pressIndexContent.news.missingTitleLabel),
|
||||
text('missingCategoryLabel', 'Missing news category label', pressIndexContent.news.missingCategoryLabel),
|
||||
uploadField('collectionFallbackImage', 'Missing news image fallback', `Current frontend image: /images/${pressIndexContent.news.collectionFallbackImageFilename}`),
|
||||
{
|
||||
name: 'fallbackItems',
|
||||
label: 'Fallback news cards',
|
||||
|
||||
@@ -1492,7 +1492,23 @@ export interface Page {
|
||||
heading?: string | null;
|
||||
description?: string | null;
|
||||
detailLabel?: string | null;
|
||||
defaultStatusLabel?: string | null;
|
||||
openStatusLabel?: string | null;
|
||||
missingTitleLabel?: string | null;
|
||||
missingDateLabel?: string | null;
|
||||
missingLocationLabel?: string | null;
|
||||
missingCategoryLabel?: string | null;
|
||||
/**
|
||||
* Current frontend image: /images/buehne-moderatoren.jpg
|
||||
*/
|
||||
collectionFallbackImage?: (number | null) | Media;
|
||||
statusLabels?:
|
||||
| {
|
||||
value?: string | null;
|
||||
label?: string | null;
|
||||
id?: string | null;
|
||||
}[]
|
||||
| null;
|
||||
fallbackItems?:
|
||||
| {
|
||||
title?: string | null;
|
||||
@@ -1515,6 +1531,12 @@ export interface Page {
|
||||
heading?: string | null;
|
||||
description?: string | null;
|
||||
readMoreLabel?: string | null;
|
||||
missingTitleLabel?: string | null;
|
||||
missingCategoryLabel?: string | null;
|
||||
/**
|
||||
* Current frontend image: /images/gala-saal-overview.jpg
|
||||
*/
|
||||
collectionFallbackImage?: (number | null) | Media;
|
||||
fallbackItems?:
|
||||
| {
|
||||
title?: string | null;
|
||||
@@ -4299,7 +4321,20 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
heading?: T;
|
||||
description?: T;
|
||||
detailLabel?: T;
|
||||
defaultStatusLabel?: T;
|
||||
openStatusLabel?: T;
|
||||
missingTitleLabel?: T;
|
||||
missingDateLabel?: T;
|
||||
missingLocationLabel?: T;
|
||||
missingCategoryLabel?: T;
|
||||
collectionFallbackImage?: T;
|
||||
statusLabels?:
|
||||
| T
|
||||
| {
|
||||
value?: T;
|
||||
label?: T;
|
||||
id?: T;
|
||||
};
|
||||
fallbackItems?:
|
||||
| T
|
||||
| {
|
||||
@@ -4321,6 +4356,9 @@ export interface PagesSelect<T extends boolean = true> {
|
||||
heading?: T;
|
||||
description?: T;
|
||||
readMoreLabel?: T;
|
||||
missingTitleLabel?: T;
|
||||
missingCategoryLabel?: T;
|
||||
collectionFallbackImage?: T;
|
||||
fallbackItems?:
|
||||
| T
|
||||
| {
|
||||
|
||||
@@ -32,8 +32,14 @@ async function main() {
|
||||
const page = pageResult.docs[0]
|
||||
if (!page) throw new Error('Press index page not found. Expected spaPath=/presse or slug=presse/press.')
|
||||
|
||||
const heroImage = await mediaByFilename(payload, pressIndexContent.hero.imageFilename)
|
||||
const [heroImage, eventFallbackImage, newsFallbackImage] = await Promise.all([
|
||||
mediaByFilename(payload, pressIndexContent.hero.imageFilename),
|
||||
mediaByFilename(payload, pressIndexContent.events.collectionFallbackImageFilename),
|
||||
mediaByFilename(payload, pressIndexContent.news.collectionFallbackImageFilename),
|
||||
])
|
||||
const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero
|
||||
const { collectionFallbackImageFilename: _eventFallbackFilename, ...eventsContent } = pressIndexContent.events
|
||||
const { collectionFallbackImageFilename: _newsFallbackFilename, ...newsContent } = pressIndexContent.news
|
||||
|
||||
await payload.update({
|
||||
collection: 'pages',
|
||||
@@ -47,6 +53,14 @@ async function main() {
|
||||
...heroContent,
|
||||
image: heroImage,
|
||||
},
|
||||
events: {
|
||||
...eventsContent,
|
||||
collectionFallbackImage: eventFallbackImage,
|
||||
},
|
||||
news: {
|
||||
...newsContent,
|
||||
collectionFallbackImage: newsFallbackImage,
|
||||
},
|
||||
},
|
||||
} as never,
|
||||
})
|
||||
|
||||
@@ -16,6 +16,8 @@ const FB = '"Inter", sans-serif';
|
||||
|
||||
type PressIndexCms = Partial<typeof pressIndexContent> & {
|
||||
hero?: Partial<typeof pressIndexContent.hero> & { image?: unknown }
|
||||
events?: Partial<typeof pressIndexContent.events> & { collectionFallbackImage?: unknown }
|
||||
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown }
|
||||
}
|
||||
|
||||
type PressEventItem = (typeof pressIndexContent.events.fallbackItems)[number]
|
||||
@@ -24,34 +26,45 @@ type PressNewsItem = (typeof pressIndexContent.news.fallbackItems)[number]
|
||||
const fallbackText = (value: unknown, fallback: string) => typeof value === 'string' && value.length > 0 ? value : fallback;
|
||||
const fallbackArray = <T,>(value: unknown, fallback: T[]) => Array.isArray(value) && value.length ? value as T[] : fallback;
|
||||
|
||||
const statusLabels: Record<string, string> = {
|
||||
geplant: 'In Planung',
|
||||
'anmeldung-offen': 'Anmeldung offen',
|
||||
intern: 'Intern',
|
||||
abgeschlossen: 'Abgeschlossen',
|
||||
type StatusLabel = {
|
||||
value?: string | null
|
||||
label?: string | null
|
||||
}
|
||||
|
||||
const normalizeStatus = (status: unknown) => {
|
||||
const value = fallbackText(status, 'Geplant')
|
||||
return statusLabels[value] || value
|
||||
const statusLabelMap = (items: unknown) =>
|
||||
fallbackArray<StatusLabel>(items, pressIndexContent.events.statusLabels).reduce<Record<string, string>>((labels, item) => {
|
||||
const value = fallbackText(item.value, '')
|
||||
const label = fallbackText(item.label, '')
|
||||
if (value && label) labels[value] = label
|
||||
return labels
|
||||
}, {})
|
||||
|
||||
const normalizeStatus = (status: unknown, labels: Record<string, string>, defaultStatusLabel: string) => {
|
||||
const value = fallbackText(status, defaultStatusLabel)
|
||||
return labels[value] || value
|
||||
}
|
||||
|
||||
const eventFromCms = (event: CmsRouteDoc): PressEventItem => ({
|
||||
title: String(event.title || 'Event'),
|
||||
date: String(event.date || 'Termin folgt'),
|
||||
location: String(event.location || event.venue || 'Bayern'),
|
||||
cat: String(event.category || 'Event'),
|
||||
status: normalizeStatus(event.status),
|
||||
img: docImageUrl(event, '/images/buehne-moderatoren.jpg'),
|
||||
const eventFromCms = (
|
||||
event: CmsRouteDoc,
|
||||
fallbackImage: string,
|
||||
labels: Record<string, string>,
|
||||
eventsSection: NonNullable<PressIndexCms['events']>,
|
||||
): PressEventItem => ({
|
||||
title: String(event.title || fallbackText(eventsSection.missingTitleLabel, pressIndexContent.events.missingTitleLabel)),
|
||||
date: String(event.date || fallbackText(eventsSection.missingDateLabel, pressIndexContent.events.missingDateLabel)),
|
||||
location: String(event.location || event.venue || fallbackText(eventsSection.missingLocationLabel, pressIndexContent.events.missingLocationLabel)),
|
||||
cat: String(event.category || fallbackText(eventsSection.missingCategoryLabel, pressIndexContent.events.missingCategoryLabel)),
|
||||
status: normalizeStatus(event.status, labels, fallbackText(eventsSection.defaultStatusLabel, pressIndexContent.events.defaultStatusLabel)),
|
||||
img: docImageUrl(event, fallbackImage),
|
||||
desc: String(event.description || event.meta?.description || ''),
|
||||
slug: String(event.spaPath || `/presse/events/${event.slug}`),
|
||||
})
|
||||
|
||||
const postFromCms = (post: CmsRouteDoc): PressNewsItem => ({
|
||||
title: String(post.title || 'Presse'),
|
||||
const postFromCms = (post: CmsRouteDoc, fallbackImage: string, newsSection: NonNullable<PressIndexCms['news']>): PressNewsItem => ({
|
||||
title: String(post.title || fallbackText(newsSection.missingTitleLabel, pressIndexContent.news.missingTitleLabel)),
|
||||
excerpt: String(post.excerpt || post.meta?.description || ''),
|
||||
cat: String(post.cat || 'Presse'),
|
||||
img: docImageUrl(post, '/images/gala-saal-overview.jpg', 'heroImage'),
|
||||
cat: String(post.cat || fallbackText(newsSection.missingCategoryLabel, pressIndexContent.news.missingCategoryLabel)),
|
||||
img: docImageUrl(post, fallbackImage, 'heroImage'),
|
||||
slug: String(post.spaPath || `/presse/blog/${post.slug}`),
|
||||
})
|
||||
|
||||
@@ -204,22 +217,34 @@ function DarkInput(props: React.InputHTMLAttributes<HTMLInputElement>) {
|
||||
const Press: React.FC = () => {
|
||||
const isMobile = useIsMobile();
|
||||
const cms = (useCmsRoute()?.doc?.pressIndex || {}) as PressIndexCms;
|
||||
const hero = { ...pressIndexContent.hero, ...(cms.hero || {}) };
|
||||
const eventsSection = { ...pressIndexContent.events, ...(cms.events || {}) };
|
||||
const newsSection = { ...pressIndexContent.news, ...(cms.news || {}) };
|
||||
const downloads = { ...pressIndexContent.downloads, ...(cms.downloads || {}) };
|
||||
const accreditation = { ...pressIndexContent.accreditation, ...(cms.accreditation || {}) };
|
||||
const hero = React.useMemo(() => ({ ...pressIndexContent.hero, ...(cms.hero || {}) }), [cms.hero]);
|
||||
const eventsSection = React.useMemo(() => ({ ...pressIndexContent.events, ...(cms.events || {}) }), [cms.events]);
|
||||
const newsSection = React.useMemo(() => ({ ...pressIndexContent.news, ...(cms.news || {}) }), [cms.news]);
|
||||
const downloads = React.useMemo(() => ({ ...pressIndexContent.downloads, ...(cms.downloads || {}) }), [cms.downloads]);
|
||||
const accreditation = React.useMemo(() => ({ ...pressIndexContent.accreditation, ...(cms.accreditation || {}) }), [cms.accreditation]);
|
||||
const contactLines = fallbackText(downloads.contactLines, pressIndexContent.downloads.contactLines).split('\n');
|
||||
const cmsEvents = useCmsCollection('events');
|
||||
const cmsPosts = useCmsCollection('posts');
|
||||
const eventStatusLabels = React.useMemo(() => statusLabelMap(eventsSection.statusLabels), [eventsSection.statusLabels]);
|
||||
const eventCollectionFallbackImage = mediaUrl(eventsSection.collectionFallbackImage, `/images/${pressIndexContent.events.collectionFallbackImageFilename}`);
|
||||
const newsCollectionFallbackImage = mediaUrl(newsSection.collectionFallbackImage, `/images/${pressIndexContent.news.collectionFallbackImageFilename}`);
|
||||
const combinedEvents = React.useMemo(() => {
|
||||
if (cmsEvents.length) return cmsEvents.map(eventFromCms);
|
||||
if (cmsEvents.length) {
|
||||
return cmsEvents.map((event) =>
|
||||
eventFromCms(
|
||||
event,
|
||||
eventCollectionFallbackImage,
|
||||
eventStatusLabels,
|
||||
eventsSection,
|
||||
),
|
||||
);
|
||||
}
|
||||
return fallbackArray<PressEventItem>(eventsSection.fallbackItems, pressIndexContent.events.fallbackItems);
|
||||
}, [cmsEvents, eventsSection.fallbackItems]);
|
||||
}, [cmsEvents, eventCollectionFallbackImage, eventStatusLabels, eventsSection]);
|
||||
const combinedNews = React.useMemo(() => {
|
||||
if (cmsPosts.length) return cmsPosts.map(postFromCms);
|
||||
if (cmsPosts.length) return cmsPosts.map((post) => postFromCms(post, newsCollectionFallbackImage, newsSection));
|
||||
return fallbackArray<PressNewsItem>(newsSection.fallbackItems, pressIndexContent.news.fallbackItems);
|
||||
}, [cmsPosts, newsSection.fallbackItems]);
|
||||
}, [cmsPosts, newsCollectionFallbackImage, newsSection]);
|
||||
const [hoveredEvent, setHoveredEvent] = useState<number | null>(null);
|
||||
const [dlHovered, setDlHovered] = useState(false);
|
||||
const [submitHovered, setSubmitHovered] = useState(false);
|
||||
|
||||
@@ -11,7 +11,19 @@ export const pressIndexContent = {
|
||||
heading: 'EVENTS RUND UM DEN PREIS.',
|
||||
description: 'Von der Jurysitzung bis zur festlichen Gala – alle Veranstaltungen auf einen Blick.',
|
||||
detailLabel: 'Details',
|
||||
defaultStatusLabel: 'Geplant',
|
||||
openStatusLabel: 'Anmeldung offen',
|
||||
missingTitleLabel: 'Event',
|
||||
missingDateLabel: 'Termin folgt',
|
||||
missingLocationLabel: 'Bayern',
|
||||
missingCategoryLabel: 'Event',
|
||||
collectionFallbackImageFilename: 'buehne-moderatoren.jpg',
|
||||
statusLabels: [
|
||||
{ value: 'geplant', label: 'In Planung' },
|
||||
{ value: 'anmeldung-offen', label: 'Anmeldung offen' },
|
||||
{ value: 'intern', label: 'Intern' },
|
||||
{ value: 'abgeschlossen', label: 'Abgeschlossen' },
|
||||
],
|
||||
fallbackItems: [
|
||||
{
|
||||
title: 'Preisverleihung 2026',
|
||||
@@ -50,6 +62,9 @@ export const pressIndexContent = {
|
||||
heading: 'NACHRICHTEN & EINBLICKE.',
|
||||
description: 'Berichte, Hintergründe und Einblicke rund um den bayerischen Mittelstand und den BMP.',
|
||||
readMoreLabel: 'Weiterlesen',
|
||||
missingTitleLabel: 'Presse',
|
||||
missingCategoryLabel: 'Presse',
|
||||
collectionFallbackImageFilename: 'gala-saal-overview.jpg',
|
||||
fallbackItems: [
|
||||
{
|
||||
title: 'Wie Bayerns KMU die KI nutzen',
|
||||
|
||||
Reference in New Issue
Block a user