fix(cms): remove unpublished content fallbacks
This commit is contained in:
@@ -29,32 +29,11 @@ const sectionAdmin = (description: string) => ({
|
|||||||
initCollapsed: true,
|
initCollapsed: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
const eventFields: Field[] = [
|
|
||||||
text('title', 'Title'),
|
|
||||||
text('date', 'Date'),
|
|
||||||
text('location', 'Location'),
|
|
||||||
text('cat', 'Category'),
|
|
||||||
text('status', 'Status'),
|
|
||||||
uploadField('image', 'Image'),
|
|
||||||
text('img', 'Image URL or path'),
|
|
||||||
textarea('desc', 'Description'),
|
|
||||||
text('slug', 'Route'),
|
|
||||||
]
|
|
||||||
|
|
||||||
const statusLabelFields: Field[] = [
|
const statusLabelFields: Field[] = [
|
||||||
text('value', 'Raw status value'),
|
text('value', 'Raw status value'),
|
||||||
text('label', 'Display label'),
|
text('label', 'Display label'),
|
||||||
]
|
]
|
||||||
|
|
||||||
const newsFields: Field[] = [
|
|
||||||
text('title', 'Title'),
|
|
||||||
textarea('excerpt', 'Excerpt'),
|
|
||||||
text('cat', 'Category'),
|
|
||||||
uploadField('image', 'Image'),
|
|
||||||
text('img', 'Image URL or path'),
|
|
||||||
text('slug', 'Route'),
|
|
||||||
]
|
|
||||||
|
|
||||||
export const pressIndexFields: Field[] = [
|
export const pressIndexFields: Field[] = [
|
||||||
{
|
{
|
||||||
name: 'pressIndex',
|
name: 'pressIndex',
|
||||||
@@ -62,7 +41,7 @@ export const pressIndexFields: Field[] = [
|
|||||||
type: 'group',
|
type: 'group',
|
||||||
admin: {
|
admin: {
|
||||||
description:
|
description:
|
||||||
'Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections; fallback cards remain editable here.',
|
'Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections.',
|
||||||
},
|
},
|
||||||
fields: [
|
fields: [
|
||||||
{
|
{
|
||||||
@@ -82,7 +61,7 @@ export const pressIndexFields: Field[] = [
|
|||||||
name: 'events',
|
name: 'events',
|
||||||
label: '02 · Events section',
|
label: '02 · Events section',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Section header, labels, and fallback event rows.'),
|
admin: sectionAdmin('Section header, labels, and missing-field presentation fallbacks.'),
|
||||||
fields: [
|
fields: [
|
||||||
text('eyebrow', 'Eyebrow', pressIndexContent.events.eyebrow),
|
text('eyebrow', 'Eyebrow', pressIndexContent.events.eyebrow),
|
||||||
textarea('heading', 'Heading', pressIndexContent.events.heading),
|
textarea('heading', 'Heading', pressIndexContent.events.heading),
|
||||||
@@ -102,21 +81,13 @@ export const pressIndexFields: Field[] = [
|
|||||||
defaultValue: pressIndexContent.events.statusLabels,
|
defaultValue: pressIndexContent.events.statusLabels,
|
||||||
fields: statusLabelFields,
|
fields: statusLabelFields,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: 'fallbackItems',
|
|
||||||
label: 'Fallback event rows',
|
|
||||||
type: 'array',
|
|
||||||
dbName: 'press_events_fb',
|
|
||||||
defaultValue: pressIndexContent.events.fallbackItems,
|
|
||||||
fields: eventFields,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'news',
|
name: 'news',
|
||||||
label: '03 · Newsroom section',
|
label: '03 · Newsroom section',
|
||||||
type: 'group',
|
type: 'group',
|
||||||
admin: sectionAdmin('Section header, read-more label, and fallback news cards.'),
|
admin: sectionAdmin('Section header, read-more label, and missing-field presentation fallbacks.'),
|
||||||
fields: [
|
fields: [
|
||||||
text('eyebrow', 'Eyebrow', pressIndexContent.news.eyebrow),
|
text('eyebrow', 'Eyebrow', pressIndexContent.news.eyebrow),
|
||||||
textarea('heading', 'Heading', pressIndexContent.news.heading),
|
textarea('heading', 'Heading', pressIndexContent.news.heading),
|
||||||
@@ -125,14 +96,6 @@ export const pressIndexFields: Field[] = [
|
|||||||
text('missingTitleLabel', 'Missing news title label', pressIndexContent.news.missingTitleLabel),
|
text('missingTitleLabel', 'Missing news title label', pressIndexContent.news.missingTitleLabel),
|
||||||
text('missingCategoryLabel', 'Missing news category label', pressIndexContent.news.missingCategoryLabel),
|
text('missingCategoryLabel', 'Missing news category label', pressIndexContent.news.missingCategoryLabel),
|
||||||
uploadField('collectionFallbackImage', 'Missing news image fallback', `Current frontend image: /images/${pressIndexContent.news.collectionFallbackImageFilename}`),
|
uploadField('collectionFallbackImage', 'Missing news image fallback', `Current frontend image: /images/${pressIndexContent.news.collectionFallbackImageFilename}`),
|
||||||
{
|
|
||||||
name: 'fallbackItems',
|
|
||||||
label: 'Fallback news cards',
|
|
||||||
type: 'array',
|
|
||||||
dbName: 'press_news_fb',
|
|
||||||
defaultValue: pressIndexContent.news.fallbackItems,
|
|
||||||
fields: newsFields,
|
|
||||||
},
|
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
114
src/migrations/20260731_143000_remove_press_record_fallbacks.ts
Normal file
114
src/migrations/20260731_143000_remove_press_record_fallbacks.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
import { type MigrateDownArgs, type MigrateUpArgs, sql } from '@payloadcms/db-sqlite'
|
||||||
|
|
||||||
|
type MigrationDB = MigrateUpArgs['db']
|
||||||
|
|
||||||
|
const ident = (value: string) => `\`${value.replace(/`/g, '``')}\``
|
||||||
|
const literal = (value: string) => `'${value.replace(/'/g, "''")}'`
|
||||||
|
|
||||||
|
const fallbackTables = [
|
||||||
|
'press_events_fb',
|
||||||
|
'_press_events_fb_v',
|
||||||
|
'press_news_fb',
|
||||||
|
'_press_news_fb_v',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
async function tableExists(db: MigrationDB, tableName: string) {
|
||||||
|
const rows = (await db.all(
|
||||||
|
sql.raw(`SELECT name FROM sqlite_master WHERE type = 'table' AND name = ${literal(tableName)}`),
|
||||||
|
)) as Array<{ name: string }>
|
||||||
|
|
||||||
|
return rows.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createIndex(db: MigrationDB, tableName: string, suffix: string, columnName: string) {
|
||||||
|
await db.run(
|
||||||
|
sql.raw(
|
||||||
|
`CREATE INDEX IF NOT EXISTS ${ident(`${tableName}_${suffix}_idx`)} ON ${ident(tableName)} (${ident(columnName)});`,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createLiveFallbackTable(
|
||||||
|
db: MigrationDB,
|
||||||
|
tableName: 'press_events_fb' | 'press_news_fb',
|
||||||
|
itemColumns: string,
|
||||||
|
) {
|
||||||
|
if (await tableExists(db, tableName)) return
|
||||||
|
|
||||||
|
await db.run(
|
||||||
|
sql.raw(`
|
||||||
|
CREATE TABLE ${ident(tableName)} (
|
||||||
|
${ident('_order')} integer NOT NULL,
|
||||||
|
${ident('_parent_id')} integer NOT NULL,
|
||||||
|
${ident('id')} text PRIMARY KEY NOT NULL,
|
||||||
|
${itemColumns},
|
||||||
|
FOREIGN KEY (${ident('image_id')}) REFERENCES ${ident('media')}(${ident('id')}) ON UPDATE no action ON DELETE set null,
|
||||||
|
FOREIGN KEY (${ident('_parent_id')}) REFERENCES ${ident('pages')}(${ident('id')}) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`),
|
||||||
|
)
|
||||||
|
|
||||||
|
await createIndex(db, tableName, 'order', '_order')
|
||||||
|
await createIndex(db, tableName, 'parent_id', '_parent_id')
|
||||||
|
await createIndex(db, tableName, 'image', 'image_id')
|
||||||
|
}
|
||||||
|
|
||||||
|
async function createVersionFallbackTable(
|
||||||
|
db: MigrationDB,
|
||||||
|
tableName: '_press_events_fb_v' | '_press_news_fb_v',
|
||||||
|
itemColumns: string,
|
||||||
|
) {
|
||||||
|
if (await tableExists(db, tableName)) return
|
||||||
|
|
||||||
|
await db.run(
|
||||||
|
sql.raw(`
|
||||||
|
CREATE TABLE ${ident(tableName)} (
|
||||||
|
${ident('_order')} integer NOT NULL,
|
||||||
|
${ident('_parent_id')} integer NOT NULL,
|
||||||
|
${ident('id')} integer PRIMARY KEY NOT NULL,
|
||||||
|
${itemColumns},
|
||||||
|
${ident('_uuid')} text,
|
||||||
|
FOREIGN KEY (${ident('image_id')}) REFERENCES ${ident('media')}(${ident('id')}) ON UPDATE no action ON DELETE set null,
|
||||||
|
FOREIGN KEY (${ident('_parent_id')}) REFERENCES ${ident('_pages_v')}(${ident('id')}) ON UPDATE no action ON DELETE cascade
|
||||||
|
);
|
||||||
|
`),
|
||||||
|
)
|
||||||
|
|
||||||
|
await createIndex(db, tableName, 'order', '_order')
|
||||||
|
await createIndex(db, tableName, 'parent_id', '_parent_id')
|
||||||
|
await createIndex(db, tableName, 'image', 'image_id')
|
||||||
|
}
|
||||||
|
|
||||||
|
const eventColumns = [
|
||||||
|
`${ident('title')} text`,
|
||||||
|
`${ident('date')} text`,
|
||||||
|
`${ident('location')} text`,
|
||||||
|
`${ident('cat')} text`,
|
||||||
|
`${ident('status')} text`,
|
||||||
|
`${ident('image_id')} integer`,
|
||||||
|
`${ident('img')} text`,
|
||||||
|
`${ident('desc')} text`,
|
||||||
|
`${ident('slug')} text`,
|
||||||
|
].join(',\n ')
|
||||||
|
|
||||||
|
const newsColumns = [
|
||||||
|
`${ident('title')} text`,
|
||||||
|
`${ident('excerpt')} text`,
|
||||||
|
`${ident('cat')} text`,
|
||||||
|
`${ident('image_id')} integer`,
|
||||||
|
`${ident('img')} text`,
|
||||||
|
`${ident('slug')} text`,
|
||||||
|
].join(',\n ')
|
||||||
|
|
||||||
|
export async function up({ db, payload: _payload, req: _req }: MigrateUpArgs): Promise<void> {
|
||||||
|
for (const tableName of fallbackTables) {
|
||||||
|
await db.run(sql.raw(`DROP TABLE IF EXISTS ${ident(tableName)};`))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down({ db, payload: _payload, req: _req }: MigrateDownArgs): Promise<void> {
|
||||||
|
await createLiveFallbackTable(db, 'press_events_fb', eventColumns)
|
||||||
|
await createVersionFallbackTable(db, '_press_events_fb_v', eventColumns)
|
||||||
|
await createLiveFallbackTable(db, 'press_news_fb', newsColumns)
|
||||||
|
await createVersionFallbackTable(db, '_press_news_fb_v', newsColumns)
|
||||||
|
}
|
||||||
@@ -15,6 +15,7 @@ import * as migration_20260713_163000_participation_supporting_awards_copy from
|
|||||||
import * as migration_20260713_194500_contact_submissions from './20260713_194500_contact_submissions';
|
import * as migration_20260713_194500_contact_submissions from './20260713_194500_contact_submissions';
|
||||||
import * as migration_20260713_213000_netzwerk_jury_selection from './20260713_213000_netzwerk_jury_selection';
|
import * as migration_20260713_213000_netzwerk_jury_selection from './20260713_213000_netzwerk_jury_selection';
|
||||||
import * as migration_20260713_220000_preistraeger_detail_cleanup from './20260713_220000_preistraeger_detail_cleanup';
|
import * as migration_20260713_220000_preistraeger_detail_cleanup from './20260713_220000_preistraeger_detail_cleanup';
|
||||||
|
import * as migration_20260731_143000_remove_press_record_fallbacks from './20260731_143000_remove_press_record_fallbacks';
|
||||||
|
|
||||||
export const migrations = [
|
export const migrations = [
|
||||||
{
|
{
|
||||||
@@ -102,4 +103,9 @@ export const migrations = [
|
|||||||
down: migration_20260713_220000_preistraeger_detail_cleanup.down,
|
down: migration_20260713_220000_preistraeger_detail_cleanup.down,
|
||||||
name: '20260713_220000_preistraeger_detail_cleanup',
|
name: '20260713_220000_preistraeger_detail_cleanup',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
up: migration_20260731_143000_remove_press_record_fallbacks.up,
|
||||||
|
down: migration_20260731_143000_remove_press_record_fallbacks.down,
|
||||||
|
name: '20260731_143000_remove_press_record_fallbacks',
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1495,7 +1495,7 @@ export interface Page {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections; fallback cards remain editable here.
|
* Edit the Presse listing page chrome. Event and news records are managed through Events and Presse collections.
|
||||||
*/
|
*/
|
||||||
pressIndex?: {
|
pressIndex?: {
|
||||||
/**
|
/**
|
||||||
@@ -1512,7 +1512,7 @@ export interface Page {
|
|||||||
description?: string | null;
|
description?: string | null;
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Section header, labels, and fallback event rows.
|
* Section header, labels, and missing-field presentation fallbacks.
|
||||||
*/
|
*/
|
||||||
events?: {
|
events?: {
|
||||||
eyebrow?: string | null;
|
eyebrow?: string | null;
|
||||||
@@ -1536,23 +1536,9 @@ export interface Page {
|
|||||||
id?: string | null;
|
id?: string | null;
|
||||||
}[]
|
}[]
|
||||||
| null;
|
| null;
|
||||||
fallbackItems?:
|
|
||||||
| {
|
|
||||||
title?: string | null;
|
|
||||||
date?: string | null;
|
|
||||||
location?: string | null;
|
|
||||||
cat?: string | null;
|
|
||||||
status?: string | null;
|
|
||||||
image?: (number | null) | Media;
|
|
||||||
img?: string | null;
|
|
||||||
desc?: string | null;
|
|
||||||
slug?: string | null;
|
|
||||||
id?: string | null;
|
|
||||||
}[]
|
|
||||||
| null;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Section header, read-more label, and fallback news cards.
|
* Section header, read-more label, and missing-field presentation fallbacks.
|
||||||
*/
|
*/
|
||||||
news?: {
|
news?: {
|
||||||
eyebrow?: string | null;
|
eyebrow?: string | null;
|
||||||
@@ -1565,17 +1551,6 @@ export interface Page {
|
|||||||
* Current frontend image: /images/gala-saal-overview.jpg
|
* Current frontend image: /images/gala-saal-overview.jpg
|
||||||
*/
|
*/
|
||||||
collectionFallbackImage?: (number | null) | Media;
|
collectionFallbackImage?: (number | null) | Media;
|
||||||
fallbackItems?:
|
|
||||||
| {
|
|
||||||
title?: string | null;
|
|
||||||
excerpt?: string | null;
|
|
||||||
cat?: string | null;
|
|
||||||
image?: (number | null) | Media;
|
|
||||||
img?: string | null;
|
|
||||||
slug?: string | null;
|
|
||||||
id?: string | null;
|
|
||||||
}[]
|
|
||||||
| null;
|
|
||||||
};
|
};
|
||||||
/**
|
/**
|
||||||
* Download button and press contact block.
|
* Download button and press contact block.
|
||||||
@@ -4176,20 +4151,6 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
label?: T;
|
label?: T;
|
||||||
id?: T;
|
id?: T;
|
||||||
};
|
};
|
||||||
fallbackItems?:
|
|
||||||
| T
|
|
||||||
| {
|
|
||||||
title?: T;
|
|
||||||
date?: T;
|
|
||||||
location?: T;
|
|
||||||
cat?: T;
|
|
||||||
status?: T;
|
|
||||||
image?: T;
|
|
||||||
img?: T;
|
|
||||||
desc?: T;
|
|
||||||
slug?: T;
|
|
||||||
id?: T;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
news?:
|
news?:
|
||||||
| T
|
| T
|
||||||
@@ -4201,17 +4162,6 @@ export interface PagesSelect<T extends boolean = true> {
|
|||||||
missingTitleLabel?: T;
|
missingTitleLabel?: T;
|
||||||
missingCategoryLabel?: T;
|
missingCategoryLabel?: T;
|
||||||
collectionFallbackImage?: T;
|
collectionFallbackImage?: T;
|
||||||
fallbackItems?:
|
|
||||||
| T
|
|
||||||
| {
|
|
||||||
title?: T;
|
|
||||||
excerpt?: T;
|
|
||||||
cat?: T;
|
|
||||||
image?: T;
|
|
||||||
img?: T;
|
|
||||||
slug?: T;
|
|
||||||
id?: T;
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
downloads?:
|
downloads?:
|
||||||
| T
|
| T
|
||||||
|
|||||||
@@ -1,27 +1,11 @@
|
|||||||
import 'dotenv/config'
|
import 'dotenv/config'
|
||||||
|
|
||||||
import config from '@payload-config'
|
import config from '@payload-config'
|
||||||
import { getPayload, type Payload } from 'payload'
|
import { getPayload } from 'payload'
|
||||||
import { mediaByFilename } from './mediaByFilename'
|
import { mediaByFilename } from './mediaByFilename'
|
||||||
|
|
||||||
import { pressIndexContent } from '@/spa/pressIndexContent'
|
import { pressIndexContent } from '@/spa/pressIndexContent'
|
||||||
|
|
||||||
const filenameFromPath = (source: string) => source.split('?')[0]?.split('/').filter(Boolean).pop()
|
|
||||||
|
|
||||||
const fallbackItemsWithImages = async <T extends { img?: string }>(
|
|
||||||
payload: Payload,
|
|
||||||
items: T[],
|
|
||||||
) =>
|
|
||||||
Promise.all(
|
|
||||||
items.map(async (item) => {
|
|
||||||
const filename = item.img ? filenameFromPath(item.img) : undefined
|
|
||||||
return {
|
|
||||||
...item,
|
|
||||||
image: filename ? await mediaByFilename(payload, filename) : undefined,
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const payload = await getPayload({ config })
|
const payload = await getPayload({ config })
|
||||||
|
|
||||||
@@ -48,11 +32,6 @@ async function main() {
|
|||||||
const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero
|
const { imageFilename: _heroFilename, ...heroContent } = pressIndexContent.hero
|
||||||
const { collectionFallbackImageFilename: _eventFallbackFilename, ...eventsContent } = pressIndexContent.events
|
const { collectionFallbackImageFilename: _eventFallbackFilename, ...eventsContent } = pressIndexContent.events
|
||||||
const { collectionFallbackImageFilename: _newsFallbackFilename, ...newsContent } = pressIndexContent.news
|
const { collectionFallbackImageFilename: _newsFallbackFilename, ...newsContent } = pressIndexContent.news
|
||||||
const [eventFallbackItems, newsFallbackItems] = await Promise.all([
|
|
||||||
fallbackItemsWithImages(payload, pressIndexContent.events.fallbackItems),
|
|
||||||
fallbackItemsWithImages(payload, pressIndexContent.news.fallbackItems),
|
|
||||||
])
|
|
||||||
|
|
||||||
await payload.update({
|
await payload.update({
|
||||||
collection: 'pages',
|
collection: 'pages',
|
||||||
id: page.id,
|
id: page.id,
|
||||||
@@ -68,12 +47,10 @@ async function main() {
|
|||||||
events: {
|
events: {
|
||||||
...eventsContent,
|
...eventsContent,
|
||||||
collectionFallbackImage: eventFallbackImage,
|
collectionFallbackImage: eventFallbackImage,
|
||||||
fallbackItems: eventFallbackItems,
|
|
||||||
},
|
},
|
||||||
news: {
|
news: {
|
||||||
...newsContent,
|
...newsContent,
|
||||||
collectionFallbackImage: newsFallbackImage,
|
collectionFallbackImage: newsFallbackImage,
|
||||||
fallbackItems: newsFallbackItems,
|
|
||||||
},
|
},
|
||||||
downloads: {
|
downloads: {
|
||||||
...pressIndexContent.downloads,
|
...pressIndexContent.downloads,
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ export default function HomeWinnersSection({ content }: { content?: HomeWinnersC
|
|||||||
const placeholderImage = usePreistraegerPlaceholderImage();
|
const placeholderImage = usePreistraegerPlaceholderImage();
|
||||||
const cmsPreistraeger = useCmsCollection('preistraeger');
|
const cmsPreistraeger = useCmsCollection('preistraeger');
|
||||||
const winnersSection = content || {};
|
const winnersSection = content || {};
|
||||||
const homeWinners = resolveHomeWinners(winnersSection.featured, cmsPreistraeger, WINNERS);
|
const homeWinners = resolveHomeWinners(winnersSection.featured, cmsPreistraeger);
|
||||||
const scrollDesktopWinners = !isMobile && homeWinners.length > 8;
|
const scrollDesktopWinners = !isMobile && homeWinners.length > 8;
|
||||||
const scrollWinnerGrid = isMobile || scrollDesktopWinners;
|
const scrollWinnerGrid = isMobile || scrollDesktopWinners;
|
||||||
|
|
||||||
|
|||||||
@@ -3,21 +3,12 @@ import { resolveCmsRelationships, type CmsRouteDoc } from '@/spa/cmsRoute'
|
|||||||
const FALLBACK_YEAR = 2025
|
const FALLBACK_YEAR = 2025
|
||||||
const FALLBACK_LIMIT = 8
|
const FALLBACK_LIMIT = 8
|
||||||
|
|
||||||
type FallbackWinner = {
|
export function resolveHomeWinners(featured: unknown, cmsWinners: CmsRouteDoc[]) {
|
||||||
id: string | number
|
|
||||||
year?: string | number
|
|
||||||
}
|
|
||||||
|
|
||||||
export function resolveHomeWinners(
|
|
||||||
featured: unknown,
|
|
||||||
cmsWinners: CmsRouteDoc[],
|
|
||||||
fallbackWinners: FallbackWinner[],
|
|
||||||
) {
|
|
||||||
const selectedWinners = Array.isArray(featured) ? featured : []
|
const selectedWinners = Array.isArray(featured) ? featured : []
|
||||||
|
|
||||||
if (selectedWinners.length) return resolveCmsRelationships(selectedWinners, cmsWinners)
|
if (selectedWinners.length) return resolveCmsRelationships(selectedWinners, cmsWinners)
|
||||||
|
|
||||||
return (cmsWinners.length ? cmsWinners : fallbackWinners)
|
return cmsWinners
|
||||||
.filter((winner) => Number(winner.year) === FALLBACK_YEAR)
|
.filter((winner) => Number(winner.year) === FALLBACK_YEAR)
|
||||||
.slice(0, FALLBACK_LIMIT)
|
.slice(0, FALLBACK_LIMIT)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -483,15 +483,11 @@ const Netzwerk: React.FC = () => {
|
|||||||
const sponsoring = { ...netzwerkContent.sponsoring, ...(cms.sponsoring || {}) };
|
const sponsoring = { ...netzwerkContent.sponsoring, ...(cms.sponsoring || {}) };
|
||||||
const patronPeople = fallbackArray<PatronPerson>(patronage.people, netzwerkContent.patronage.people);
|
const patronPeople = fallbackArray<PatronPerson>(patronage.people, netzwerkContent.patronage.people);
|
||||||
const cmsJurySelection = cms.jury?.members;
|
const cmsJurySelection = cms.jury?.members;
|
||||||
const hasCmsJurySelection = cmsJurySelection !== undefined;
|
|
||||||
const sourcedJuryDocs = selectJuryDocs(cmsJurySelection, cmsJuryMembers);
|
const sourcedJuryDocs = selectJuryDocs(cmsJurySelection, cmsJuryMembers);
|
||||||
const normalizedJuryMembers = sourcedJuryDocs
|
const normalizedJuryMembers = sourcedJuryDocs
|
||||||
.map(normalizeJuryMember)
|
.map(normalizeJuryMember)
|
||||||
.filter((member) => member.active !== false && member.name);
|
.filter((member) => member.active !== false && member.name);
|
||||||
const jurySourceMembers: JuryMember[] = normalizedJuryMembers.length || hasCmsJurySelection
|
const juryMembers = sortJuryMembersQuoteFirst(normalizedJuryMembers);
|
||||||
? normalizedJuryMembers
|
|
||||||
: netzwerkContent.jury.members;
|
|
||||||
const juryMembers = sortJuryMembersQuoteFirst(jurySourceMembers);
|
|
||||||
const expertiseRows = fallbackArray<ExpertiseRow>(expertise.rows, netzwerkContent.expertise.rows);
|
const expertiseRows = fallbackArray<ExpertiseRow>(expertise.rows, netzwerkContent.expertise.rows);
|
||||||
const partners = sortedPartners(cmsPartners)
|
const partners = sortedPartners(cmsPartners)
|
||||||
.filter((partner) => partner.active !== false)
|
.filter((partner) => partner.active !== false)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import React, { useState, useMemo } from 'react';
|
import React, { useState, useMemo } from 'react';
|
||||||
import { Link } from '@/spa/router';
|
import { Link } from '@/spa/router';
|
||||||
import { Trophy, ArrowRight, Medal } from 'lucide-react';
|
import { Trophy, ArrowRight, Medal } from 'lucide-react';
|
||||||
import { WINNERS, type Winner } from '@/spa/data/winners';
|
import type { Winner } from '@/spa/data/winners';
|
||||||
import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute';
|
import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute';
|
||||||
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||||
@@ -151,8 +151,7 @@ export default function Preistraeger() {
|
|||||||
);
|
);
|
||||||
const winners = useMemo(() => {
|
const winners = useMemo(() => {
|
||||||
const fallbackWinner = card.fallbackWinner || preistraegerIndexContent.card.fallbackWinner;
|
const fallbackWinner = card.fallbackWinner || preistraegerIndexContent.card.fallbackWinner;
|
||||||
if (cmsPreistraeger.length) return cmsPreistraeger.map((doc) => normalizeWinner(doc, placeholderImage, fallbackWinner)).filter((winner) => winner.slug);
|
return cmsPreistraeger.map((doc) => normalizeWinner(doc, placeholderImage, fallbackWinner)).filter((winner) => winner.slug);
|
||||||
return WINNERS;
|
|
||||||
}, [card, cmsPreistraeger, placeholderImage]);
|
}, [card, cmsPreistraeger, placeholderImage]);
|
||||||
const periods = useMemo<WinnerPeriod[]>(() => {
|
const periods = useMemo<WinnerPeriod[]>(() => {
|
||||||
const annualYears = [...new Set(
|
const annualYears = [...new Set(
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Link, useParams, Navigate } from '@/spa/router';
|
import { Link, useParams, Navigate } from '@/spa/router';
|
||||||
import { ArrowLeft, 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 { useCmsRoute } from '@/spa/cmsRoute';
|
||||||
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||||
@@ -79,7 +78,7 @@ export default function PreistraegerDetail() {
|
|||||||
website: String(cmsWinner.website || '#'),
|
website: String(cmsWinner.website || '#'),
|
||||||
hasMedia: Boolean(cmsWinner.hasMedia),
|
hasMedia: Boolean(cmsWinner.hasMedia),
|
||||||
detailPage: (cmsWinner.detailPage || {}) as PreistraegerDetailCms,
|
detailPage: (cmsWinner.detailPage || {}) as PreistraegerDetailCms,
|
||||||
} : WINNERS.find(w => w.slug === slug);
|
} : undefined;
|
||||||
const isMobile = useIsMobile();
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const detail = (winner && 'detailPage' in winner ? { ...preistraegerDetailContent, ...(winner.detailPage || {}) } : cmsDetail) as PreistraegerDetailCms;
|
const detail = (winner && 'detailPage' in winner ? { ...preistraegerDetailContent, ...(winner.detailPage || {}) } : cmsDetail) as PreistraegerDetailCms;
|
||||||
|
|||||||
@@ -2,9 +2,15 @@ import React from 'react';
|
|||||||
import { Calendar, MapPin } from 'lucide-react';
|
import { Calendar, MapPin } from 'lucide-react';
|
||||||
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
import MunichSkylineBg from '@/spa/components/ui/munich-skyline-bg';
|
||||||
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
import { useIsMobile } from '@/spa/hooks/useIsMobile';
|
||||||
import { useCmsCollection, useCmsRoute, type CmsRouteDoc } from '@/spa/cmsRoute';
|
import { useCmsCollection, useCmsRoute } from '@/spa/cmsRoute';
|
||||||
import { docImageUrl, mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
import { mediaAlt, mediaUrl } from '@/spa/cmsMediaField';
|
||||||
import Image from '@/spa/components/ui/UnoptimizedImage'
|
import Image from '@/spa/components/ui/UnoptimizedImage'
|
||||||
|
import {
|
||||||
|
pressEventsFromCms,
|
||||||
|
pressNewsFromCms,
|
||||||
|
type PressEventItem,
|
||||||
|
type PressNewsItem,
|
||||||
|
} from '@/spa/pressCollectionItems';
|
||||||
import { pressIndexContent } from '@/spa/pressIndexContent';
|
import { pressIndexContent } from '@/spa/pressIndexContent';
|
||||||
import { Link } from '@/spa/router';
|
import { Link } from '@/spa/router';
|
||||||
|
|
||||||
@@ -15,14 +21,11 @@ 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; fallbackItems?: PressEventItem[] }
|
events?: Partial<typeof pressIndexContent.events> & { collectionFallbackImage?: unknown }
|
||||||
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown; fallbackItems?: PressNewsItem[] }
|
news?: Partial<typeof pressIndexContent.news> & { collectionFallbackImage?: unknown }
|
||||||
downloads?: Partial<typeof pressIndexContent.downloads> & { kitFile?: unknown }
|
downloads?: Partial<typeof pressIndexContent.downloads> & { kitFile?: unknown }
|
||||||
}
|
}
|
||||||
|
|
||||||
type PressEventItem = (typeof pressIndexContent.events.fallbackItems)[number] & { image?: unknown }
|
|
||||||
type PressNewsItem = (typeof pressIndexContent.news.fallbackItems)[number] & { image?: unknown }
|
|
||||||
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -39,43 +42,6 @@ const statusLabelMap = (items: unknown) =>
|
|||||||
return labels
|
return labels
|
||||||
}, {})
|
}, {})
|
||||||
|
|
||||||
const normalizeStatus = (status: unknown, labels: Record<string, string>, defaultStatusLabel: string) => {
|
|
||||||
const value = fallbackText(status, defaultStatusLabel)
|
|
||||||
return labels[value] || value
|
|
||||||
}
|
|
||||||
|
|
||||||
const detailPath = (spaPath: unknown, slug: unknown, prefix: '/presse/events' | '/presse/blog') => {
|
|
||||||
const path = fallbackText(spaPath, '')
|
|
||||||
if (path.startsWith(`${prefix}/`)) return path
|
|
||||||
|
|
||||||
const pathSlug = fallbackText(slug, '').replace(/^\/+/, '')
|
|
||||||
return pathSlug ? `${prefix}/${pathSlug}` : prefix
|
|
||||||
}
|
|
||||||
|
|
||||||
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: detailPath(event.spaPath, event.slug, '/presse/events'),
|
|
||||||
})
|
|
||||||
|
|
||||||
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 || fallbackText(newsSection.missingCategoryLabel, pressIndexContent.news.missingCategoryLabel)),
|
|
||||||
img: docImageUrl(post, fallbackImage, 'heroImage'),
|
|
||||||
slug: detailPath(post.spaPath, post.slug, '/presse/blog'),
|
|
||||||
})
|
|
||||||
|
|
||||||
function Lines({ text }: { text: string }) {
|
function Lines({ text }: { text: string }) {
|
||||||
return <>{text.split('\n').map((line, i) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>
|
return <>{text.split('\n').map((line, i) => <React.Fragment key={`${line}-${i}`}>{i > 0 && <br />}{line}</React.Fragment>)}</>
|
||||||
}
|
}
|
||||||
@@ -185,23 +151,14 @@ const Press: React.FC = () => {
|
|||||||
const eventStatusLabels = React.useMemo(() => statusLabelMap(eventsSection.statusLabels), [eventsSection.statusLabels]);
|
const eventStatusLabels = React.useMemo(() => statusLabelMap(eventsSection.statusLabels), [eventsSection.statusLabels]);
|
||||||
const eventCollectionFallbackImage = mediaUrl(eventsSection.collectionFallbackImage, `/images/${pressIndexContent.events.collectionFallbackImageFilename}`);
|
const eventCollectionFallbackImage = mediaUrl(eventsSection.collectionFallbackImage, `/images/${pressIndexContent.events.collectionFallbackImageFilename}`);
|
||||||
const newsCollectionFallbackImage = mediaUrl(newsSection.collectionFallbackImage, `/images/${pressIndexContent.news.collectionFallbackImageFilename}`);
|
const newsCollectionFallbackImage = mediaUrl(newsSection.collectionFallbackImage, `/images/${pressIndexContent.news.collectionFallbackImageFilename}`);
|
||||||
const combinedEvents = React.useMemo(() => {
|
const combinedEvents = React.useMemo(
|
||||||
if (cmsEvents.length) {
|
() => pressEventsFromCms(cmsEvents, eventCollectionFallbackImage, eventStatusLabels, eventsSection),
|
||||||
return cmsEvents.map((event) =>
|
[cmsEvents, eventCollectionFallbackImage, eventStatusLabels, eventsSection],
|
||||||
eventFromCms(
|
);
|
||||||
event,
|
const combinedNews = React.useMemo(
|
||||||
eventCollectionFallbackImage,
|
() => pressNewsFromCms(cmsPosts, newsCollectionFallbackImage, newsSection),
|
||||||
eventStatusLabels,
|
[cmsPosts, newsCollectionFallbackImage, newsSection],
|
||||||
eventsSection,
|
);
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return fallbackArray<PressEventItem>(eventsSection.fallbackItems, pressIndexContent.events.fallbackItems);
|
|
||||||
}, [cmsEvents, eventCollectionFallbackImage, eventStatusLabels, eventsSection]);
|
|
||||||
const combinedNews = React.useMemo(() => {
|
|
||||||
if (cmsPosts.length) return cmsPosts.map((post) => postFromCms(post, newsCollectionFallbackImage, newsSection));
|
|
||||||
return fallbackArray<PressNewsItem>(newsSection.fallbackItems, pressIndexContent.news.fallbackItems);
|
|
||||||
}, [cmsPosts, newsCollectionFallbackImage, newsSection]);
|
|
||||||
return (
|
return (
|
||||||
<div className="animate-fade-in">
|
<div className="animate-fade-in">
|
||||||
|
|
||||||
|
|||||||
102
src/spa/pressCollectionItems.ts
Normal file
102
src/spa/pressCollectionItems.ts
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
import { docImageUrl } from '@/spa/cmsMediaField'
|
||||||
|
import type { CmsRouteDoc } from '@/spa/cmsRoute'
|
||||||
|
import { pressIndexContent } from '@/spa/pressIndexContent'
|
||||||
|
|
||||||
|
export type PressEventItem = {
|
||||||
|
title: string
|
||||||
|
date: string
|
||||||
|
location: string
|
||||||
|
cat: string
|
||||||
|
status: string
|
||||||
|
img: string
|
||||||
|
desc: string
|
||||||
|
slug: string
|
||||||
|
image?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
export type PressNewsItem = {
|
||||||
|
title: string
|
||||||
|
excerpt: string
|
||||||
|
cat: string
|
||||||
|
img: string
|
||||||
|
slug: string
|
||||||
|
image?: unknown
|
||||||
|
}
|
||||||
|
|
||||||
|
type PressEventsSection = Partial<typeof pressIndexContent.events>
|
||||||
|
type PressNewsSection = Partial<typeof pressIndexContent.news>
|
||||||
|
|
||||||
|
const fallbackText = (value: unknown, fallback: string) =>
|
||||||
|
typeof value === 'string' && value.length > 0 ? value : fallback
|
||||||
|
|
||||||
|
const normalizeStatus = (
|
||||||
|
status: unknown,
|
||||||
|
labels: Record<string, string>,
|
||||||
|
defaultStatusLabel: string,
|
||||||
|
) => {
|
||||||
|
const value = fallbackText(status, defaultStatusLabel)
|
||||||
|
return labels[value] || value
|
||||||
|
}
|
||||||
|
|
||||||
|
const detailPath = (spaPath: unknown, slug: unknown, prefix: '/presse/events' | '/presse/blog') => {
|
||||||
|
const path = fallbackText(spaPath, '')
|
||||||
|
if (path.startsWith(`${prefix}/`)) return path
|
||||||
|
|
||||||
|
const pathSlug = fallbackText(slug, '').replace(/^\/+/, '')
|
||||||
|
return pathSlug ? `${prefix}/${pathSlug}` : prefix
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pressEventsFromCms(
|
||||||
|
events: CmsRouteDoc[],
|
||||||
|
fallbackImage: string,
|
||||||
|
labels: Record<string, string>,
|
||||||
|
section: PressEventsSection,
|
||||||
|
): PressEventItem[] {
|
||||||
|
return events.map((event) => ({
|
||||||
|
title: String(
|
||||||
|
event.title ||
|
||||||
|
fallbackText(section.missingTitleLabel, pressIndexContent.events.missingTitleLabel),
|
||||||
|
),
|
||||||
|
date: String(
|
||||||
|
event.date ||
|
||||||
|
fallbackText(section.missingDateLabel, pressIndexContent.events.missingDateLabel),
|
||||||
|
),
|
||||||
|
location: String(
|
||||||
|
event.location ||
|
||||||
|
event.venue ||
|
||||||
|
fallbackText(section.missingLocationLabel, pressIndexContent.events.missingLocationLabel),
|
||||||
|
),
|
||||||
|
cat: String(
|
||||||
|
event.category ||
|
||||||
|
fallbackText(section.missingCategoryLabel, pressIndexContent.events.missingCategoryLabel),
|
||||||
|
),
|
||||||
|
status: normalizeStatus(
|
||||||
|
event.status,
|
||||||
|
labels,
|
||||||
|
fallbackText(section.defaultStatusLabel, pressIndexContent.events.defaultStatusLabel),
|
||||||
|
),
|
||||||
|
img: docImageUrl(event, fallbackImage),
|
||||||
|
desc: String(event.description || event.meta?.description || ''),
|
||||||
|
slug: detailPath(event.spaPath, event.slug, '/presse/events'),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
export function pressNewsFromCms(
|
||||||
|
posts: CmsRouteDoc[],
|
||||||
|
fallbackImage: string,
|
||||||
|
section: PressNewsSection,
|
||||||
|
): PressNewsItem[] {
|
||||||
|
return posts.map((post) => ({
|
||||||
|
title: String(
|
||||||
|
post.title ||
|
||||||
|
fallbackText(section.missingTitleLabel, pressIndexContent.news.missingTitleLabel),
|
||||||
|
),
|
||||||
|
excerpt: String(post.excerpt || post.meta?.description || ''),
|
||||||
|
cat: String(
|
||||||
|
post.cat ||
|
||||||
|
fallbackText(section.missingCategoryLabel, pressIndexContent.news.missingCategoryLabel),
|
||||||
|
),
|
||||||
|
img: docImageUrl(post, fallbackImage, 'heroImage'),
|
||||||
|
slug: detailPath(post.spaPath, post.slug, '/presse/blog'),
|
||||||
|
}))
|
||||||
|
}
|
||||||
@@ -24,38 +24,6 @@ export const pressIndexContent = {
|
|||||||
{ value: 'intern', label: 'Intern' },
|
{ value: 'intern', label: 'Intern' },
|
||||||
{ value: 'abgeschlossen', label: 'Abgeschlossen' },
|
{ value: 'abgeschlossen', label: 'Abgeschlossen' },
|
||||||
],
|
],
|
||||||
fallbackItems: [
|
|
||||||
{
|
|
||||||
title: 'Preisverleihung 2026',
|
|
||||||
date: '22. Okt 2026',
|
|
||||||
location: 'München, Residenz',
|
|
||||||
cat: 'Gala / Event',
|
|
||||||
status: 'In Planung',
|
|
||||||
img: '/images/buehne-moderatoren.jpg',
|
|
||||||
desc: 'Der glanzvolle Höhepunkt des Jahres. Die Verleihung der Preise in der prachtvollen Kulisse der Residenz München.',
|
|
||||||
slug: '/presse/events/preisverleihung-2026',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Mittelstands-Gipfel',
|
|
||||||
date: '15. Juni 2026',
|
|
||||||
location: 'Nürnberg',
|
|
||||||
cat: 'Workshop',
|
|
||||||
status: 'Anmeldung offen',
|
|
||||||
img: '/images/networking-innenhof.jpg',
|
|
||||||
desc: 'Regionaler Austausch und Best-Practices für bayerische Unternehmen auf dem Weg zum Preis.',
|
|
||||||
slug: '/presse/events/mittelstands-gipfel-2026',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Nominierten-Auswahl',
|
|
||||||
date: '04. Aug 2026',
|
|
||||||
location: 'Regensburg',
|
|
||||||
cat: 'Jury-Sitzung',
|
|
||||||
status: 'Intern',
|
|
||||||
img: '/images/saal-gedeckt.jpg',
|
|
||||||
desc: 'Das Gremium sichtet die Ergebnisse der Audits und legt die Nominierten für die Hauptpreise fest.',
|
|
||||||
slug: '/presse/events/nominierten-auswahl-2026',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
news: {
|
news: {
|
||||||
eyebrow: 'Newsroom',
|
eyebrow: 'Newsroom',
|
||||||
@@ -65,29 +33,6 @@ export const pressIndexContent = {
|
|||||||
missingTitleLabel: 'Presse',
|
missingTitleLabel: 'Presse',
|
||||||
missingCategoryLabel: 'Presse',
|
missingCategoryLabel: 'Presse',
|
||||||
collectionFallbackImageFilename: 'gala-saal-overview.jpg',
|
collectionFallbackImageFilename: 'gala-saal-overview.jpg',
|
||||||
fallbackItems: [
|
|
||||||
{
|
|
||||||
title: 'Wie Bayerns KMU die KI nutzen',
|
|
||||||
excerpt: 'Ein Deep-Dive in die Bewerbungsunterlagen 2024 zeigt: Der Mittelstand treibt die Digitalisierung aktiv voran.',
|
|
||||||
cat: 'Innovation',
|
|
||||||
img: '/images/gala-saal-overview.jpg',
|
|
||||||
slug: '/presse/blog/ki-nutzung-bayerischer-kmu',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Start der Ehrenamts-Initiative',
|
|
||||||
excerpt: 'Gemeinsam mit unseren Partnern fördern wir soziale Projekte mittelständischer Unternehmen.',
|
|
||||||
cat: 'Engagement',
|
|
||||||
img: '/images/networking-innenhof.jpg',
|
|
||||||
slug: '/presse/blog/ehrenamts-initiative-2026',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Bayerische Wirtschaft wächst',
|
|
||||||
excerpt: 'Neue Prognosen zeigen ein stabiles Wachstum für den Mittelstand – gute Aussichten für die Awards.',
|
|
||||||
cat: 'Wirtschaft',
|
|
||||||
img: '/images/gala-dinner.jpg',
|
|
||||||
slug: '/presse/blog/bayerische-wirtschaft-waechst',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
},
|
||||||
downloads: {
|
downloads: {
|
||||||
eyebrow: 'Pressekontakt & Material',
|
eyebrow: 'Pressekontakt & Material',
|
||||||
|
|||||||
@@ -18,18 +18,14 @@ const published2025Winners: CmsRouteDoc[] = Array.from({ length: 9 }, (_, index)
|
|||||||
describe('home winner selection', () => {
|
describe('home winner selection', () => {
|
||||||
it('uses a non-empty CMS selection without appending winners from the fallback year', () => {
|
it('uses a non-empty CMS selection without appending winners from the fallback year', () => {
|
||||||
const featured = selected2026Winners.map(({ id }) => id)
|
const featured = selected2026Winners.map(({ id }) => id)
|
||||||
const winners = resolveHomeWinners(
|
const winners = resolveHomeWinners(featured, [...selected2026Winners, ...published2025Winners])
|
||||||
featured,
|
|
||||||
[...selected2026Winners, ...published2025Winners],
|
|
||||||
[],
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(winners).toEqual(selected2026Winners)
|
expect(winners).toEqual(selected2026Winners)
|
||||||
expect(winners.some((winner) => winner.year === 2025)).toBe(false)
|
expect(winners.some((winner) => winner.year === 2025)).toBe(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('falls back to at most eight 2025 winners when the CMS selection is empty', () => {
|
it('falls back to at most eight 2025 winners when the CMS selection is empty', () => {
|
||||||
const winners = resolveHomeWinners([], published2025Winners, [])
|
const winners = resolveHomeWinners([], published2025Winners)
|
||||||
|
|
||||||
expect(winners).toEqual(published2025Winners.slice(0, 8))
|
expect(winners).toEqual(published2025Winners.slice(0, 8))
|
||||||
})
|
})
|
||||||
@@ -37,12 +33,8 @@ describe('home winner selection', () => {
|
|||||||
it('omits an unpublished selected winner without changing the remaining page order', () => {
|
it('omits an unpublished selected winner without changing the remaining page order', () => {
|
||||||
const [first, unpublished, third] = selected2026Winners
|
const [first, unpublished, third] = selected2026Winners
|
||||||
const selection = [third, { ...unpublished, image: { id: 99 } }, first]
|
const selection = [third, { ...unpublished, image: { id: 99 } }, first]
|
||||||
const publicWinners = resolveHomeWinners(selection, [first, third], published2025Winners)
|
const publicWinners = resolveHomeWinners(selection, [first, third])
|
||||||
const previewWinners = resolveHomeWinners(
|
const previewWinners = resolveHomeWinners(selection, [first, unpublished, third])
|
||||||
selection,
|
|
||||||
[first, unpublished, third],
|
|
||||||
published2025Winners,
|
|
||||||
)
|
|
||||||
|
|
||||||
expect(publicWinners).toEqual([third, first])
|
expect(publicWinners).toEqual([third, first])
|
||||||
expect(previewWinners).toEqual([third, unpublished, first])
|
expect(previewWinners).toEqual([third, unpublished, first])
|
||||||
@@ -51,8 +43,10 @@ describe('home winner selection', () => {
|
|||||||
it('does not replace an entirely unpublished CMS selection with fallback winners', () => {
|
it('does not replace an entirely unpublished CMS selection with fallback winners', () => {
|
||||||
const [unpublished] = selected2026Winners
|
const [unpublished] = selected2026Winners
|
||||||
|
|
||||||
expect(
|
expect(resolveHomeWinners([{ ...unpublished, image: { id: 99 } }], [])).toEqual([])
|
||||||
resolveHomeWinners([{ ...unpublished, image: { id: 99 } }], [], published2025Winners),
|
})
|
||||||
).toEqual([])
|
|
||||||
|
it('does not resurrect static winners when the published collection is empty', () => {
|
||||||
|
expect(resolveHomeWinners([], [])).toEqual([])
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ describe('Netzwerk jury members', () => {
|
|||||||
expect(selectJuryDocs([3, { id: 1 }], docs).map((member) => member.id)).toEqual([3, 1])
|
expect(selectJuryDocs([3, { id: 1 }], docs).map((member) => member.id)).toEqual([3, 1])
|
||||||
expect(selectJuryDocs([], docs)).toEqual([])
|
expect(selectJuryDocs([], docs)).toEqual([])
|
||||||
expect(selectJuryDocs(undefined, docs)).toEqual(docs)
|
expect(selectJuryDocs(undefined, docs)).toEqual(docs)
|
||||||
|
expect(selectJuryDocs(undefined, [])).toEqual([])
|
||||||
})
|
})
|
||||||
|
|
||||||
it('omits an unpublished selected member publicly and restores it in preview order', () => {
|
it('omits an unpublished selected member publicly and restores it in preview order', () => {
|
||||||
|
|||||||
16
tests/int/press-collection-items.int.spec.ts
Normal file
16
tests/int/press-collection-items.int.spec.ts
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import { describe, expect, it } from 'vitest'
|
||||||
|
|
||||||
|
import { pressEventsFromCms, pressNewsFromCms } from '@/spa/pressCollectionItems'
|
||||||
|
import { pressIndexContent } from '@/spa/pressIndexContent'
|
||||||
|
|
||||||
|
describe('press collection items', () => {
|
||||||
|
it('does not replace an empty published events collection with demo event rows', () => {
|
||||||
|
expect(
|
||||||
|
pressEventsFromCms([], '/images/event-placeholder.jpg', {}, pressIndexContent.events),
|
||||||
|
).toEqual([])
|
||||||
|
})
|
||||||
|
|
||||||
|
it('does not replace an empty published posts collection with demo news cards', () => {
|
||||||
|
expect(pressNewsFromCms([], '/images/news-placeholder.jpg', pressIndexContent.news)).toEqual([])
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -202,11 +202,11 @@ describe('public route hydration', () => {
|
|||||||
const publicDocs = await findFixtureDocs('preistraeger', ids, false)
|
const publicDocs = await findFixtureDocs('preistraeger', ids, false)
|
||||||
const previewDocs = await findFixtureDocs('preistraeger', ids, true)
|
const previewDocs = await findFixtureDocs('preistraeger', ids, true)
|
||||||
|
|
||||||
expect(resolveHomeWinners(selection, publicDocs, []).map((doc) => doc.id)).toEqual([
|
expect(resolveHomeWinners(selection, publicDocs).map((doc) => doc.id)).toEqual([
|
||||||
third.id,
|
third.id,
|
||||||
first.id,
|
first.id,
|
||||||
])
|
])
|
||||||
expect(resolveHomeWinners(selection, previewDocs, []).map((doc) => doc.id)).toEqual([
|
expect(resolveHomeWinners(selection, previewDocs).map((doc) => doc.id)).toEqual([
|
||||||
third.id,
|
third.id,
|
||||||
unpublished.id,
|
unpublished.id,
|
||||||
first.id,
|
first.id,
|
||||||
@@ -221,7 +221,7 @@ describe('public route hydration', () => {
|
|||||||
})
|
})
|
||||||
const republishedDocs = await findFixtureDocs('preistraeger', ids, false)
|
const republishedDocs = await findFixtureDocs('preistraeger', ids, false)
|
||||||
|
|
||||||
expect(resolveHomeWinners(selection, republishedDocs, []).map((doc) => doc.id)).toEqual([
|
expect(resolveHomeWinners(selection, republishedDocs).map((doc) => doc.id)).toEqual([
|
||||||
third.id,
|
third.id,
|
||||||
unpublished.id,
|
unpublished.id,
|
||||||
first.id,
|
first.id,
|
||||||
@@ -297,6 +297,43 @@ describe('public route hydration', () => {
|
|||||||
expect(previewDocs.map((doc) => doc.id)).toEqual([first.id, unpublished.id, third.id])
|
expect(previewDocs.map((doc) => doc.id)).toEqual([first.id, unpublished.id, third.id])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('leaves no event row after the final /presse event is unpublished', async () => {
|
||||||
|
const event = await payload.create({
|
||||||
|
collection: 'events',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
data: {
|
||||||
|
_status: 'published',
|
||||||
|
slug: `${fixtureKey}-event-final`,
|
||||||
|
title: `${fixtureKey} event final`,
|
||||||
|
},
|
||||||
|
draft: false,
|
||||||
|
})
|
||||||
|
cleanup.push(() =>
|
||||||
|
payload.delete({
|
||||||
|
collection: 'events',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
id: event.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect((await findFixtureDocs('events', [event.id], false)).map((doc) => doc.id)).toEqual([
|
||||||
|
event.id,
|
||||||
|
])
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
collection: 'events',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
id: event.id,
|
||||||
|
data: { _status: 'draft' },
|
||||||
|
draft: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await findFixtureDocs('events', [event.id], false)).toEqual([])
|
||||||
|
expect((await findFixtureDocs('events', [event.id], true)).map((doc) => doc.id)).toEqual([
|
||||||
|
event.id,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('omits an unpublished event previously rendered on /presse', async () => {
|
it('omits an unpublished event previously rendered on /presse', async () => {
|
||||||
const published = await payload.create({
|
const published = await payload.create({
|
||||||
collection: 'events',
|
collection: 'events',
|
||||||
@@ -341,6 +378,44 @@ describe('public route hydration', () => {
|
|||||||
expect(previewDocs.map((doc) => doc.id).sort()).toEqual(ids.sort())
|
expect(previewDocs.map((doc) => doc.id).sort()).toEqual(ids.sort())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('leaves no news card after the final /presse article is unpublished', async () => {
|
||||||
|
const post = await payload.create({
|
||||||
|
collection: 'posts',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
data: {
|
||||||
|
_status: 'published',
|
||||||
|
content: lexicalDocument,
|
||||||
|
slug: `${fixtureKey}-post-final`,
|
||||||
|
title: `${fixtureKey} post final`,
|
||||||
|
},
|
||||||
|
draft: false,
|
||||||
|
})
|
||||||
|
cleanup.push(() =>
|
||||||
|
payload.delete({
|
||||||
|
collection: 'posts',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
id: post.id,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
expect((await findFixtureDocs('posts', [post.id], false)).map((doc) => doc.id)).toEqual([
|
||||||
|
post.id,
|
||||||
|
])
|
||||||
|
|
||||||
|
await payload.update({
|
||||||
|
collection: 'posts',
|
||||||
|
context: { disableRevalidate: true },
|
||||||
|
id: post.id,
|
||||||
|
data: { _status: 'draft' },
|
||||||
|
draft: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
expect(await findFixtureDocs('posts', [post.id], false)).toEqual([])
|
||||||
|
expect((await findFixtureDocs('posts', [post.id], true)).map((doc) => doc.id)).toEqual([
|
||||||
|
post.id,
|
||||||
|
])
|
||||||
|
})
|
||||||
|
|
||||||
it('omits an unpublished press article previously rendered on /presse', async () => {
|
it('omits an unpublished press article previously rendered on /presse', async () => {
|
||||||
const published = await payload.create({
|
const published = await payload.create({
|
||||||
collection: 'posts',
|
collection: 'posts',
|
||||||
|
|||||||
Reference in New Issue
Block a user