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