mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 18:34:13 +00:00
feat: add priority collections and advanced content blocks
New Collections: - Events: Veranstaltungen mit Datum, Ort, Registrierung - Jobs: Stellenangebote mit Standort und Bewerbungsfrist - Locations: Standorte mit Adresse, Kontakt, Öffnungszeiten - Partners: Partner/Kunden mit Logo und Beschreibung - Downloads: Dateien mit Kategorisierung und Tracking New Blocks: - EventsBlock: Veranstaltungslisten mit Kalender-Ansicht - JobsBlock: Stellenanzeigen mit Filterfunktion - LocationsBlock: Standort-Karten und Listen - PricingBlock: Preistabellen mit Feature-Vergleich - TabsBlock: Tabbed Content mit verschiedenen Stilen - AccordionBlock: FAQ/Accordion mit Animationen - ComparisonBlock: Vergleichstabellen (Tabelle, Karten, Pro/Contra) - StatsBlock: Statistiken mit Counter-Animation - LogoGridBlock: Logo-Wolken und Partner-Galerien - MapBlock: Interaktive Karten mit Markern - DownloadsBlock: Download-Listen mit Kategorien All collections support multi-tenant isolation and localization. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
2b097eefb3
commit
9e5c741941
23 changed files with 23275 additions and 536 deletions
400
src/blocks/AccordionBlock.ts
Normal file
400
src/blocks/AccordionBlock.ts
Normal file
|
|
@ -0,0 +1,400 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Accordion Block
|
||||
*
|
||||
* Aufklappbare Inhaltsbereiche für FAQs, Dokumentation,
|
||||
* Produktdetails etc. Mehr Flexibilität als der FAQ-Block.
|
||||
*/
|
||||
export const AccordionBlock: Block = {
|
||||
slug: 'accordion',
|
||||
labels: {
|
||||
singular: 'Accordion',
|
||||
plural: 'Accordions',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
// Accordion Items
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Einträge',
|
||||
minRows: 1,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Keines', value: 'none' },
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Frage', value: 'question' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'Häkchen', value: 'check' },
|
||||
{ label: 'Warnung', value: 'warning' },
|
||||
{ label: 'Dokument', value: 'document' },
|
||||
{ label: 'Benutzer', value: 'user' },
|
||||
{ label: 'Einstellungen', value: 'settings' },
|
||||
{ label: 'Code', value: 'code' },
|
||||
{ label: 'Lampe', value: 'lightbulb' },
|
||||
{ label: 'Schloss', value: 'lock' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customIcon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Eigenes Icon',
|
||||
},
|
||||
// Content
|
||||
{
|
||||
name: 'contentType',
|
||||
type: 'select',
|
||||
defaultValue: 'richtext',
|
||||
label: 'Content-Typ',
|
||||
options: [
|
||||
{ label: 'Rich Text', value: 'richtext' },
|
||||
{ label: 'Bild & Text', value: 'image-text' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Tabelle', value: 'table' },
|
||||
{ label: 'Code', value: 'code' },
|
||||
],
|
||||
},
|
||||
// Rich Text
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
label: 'Inhalt',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'richtext',
|
||||
},
|
||||
},
|
||||
// Image & Text
|
||||
{
|
||||
name: 'imgTxt',
|
||||
type: 'group',
|
||||
label: 'Bild & Text',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'image-text',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'img',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'imgPos',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Bild-Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'richText',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// List
|
||||
{
|
||||
name: 'listItems',
|
||||
type: 'array',
|
||||
label: 'Listen-Einträge',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'list',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
defaultValue: 'check',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Häkchen', value: 'check' },
|
||||
{ label: 'Punkt', value: 'dot' },
|
||||
{ label: 'Pfeil', value: 'arrow' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'X', value: 'x' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Table
|
||||
{
|
||||
name: 'tableData',
|
||||
type: 'group',
|
||||
label: 'Tabelle',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'table',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'headers',
|
||||
type: 'array',
|
||||
label: 'Spaltenüberschriften',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'rows',
|
||||
type: 'array',
|
||||
label: 'Zeilen',
|
||||
fields: [
|
||||
{
|
||||
name: 'cells',
|
||||
type: 'array',
|
||||
label: 'Zellen',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Code
|
||||
{
|
||||
name: 'codeContent',
|
||||
type: 'group',
|
||||
label: 'Code',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'code',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'language',
|
||||
type: 'select',
|
||||
label: 'Sprache',
|
||||
options: [
|
||||
{ label: 'JavaScript', value: 'javascript' },
|
||||
{ label: 'TypeScript', value: 'typescript' },
|
||||
{ label: 'HTML', value: 'html' },
|
||||
{ label: 'CSS', value: 'css' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: 'Python', value: 'python' },
|
||||
{ label: 'Bash', value: 'bash' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'code',
|
||||
label: 'Code',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Status/Badge
|
||||
{
|
||||
name: 'badge',
|
||||
type: 'text',
|
||||
label: 'Badge',
|
||||
admin: {
|
||||
description: 'z.B. "Neu", "Wichtig", "Pro"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'badgeColor',
|
||||
type: 'select',
|
||||
label: 'Badge-Farbe',
|
||||
options: [
|
||||
{ label: 'Grau', value: 'gray' },
|
||||
{ label: 'Blau', value: 'blue' },
|
||||
{ label: 'Grün', value: 'green' },
|
||||
{ label: 'Gelb', value: 'yellow' },
|
||||
{ label: 'Rot', value: 'red' },
|
||||
{ label: 'Lila', value: 'purple' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.badge,
|
||||
},
|
||||
},
|
||||
// State
|
||||
{
|
||||
name: 'defaultOpen',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Standardmäßig geöffnet',
|
||||
},
|
||||
{
|
||||
name: 'disabled',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Deaktiviert',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verhalten
|
||||
{
|
||||
name: 'behavior',
|
||||
type: 'select',
|
||||
defaultValue: 'single',
|
||||
label: 'Verhalten',
|
||||
options: [
|
||||
{ label: 'Nur eines offen (klassisch)', value: 'single' },
|
||||
{ label: 'Mehrere gleichzeitig', value: 'multiple' },
|
||||
{ label: 'Mindestens eines offen', value: 'at-least-one' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'expandFirst',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Erstes Element standardmäßig öffnen',
|
||||
},
|
||||
{
|
||||
name: 'animated',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Animierter Übergang',
|
||||
},
|
||||
// Stil
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
label: 'Stil',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Bordered', value: 'bordered' },
|
||||
{ label: 'Separated', value: 'separated' },
|
||||
{ label: 'Flush', value: 'flush' },
|
||||
{ label: 'Rounded', value: 'rounded' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'iconPosition',
|
||||
type: 'select',
|
||||
defaultValue: 'right',
|
||||
label: 'Pfeil-Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'iconStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'chevron',
|
||||
label: 'Pfeil-Stil',
|
||||
options: [
|
||||
{ label: 'Chevron (>)', value: 'chevron' },
|
||||
{ label: 'Plus/Minus', value: 'plus-minus' },
|
||||
{ label: 'Pfeil', value: 'arrow' },
|
||||
{ label: 'Caret', value: 'caret' },
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'full',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Volle Breite', value: 'full' },
|
||||
{ label: 'Zentriert', value: 'centered' },
|
||||
{ label: 'Schmal', value: 'narrow' },
|
||||
{ label: 'Zweispaltig', value: 'two-column' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'titleSize',
|
||||
type: 'select',
|
||||
defaultValue: 'medium',
|
||||
label: 'Titel-Größe',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'small' },
|
||||
{ label: 'Mittel', value: 'medium' },
|
||||
{ label: 'Groß', value: 'large' },
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'headerBackground',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Header-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
],
|
||||
},
|
||||
// Schema.org
|
||||
{
|
||||
name: 'enableSchemaOrg',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Schema.org FAQ generieren',
|
||||
admin: {
|
||||
description: 'Wenn aktiviert, wird FAQ Structured Data generiert',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
598
src/blocks/ComparisonBlock.ts
Normal file
598
src/blocks/ComparisonBlock.ts
Normal file
|
|
@ -0,0 +1,598 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Comparison Block
|
||||
*
|
||||
* Vergleichstabellen für Produkte, Dienstleistungen, Pläne etc.
|
||||
* Unterstützt verschiedene Layouts und Darstellungsformen.
|
||||
*/
|
||||
export const ComparisonBlock: Block = {
|
||||
slug: 'comparison',
|
||||
labels: {
|
||||
singular: 'Vergleich',
|
||||
plural: 'Vergleiche',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
// Vergleichstyp
|
||||
{
|
||||
name: 'comparisonType',
|
||||
type: 'select',
|
||||
defaultValue: 'table',
|
||||
label: 'Vergleichs-Typ',
|
||||
options: [
|
||||
{ label: 'Tabelle', value: 'table' },
|
||||
{ label: 'Karten nebeneinander', value: 'cards' },
|
||||
{ label: 'Vorher/Nachher', value: 'before-after' },
|
||||
{ label: 'Pro/Contra', value: 'pros-cons' },
|
||||
{ label: 'Feature-Matrix', value: 'feature-matrix' },
|
||||
],
|
||||
},
|
||||
// Tabellen-Vergleich
|
||||
{
|
||||
name: 'tbl',
|
||||
type: 'group',
|
||||
label: 'Tabellen-Vergleich',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.comparisonType === 'table' ||
|
||||
siblingData?.comparisonType === 'feature-matrix',
|
||||
},
|
||||
fields: [
|
||||
// Spalten (Produkte/Optionen)
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'array',
|
||||
label: 'Spalten (Optionen)',
|
||||
minRows: 2,
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild/Logo',
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
label: 'Preis',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'isHighlighted',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorheben',
|
||||
},
|
||||
{
|
||||
name: 'highlightLabel',
|
||||
type: 'text',
|
||||
label: 'Highlight-Label',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isHighlighted,
|
||||
description: 'z.B. "Empfohlen", "Bestseller"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'ctaLink',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Zeilen (Features)
|
||||
{
|
||||
name: 'rows',
|
||||
type: 'array',
|
||||
label: 'Zeilen (Features)',
|
||||
fields: [
|
||||
{
|
||||
name: 'feature',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Feature',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Kategorie',
|
||||
admin: {
|
||||
description: 'Für Gruppierung',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tooltip',
|
||||
type: 'text',
|
||||
label: 'Tooltip',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'values',
|
||||
type: 'array',
|
||||
label: 'Werte',
|
||||
fields: [
|
||||
{
|
||||
name: 'columnIndex',
|
||||
type: 'number',
|
||||
label: 'Spalten-Index',
|
||||
admin: {
|
||||
description: '0 = erste Spalte',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'valueType',
|
||||
type: 'select',
|
||||
defaultValue: 'text',
|
||||
label: 'Wert-Typ',
|
||||
options: [
|
||||
{ label: 'Text', value: 'text' },
|
||||
{ label: 'Ja/Nein', value: 'boolean' },
|
||||
{ label: 'Teilweise', value: 'partial' },
|
||||
{ label: 'Nicht verfügbar', value: 'na' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'textValue',
|
||||
type: 'text',
|
||||
label: 'Text-Wert',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.valueType === 'text',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'booleanValue',
|
||||
type: 'checkbox',
|
||||
label: 'Ja/Nein',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.valueType === 'boolean',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'partialNote',
|
||||
type: 'text',
|
||||
label: 'Hinweis',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.valueType === 'partial',
|
||||
description: 'z.B. "Eingeschränkt", "Mit Aufpreis"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Karten-Vergleich
|
||||
{
|
||||
name: 'crd',
|
||||
type: 'group',
|
||||
label: 'Karten-Vergleich',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.comparisonType === 'cards',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Karten',
|
||||
minRows: 2,
|
||||
maxRows: 4,
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'features',
|
||||
type: 'array',
|
||||
label: 'Features',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'included',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'text',
|
||||
label: 'Preis',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'ctaLink',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
},
|
||||
{
|
||||
name: 'isHighlighted',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorheben',
|
||||
},
|
||||
{
|
||||
name: 'accentColor',
|
||||
type: 'select',
|
||||
label: 'Akzentfarbe',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Sekundär', value: 'secondary' },
|
||||
{ label: 'Grün', value: 'green' },
|
||||
{ label: 'Blau', value: 'blue' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Vorher/Nachher
|
||||
{
|
||||
name: 'beforeAfter',
|
||||
type: 'group',
|
||||
label: 'Vorher/Nachher',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.comparisonType === 'before-after',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'beforeLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Vorher',
|
||||
label: 'Vorher-Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'afterLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Nachher',
|
||||
label: 'Nachher-Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Vergleiche',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'beforeImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Vorher-Bild',
|
||||
},
|
||||
{
|
||||
name: 'afterImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Nachher-Bild',
|
||||
},
|
||||
{
|
||||
name: 'beforeText',
|
||||
type: 'textarea',
|
||||
label: 'Vorher-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'afterText',
|
||||
type: 'textarea',
|
||||
label: 'Nachher-Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'displayStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'slider',
|
||||
label: 'Darstellung',
|
||||
options: [
|
||||
{ label: 'Slider (Schieberegler)', value: 'slider' },
|
||||
{ label: 'Nebeneinander', value: 'side-by-side' },
|
||||
{ label: 'Übereinander', value: 'stacked' },
|
||||
{ label: 'Flip', value: 'flip' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Pro/Contra
|
||||
{
|
||||
name: 'prosCons',
|
||||
type: 'group',
|
||||
label: 'Pro/Contra',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.comparisonType === 'pros-cons',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'prosLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Vorteile',
|
||||
label: 'Vorteile-Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'consLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Nachteile',
|
||||
label: 'Nachteile-Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'items',
|
||||
type: 'array',
|
||||
label: 'Einträge',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'pros',
|
||||
type: 'array',
|
||||
label: 'Vorteile',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cons',
|
||||
type: 'array',
|
||||
label: 'Nachteile',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'verdict',
|
||||
type: 'textarea',
|
||||
label: 'Fazit',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'rating',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
max: 5,
|
||||
label: 'Bewertung (0-5)',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout-Optionen
|
||||
{
|
||||
name: 'stickyHeader',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Sticky Header',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.comparisonType === 'table' ||
|
||||
siblingData?.comparisonType === 'feature-matrix',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showCategories',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorien anzeigen',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.comparisonType === 'table' ||
|
||||
siblingData?.comparisonType === 'feature-matrix',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'collapsibleCategories',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kategorien einklappbar',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.comparisonType === 'table' ||
|
||||
siblingData?.comparisonType === 'feature-matrix',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showRowDividers',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zeilen-Trennlinien',
|
||||
},
|
||||
{
|
||||
name: 'highlightDifferences',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Unterschiede hervorheben',
|
||||
},
|
||||
// Mobile
|
||||
{
|
||||
name: 'mobileView',
|
||||
type: 'select',
|
||||
defaultValue: 'scroll',
|
||||
label: 'Mobile-Ansicht',
|
||||
options: [
|
||||
{ label: 'Horizontal scrollen', value: 'scroll' },
|
||||
{ label: 'Karten gestapelt', value: 'stacked' },
|
||||
{ label: 'Accordion', value: 'accordion' },
|
||||
{ label: 'Dropdown-Auswahl', value: 'dropdown' },
|
||||
],
|
||||
},
|
||||
// Symbole
|
||||
{
|
||||
name: 'symbols',
|
||||
type: 'group',
|
||||
label: 'Symbole',
|
||||
fields: [
|
||||
{
|
||||
name: 'checkSymbol',
|
||||
type: 'text',
|
||||
defaultValue: '✓',
|
||||
label: 'Häkchen-Symbol',
|
||||
},
|
||||
{
|
||||
name: 'crossSymbol',
|
||||
type: 'text',
|
||||
defaultValue: '✗',
|
||||
label: 'X-Symbol',
|
||||
},
|
||||
{
|
||||
name: 'partialSymbol',
|
||||
type: 'text',
|
||||
defaultValue: '○',
|
||||
label: 'Teilweise-Symbol',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tableStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'bordered',
|
||||
label: 'Tabellen-Stil',
|
||||
options: [
|
||||
{ label: 'Umrandet', value: 'bordered' },
|
||||
{ label: 'Striped', value: 'striped' },
|
||||
{ label: 'Minimalistisch', value: 'minimal' },
|
||||
{ label: 'Karten', value: 'cards' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.comparisonType === 'table' ||
|
||||
siblingData?.comparisonType === 'feature-matrix',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'checkColor',
|
||||
type: 'select',
|
||||
defaultValue: 'green',
|
||||
label: 'Häkchen-Farbe',
|
||||
options: [
|
||||
{ label: 'Grün', value: 'green' },
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Blau', value: 'blue' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'crossColor',
|
||||
type: 'select',
|
||||
defaultValue: 'red',
|
||||
label: 'X-Farbe',
|
||||
options: [
|
||||
{ label: 'Rot', value: 'red' },
|
||||
{ label: 'Grau', value: 'gray' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
457
src/blocks/DownloadsBlock.ts
Normal file
457
src/blocks/DownloadsBlock.ts
Normal file
|
|
@ -0,0 +1,457 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* DownloadsBlock
|
||||
*
|
||||
* Zeigt Downloads mit Filter- und Suchfunktion.
|
||||
* Unterstützt verschiedene Layouts und Kategoriefilter.
|
||||
*/
|
||||
export const DownloadsBlock: Block = {
|
||||
slug: 'downloads-block',
|
||||
labels: {
|
||||
singular: 'Downloads',
|
||||
plural: 'Downloads',
|
||||
},
|
||||
imageURL: '/assets/blocks/downloads.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Downloads',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'introduction',
|
||||
type: 'richText',
|
||||
label: 'Einleitungstext',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
label: 'Downloads',
|
||||
options: [
|
||||
{ label: 'Alle aktiven', value: 'all' },
|
||||
{ label: 'Nach Kategorie', value: 'category' },
|
||||
{ label: 'Nach Tags', value: 'tags' },
|
||||
{ label: 'Nur hervorgehobene', value: 'featured' },
|
||||
{ label: 'Zugehörig zu Service', value: 'service' },
|
||||
{ label: 'Zugehörig zu Produkt', value: 'product' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
label: 'Kategorie',
|
||||
options: [
|
||||
{ label: 'Broschüre', value: 'brochure' },
|
||||
{ label: 'Flyer', value: 'flyer' },
|
||||
{ label: 'Katalog', value: 'catalog' },
|
||||
{ label: 'Preisliste', value: 'pricelist' },
|
||||
{ label: 'Formular', value: 'form' },
|
||||
{ label: 'Anleitung', value: 'manual' },
|
||||
{ label: 'Datenblatt', value: 'datasheet' },
|
||||
{ label: 'Zertifikat', value: 'certificate' },
|
||||
{ label: 'Pressematerial', value: 'press' },
|
||||
{ label: 'Whitepaper', value: 'whitepaper' },
|
||||
{ label: 'Präsentation', value: 'presentation' },
|
||||
{ label: 'Vertrag/AGB', value: 'legal' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'category',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'filterTags',
|
||||
type: 'text',
|
||||
label: 'Tags (kommagetrennt)',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'tags',
|
||||
description: 'z.B. "produktinfo, 2024, neu"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relatedService',
|
||||
type: 'relationship',
|
||||
relationTo: 'services',
|
||||
label: 'Zugehöriger Service',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'service',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'relatedProduct',
|
||||
type: 'relationship',
|
||||
relationTo: 'products',
|
||||
label: 'Zugehöriges Produkt',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'product',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedDownloads',
|
||||
type: 'relationship',
|
||||
relationTo: 'downloads',
|
||||
hasMany: true,
|
||||
label: 'Downloads auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 20,
|
||||
min: 1,
|
||||
max: 100,
|
||||
label: 'Maximale Anzahl',
|
||||
},
|
||||
// Filter-UI
|
||||
{
|
||||
name: 'filters',
|
||||
type: 'group',
|
||||
label: 'Filter-Optionen',
|
||||
fields: [
|
||||
{
|
||||
name: 'showSearch',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Suchfeld',
|
||||
},
|
||||
{
|
||||
name: 'showCategoryFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorie-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showFileTypeFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Dateityp-Filter',
|
||||
},
|
||||
{
|
||||
name: 'filterLayout',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Filter-Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal', value: 'horizontal' },
|
||||
{ label: 'Sidebar', value: 'sidebar' },
|
||||
{ label: 'Dropdown', value: 'dropdown' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'list',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Karten', value: 'cards' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
{ label: 'Tabelle', value: 'table' },
|
||||
{ label: 'Akkordeon (nach Kategorie)', value: 'accordion' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '1',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '1 Spalte', value: '1' },
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'cards' || siblingData?.layout === 'list',
|
||||
},
|
||||
},
|
||||
// Anzuzeigende Informationen
|
||||
{
|
||||
name: 'show',
|
||||
type: 'group',
|
||||
label: 'Anzeigen',
|
||||
fields: [
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Vorschaubild/Icon',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschreibung',
|
||||
},
|
||||
{
|
||||
name: 'fileSize',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Dateigröße',
|
||||
},
|
||||
{
|
||||
name: 'fileType',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Dateityp',
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorie',
|
||||
},
|
||||
{
|
||||
name: 'downloadCount',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Download-Zähler',
|
||||
},
|
||||
{
|
||||
name: 'version',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Version',
|
||||
},
|
||||
{
|
||||
name: 'lastUpdated',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Letztes Update',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Download-Verhalten
|
||||
{
|
||||
name: 'downloadBehavior',
|
||||
type: 'group',
|
||||
label: 'Download-Verhalten',
|
||||
fields: [
|
||||
{
|
||||
name: 'directDownload',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Direkter Download',
|
||||
admin: {
|
||||
description: 'Datei direkt herunterladen vs. Vorschau öffnen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'trackDownloads',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Downloads zählen',
|
||||
},
|
||||
{
|
||||
name: 'openInNewTab',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'In neuem Tab öffnen',
|
||||
},
|
||||
],
|
||||
},
|
||||
// File-Type Icons
|
||||
{
|
||||
name: 'fileIcons',
|
||||
type: 'group',
|
||||
label: 'Datei-Icons',
|
||||
fields: [
|
||||
{
|
||||
name: 'showFileTypeIcon',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Dateityp-Icon anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'iconStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'colored',
|
||||
label: 'Icon-Stil',
|
||||
options: [
|
||||
{ label: 'Farbig', value: 'colored' },
|
||||
{ label: 'Monochrom', value: 'mono' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showFileTypeIcon,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Sortierung
|
||||
{
|
||||
name: 'sortBy',
|
||||
type: 'select',
|
||||
defaultValue: 'order',
|
||||
label: 'Sortierung',
|
||||
options: [
|
||||
{ label: 'Manuelle Reihenfolge', value: 'order' },
|
||||
{ label: 'Titel (A-Z)', value: 'title_asc' },
|
||||
{ label: 'Titel (Z-A)', value: 'title_desc' },
|
||||
{ label: 'Neueste zuerst', value: 'date_desc' },
|
||||
{ label: 'Älteste zuerst', value: 'date_asc' },
|
||||
{ label: 'Beliebteste', value: 'downloads_desc' },
|
||||
{ label: 'Dateigröße', value: 'size' },
|
||||
],
|
||||
},
|
||||
// Gruppierung
|
||||
{
|
||||
name: 'groupBy',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Gruppieren nach',
|
||||
options: [
|
||||
{ label: 'Keine Gruppierung', value: 'none' },
|
||||
{ label: 'Kategorie', value: 'category' },
|
||||
{ label: 'Dateityp', value: 'fileType' },
|
||||
],
|
||||
},
|
||||
// Pagination
|
||||
{
|
||||
name: 'pagination',
|
||||
type: 'group',
|
||||
label: 'Pagination',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'Alle anzeigen', value: 'none' },
|
||||
{ label: '"Mehr laden" Button', value: 'button' },
|
||||
{ label: 'Pagination', value: 'pagination' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'perPage',
|
||||
type: 'number',
|
||||
defaultValue: 10,
|
||||
label: 'Pro Seite',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.type === 'pagination' || siblingData?.type === 'button',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'bordered',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Einfach', value: 'simple' },
|
||||
{ label: 'Mit Rahmen', value: 'bordered' },
|
||||
{ label: 'Mit Schatten', value: 'shadow' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'hoverEffect',
|
||||
type: 'select',
|
||||
defaultValue: 'highlight',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hervorheben', value: 'highlight' },
|
||||
{ label: 'Anheben', value: 'lift' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '16',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (8px)', value: '8' },
|
||||
{ label: 'Normal (16px)', value: '16' },
|
||||
{ label: 'Groß (24px)', value: '24' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Empty State
|
||||
{
|
||||
name: 'emptyState',
|
||||
type: 'group',
|
||||
label: 'Keine Downloads',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Keine Downloads verfügbar',
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'textarea',
|
||||
label: 'Nachricht',
|
||||
localized: true,
|
||||
defaultValue: 'Aktuell sind keine Downloads in dieser Kategorie verfügbar.',
|
||||
},
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'showAllDownloadsLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: '"Alle Downloads" Link',
|
||||
},
|
||||
{
|
||||
name: 'allDownloadsText',
|
||||
type: 'text',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Alle Downloads ansehen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllDownloadsLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allDownloadsUrl',
|
||||
type: 'text',
|
||||
label: 'Link-URL',
|
||||
defaultValue: '/downloads',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllDownloadsLink,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
320
src/blocks/EventsBlock.ts
Normal file
320
src/blocks/EventsBlock.ts
Normal file
|
|
@ -0,0 +1,320 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Events Block
|
||||
*
|
||||
* Zeigt Events aus der Events Collection oder inline-definierte Events.
|
||||
* Unterstützt verschiedene Layouts (Kalender, Liste, Karten, Timeline)
|
||||
* und Filter nach Event-Typ, Zeitraum oder Kategorie.
|
||||
*/
|
||||
export const EventsBlock: Block = {
|
||||
slug: 'events',
|
||||
labels: {
|
||||
singular: 'Events',
|
||||
plural: 'Events',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Quelle
|
||||
{
|
||||
name: 'sourceMode',
|
||||
type: 'select',
|
||||
defaultValue: 'collection',
|
||||
label: 'Quelle',
|
||||
options: [
|
||||
{ label: 'Aus Events Collection', value: 'collection' },
|
||||
{ label: 'Handverlesene Auswahl', value: 'selected' },
|
||||
],
|
||||
},
|
||||
// Collection Filter
|
||||
{
|
||||
name: 'filterMode',
|
||||
type: 'select',
|
||||
defaultValue: 'upcoming',
|
||||
label: 'Filter',
|
||||
options: [
|
||||
{ label: 'Kommende Events', value: 'upcoming' },
|
||||
{ label: 'Vergangene Events', value: 'past' },
|
||||
{ label: 'Alle Events', value: 'all' },
|
||||
{ label: 'Nur hervorgehobene', value: 'featured' },
|
||||
{ label: 'Nach Typ', value: 'type' },
|
||||
{ label: 'Nach Kategorie', value: 'category' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.sourceMode === 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'eventType',
|
||||
type: 'select',
|
||||
label: 'Event-Typ filtern',
|
||||
options: [
|
||||
{ label: 'Veranstaltung', value: 'event' },
|
||||
{ label: 'Workshop', value: 'workshop' },
|
||||
{ label: 'Seminar', value: 'seminar' },
|
||||
{ label: 'Webinar', value: 'webinar' },
|
||||
{ label: 'Konferenz', value: 'conference' },
|
||||
{ label: 'Messe', value: 'tradeshow' },
|
||||
{ label: 'Networking', value: 'networking' },
|
||||
{ label: 'Kurs', value: 'course' },
|
||||
{ label: 'Vortrag', value: 'talk' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.sourceMode === 'collection' && siblingData?.filterMode === 'type',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Kategorie filtern',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.sourceMode === 'collection' && siblingData?.filterMode === 'category',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedEvents',
|
||||
type: 'relationship',
|
||||
relationTo: 'events' as 'users',
|
||||
hasMany: true,
|
||||
label: 'Events auswählen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.sourceMode === 'selected',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 6,
|
||||
min: 1,
|
||||
max: 50,
|
||||
label: 'Maximale Anzahl',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.sourceMode === 'collection',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'cards',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Karten (Grid)', value: 'cards' },
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Kompakte Liste', value: 'compact' },
|
||||
{ label: 'Timeline', value: 'timeline' },
|
||||
{ label: 'Kalender', value: 'calendar' },
|
||||
{ label: 'Agenda', value: 'agenda' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
// Anzeigeoptionen
|
||||
{
|
||||
name: 'showImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Bild anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showExcerpt',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschreibung anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDate',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Datum anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showTime',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Uhrzeit anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showLocation',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Ort anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showPrice',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Preis anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showEventType',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Event-Typ Badge anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showRegistrationButton',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Anmelde-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'registrationButtonText',
|
||||
type: 'text',
|
||||
defaultValue: 'Jetzt anmelden',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showRegistrationButton,
|
||||
},
|
||||
},
|
||||
// Kalender-Optionen
|
||||
{
|
||||
name: 'calendarOptions',
|
||||
type: 'group',
|
||||
label: 'Kalender-Optionen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'calendar',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'defaultView',
|
||||
type: 'select',
|
||||
defaultValue: 'month',
|
||||
label: 'Standard-Ansicht',
|
||||
options: [
|
||||
{ label: 'Monat', value: 'month' },
|
||||
{ label: 'Woche', value: 'week' },
|
||||
{ label: 'Tag', value: 'day' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showNavigation',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Navigation anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showViewToggle',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Ansichts-Wechsel anzeigen',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Gruppierung
|
||||
{
|
||||
name: 'groupBy',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Gruppieren nach',
|
||||
options: [
|
||||
{ label: 'Keine Gruppierung', value: 'none' },
|
||||
{ label: 'Monat', value: 'month' },
|
||||
{ label: 'Event-Typ', value: 'type' },
|
||||
{ label: 'Kategorie', value: 'category' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.layout === 'list' || siblingData?.layout === 'compact',
|
||||
},
|
||||
},
|
||||
// Sortierung
|
||||
{
|
||||
name: 'sortOrder',
|
||||
type: 'select',
|
||||
defaultValue: 'asc',
|
||||
label: 'Sortierung',
|
||||
options: [
|
||||
{ label: 'Aufsteigend (älteste zuerst)', value: 'asc' },
|
||||
{ label: 'Absteigend (neueste zuerst)', value: 'desc' },
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'showAllLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Alle Events" Link anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'allEventsLink',
|
||||
type: 'text',
|
||||
defaultValue: '/events',
|
||||
label: 'Link zu allen Events',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showAllLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allEventsText',
|
||||
type: 'text',
|
||||
defaultValue: 'Alle Veranstaltungen',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showAllLink,
|
||||
},
|
||||
},
|
||||
// Leerer Zustand
|
||||
{
|
||||
name: 'emptyMessage',
|
||||
type: 'text',
|
||||
defaultValue: 'Aktuell sind keine Veranstaltungen geplant.',
|
||||
label: 'Nachricht wenn keine Events',
|
||||
localized: true,
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Akzentfarbe', value: 'accent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'elevated',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Erhöht (Schatten)', value: 'elevated' },
|
||||
{ label: 'Umrandet', value: 'bordered' },
|
||||
{ label: 'Flach', value: 'flat' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
436
src/blocks/JobsBlock.ts
Normal file
436
src/blocks/JobsBlock.ts
Normal file
|
|
@ -0,0 +1,436 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* JobsBlock
|
||||
*
|
||||
* Zeigt Stellenanzeigen mit Filter- und Suchfunktion.
|
||||
* Unterstützt verschiedene Layouts und Filteroptionen.
|
||||
*/
|
||||
export const JobsBlock: Block = {
|
||||
slug: 'jobs-block',
|
||||
labels: {
|
||||
singular: 'Stellenanzeigen',
|
||||
plural: 'Stellenanzeigen',
|
||||
},
|
||||
imageURL: '/assets/blocks/jobs.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Offene Stellen',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'introduction',
|
||||
type: 'richText',
|
||||
label: 'Einleitungstext',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
label: 'Stellen',
|
||||
options: [
|
||||
{ label: 'Alle veröffentlichten', value: 'all' },
|
||||
{ label: 'Nach Kategorie', value: 'category' },
|
||||
{ label: 'Nach Abteilung', value: 'department' },
|
||||
{ label: 'Nach Standort', value: 'location' },
|
||||
{ label: 'Nur hervorgehobene', value: 'featured' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
label: 'Kategorie',
|
||||
options: [
|
||||
{ label: 'Pflege & Betreuung', value: 'care' },
|
||||
{ label: 'Medizin', value: 'medical' },
|
||||
{ label: 'Verwaltung', value: 'admin' },
|
||||
{ label: 'IT & Technik', value: 'it' },
|
||||
{ label: 'Marketing', value: 'marketing' },
|
||||
{ label: 'Vertrieb', value: 'sales' },
|
||||
{ label: 'Finanzen', value: 'finance' },
|
||||
{ label: 'Personal', value: 'hr' },
|
||||
{ label: 'Produktion', value: 'production' },
|
||||
{ label: 'Logistik', value: 'logistics' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'category',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'department',
|
||||
type: 'text',
|
||||
label: 'Abteilung',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'department',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'locationFilter',
|
||||
type: 'relationship',
|
||||
relationTo: 'locations',
|
||||
label: 'Standort',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'location',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedJobs',
|
||||
type: 'relationship',
|
||||
relationTo: 'jobs',
|
||||
hasMany: true,
|
||||
label: 'Stellen auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 10,
|
||||
min: 1,
|
||||
max: 50,
|
||||
label: 'Maximale Anzahl',
|
||||
},
|
||||
// Filter-UI
|
||||
{
|
||||
name: 'filters',
|
||||
type: 'group',
|
||||
label: 'Filter-Optionen',
|
||||
fields: [
|
||||
{
|
||||
name: 'showSearch',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Suchfeld',
|
||||
},
|
||||
{
|
||||
name: 'showCategoryFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kategorie-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showTypeFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschäftigungsart-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showLocationFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Standort-Filter',
|
||||
},
|
||||
{
|
||||
name: 'showWorkModelFilter',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Arbeitsmodell-Filter',
|
||||
},
|
||||
{
|
||||
name: 'filterLayout',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Filter-Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal', value: 'horizontal' },
|
||||
{ label: 'Sidebar', value: 'sidebar' },
|
||||
{ label: 'Dropdown', value: 'dropdown' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'list',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Liste', value: 'list' },
|
||||
{ label: 'Karten', value: 'cards' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
{ label: 'Akkordeon', value: 'accordion' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '1',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '1 Spalte', value: '1' },
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'cards' || siblingData?.layout === 'list',
|
||||
},
|
||||
},
|
||||
// Anzuzeigende Informationen
|
||||
{
|
||||
name: 'show',
|
||||
type: 'group',
|
||||
label: 'Anzeigen',
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'department',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Abteilung',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beschäftigungsart',
|
||||
},
|
||||
{
|
||||
name: 'location',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Standort',
|
||||
},
|
||||
{
|
||||
name: 'workModel',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Arbeitsmodell',
|
||||
},
|
||||
{
|
||||
name: 'salary',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Gehalt',
|
||||
},
|
||||
{
|
||||
name: 'summary',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kurzbeschreibung',
|
||||
},
|
||||
{
|
||||
name: 'deadline',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Bewerbungsfrist',
|
||||
},
|
||||
{
|
||||
name: 'publishDate',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Veröffentlichungsdatum',
|
||||
},
|
||||
{
|
||||
name: 'badges',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Badges (Neu, Dringend)',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Badges
|
||||
{
|
||||
name: 'badges',
|
||||
type: 'group',
|
||||
label: 'Badge-Einstellungen',
|
||||
fields: [
|
||||
{
|
||||
name: 'newDays',
|
||||
type: 'number',
|
||||
defaultValue: 7,
|
||||
label: '"Neu" für X Tage',
|
||||
admin: {
|
||||
description: 'Wie viele Tage nach Veröffentlichung "Neu" anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showUrgent',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Dringend" Badge',
|
||||
},
|
||||
{
|
||||
name: 'showFeatured',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Top" Badge für hervorgehobene',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Pagination
|
||||
{
|
||||
name: 'pagination',
|
||||
type: 'group',
|
||||
label: 'Pagination',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'button',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: '"Mehr laden" Button', value: 'button' },
|
||||
{ label: 'Pagination', value: 'pagination' },
|
||||
{ label: 'Infinite Scroll', value: 'infinite' },
|
||||
{ label: 'Alle anzeigen', value: 'none' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'perPage',
|
||||
type: 'number',
|
||||
defaultValue: 10,
|
||||
label: 'Pro Seite',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.type === 'pagination' || siblingData?.type === 'button',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'bordered',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Einfach', value: 'simple' },
|
||||
{ label: 'Mit Rahmen', value: 'bordered' },
|
||||
{ label: 'Mit Schatten', value: 'shadow' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'hoverEffect',
|
||||
type: 'select',
|
||||
defaultValue: 'lift',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Anheben', value: 'lift' },
|
||||
{ label: 'Hervorheben', value: 'highlight' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '16',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (8px)', value: '8' },
|
||||
{ label: 'Normal (16px)', value: '16' },
|
||||
{ label: 'Groß (24px)', value: '24' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Empty State
|
||||
{
|
||||
name: 'emptyState',
|
||||
type: 'group',
|
||||
label: 'Keine Stellen',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Aktuell keine offenen Stellen',
|
||||
},
|
||||
{
|
||||
name: 'message',
|
||||
type: 'textarea',
|
||||
label: 'Nachricht',
|
||||
localized: true,
|
||||
defaultValue: 'Schauen Sie später wieder vorbei oder senden Sie uns eine Initiativbewerbung.',
|
||||
},
|
||||
{
|
||||
name: 'showInitiativeLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Initiativbewerbungs-Link',
|
||||
},
|
||||
{
|
||||
name: 'initiativeText',
|
||||
type: 'text',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Initiativbewerbung senden',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showInitiativeLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'initiativeUrl',
|
||||
type: 'text',
|
||||
label: 'Link-URL',
|
||||
defaultValue: '/karriere/initiativbewerbung',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showInitiativeLink,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'showAllJobsLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: '"Alle Stellen" Link',
|
||||
},
|
||||
{
|
||||
name: 'allJobsText',
|
||||
type: 'text',
|
||||
label: 'Link-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Alle offenen Stellen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllJobsLink,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allJobsUrl',
|
||||
type: 'text',
|
||||
label: 'Link-URL',
|
||||
defaultValue: '/karriere',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showAllJobsLink,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
386
src/blocks/LocationsBlock.ts
Normal file
386
src/blocks/LocationsBlock.ts
Normal file
|
|
@ -0,0 +1,386 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* LocationsBlock
|
||||
*
|
||||
* Zeigt Firmenstandorte mit Karte, Adresse, Öffnungszeiten und Kontaktdaten.
|
||||
* Unterstützt verschiedene Layouts und Kartenanbieter.
|
||||
*/
|
||||
export const LocationsBlock: Block = {
|
||||
slug: 'locations-block',
|
||||
labels: {
|
||||
singular: 'Standorte',
|
||||
plural: 'Standorte',
|
||||
},
|
||||
imageURL: '/assets/blocks/locations.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
defaultValue: 'Unsere Standorte',
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
label: 'Standorte',
|
||||
options: [
|
||||
{ label: 'Alle aktiven Standorte', value: 'all' },
|
||||
{ label: 'Nur Hauptstandort', value: 'main' },
|
||||
{ label: 'Nach Typ', value: 'type' },
|
||||
{ label: 'Manuell auswählen', value: 'manual' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'locationType',
|
||||
type: 'select',
|
||||
label: 'Standort-Typ',
|
||||
options: [
|
||||
{ label: 'Hauptsitz', value: 'headquarters' },
|
||||
{ label: 'Büro', value: 'office' },
|
||||
{ label: 'Filiale', value: 'branch' },
|
||||
{ label: 'Lager', value: 'warehouse' },
|
||||
{ label: 'Showroom', value: 'showroom' },
|
||||
{ label: 'Studio', value: 'studio' },
|
||||
{ label: 'Praxis', value: 'practice' },
|
||||
{ label: 'Werkstatt', value: 'workshop' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'type',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedLocations',
|
||||
type: 'relationship',
|
||||
relationTo: 'locations',
|
||||
hasMany: true,
|
||||
label: 'Standorte auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'map-list',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Karte + Liste', value: 'map-list' },
|
||||
{ label: 'Nur Karte', value: 'map-only' },
|
||||
{ label: 'Nur Liste', value: 'list-only' },
|
||||
{ label: 'Karten-Grid', value: 'cards' },
|
||||
{ label: 'Akkordeon', value: 'accordion' },
|
||||
{ label: 'Tabs', value: 'tabs' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'mapPosition',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Karten-Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
{ label: 'Oben', value: 'top' },
|
||||
{ label: 'Unten', value: 'bottom' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'map-list',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '2',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '1 Spalte', value: '1' },
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
// Karten-Einstellungen
|
||||
{
|
||||
name: 'map',
|
||||
type: 'group',
|
||||
label: 'Karten-Einstellungen',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'map-list' || siblingData?.layout === 'map-only',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'provider',
|
||||
type: 'select',
|
||||
defaultValue: 'osm',
|
||||
label: 'Kartenanbieter',
|
||||
options: [
|
||||
{ label: 'OpenStreetMap', value: 'osm' },
|
||||
{ label: 'Google Maps', value: 'google' },
|
||||
{ label: 'Mapbox', value: 'mapbox' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'style',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
label: 'Kartenstil',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Satellite', value: 'satellite' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'height',
|
||||
type: 'select',
|
||||
defaultValue: '400',
|
||||
label: 'Höhe',
|
||||
options: [
|
||||
{ label: 'Klein (300px)', value: '300' },
|
||||
{ label: 'Mittel (400px)', value: '400' },
|
||||
{ label: 'Groß (500px)', value: '500' },
|
||||
{ label: 'Sehr groß (600px)', value: '600' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'defaultZoom',
|
||||
type: 'number',
|
||||
defaultValue: 12,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Standard-Zoom',
|
||||
},
|
||||
{
|
||||
name: 'fitBounds',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Alle Marker zeigen',
|
||||
admin: {
|
||||
description: 'Karte automatisch anpassen, um alle Standorte zu zeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'markerStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'pin',
|
||||
label: 'Marker-Stil',
|
||||
options: [
|
||||
{ label: 'Pin', value: 'pin' },
|
||||
{ label: 'Punkt', value: 'dot' },
|
||||
{ label: 'Icon', value: 'icon' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'markerColor',
|
||||
type: 'text',
|
||||
label: 'Marker-Farbe',
|
||||
defaultValue: '#e11d48',
|
||||
admin: {
|
||||
description: 'Hex-Farbe (z.B. #e11d48)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'clustering',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Marker clustern',
|
||||
admin: {
|
||||
description: 'Nahe Marker zu Gruppen zusammenfassen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'controls',
|
||||
type: 'group',
|
||||
label: 'Steuerung',
|
||||
fields: [
|
||||
{
|
||||
name: 'zoom',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zoom-Buttons',
|
||||
},
|
||||
{
|
||||
name: 'fullscreen',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Vollbild-Button',
|
||||
},
|
||||
{
|
||||
name: 'scrollZoom',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Scroll-Zoom',
|
||||
},
|
||||
{
|
||||
name: 'dragging',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Verschiebbar',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Anzuzeigende Informationen
|
||||
{
|
||||
name: 'show',
|
||||
type: 'group',
|
||||
label: 'Anzeigen',
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Standort-Typ',
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Adresse',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Telefon',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'hours',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Öffnungszeiten',
|
||||
},
|
||||
{
|
||||
name: 'directions',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Anfahrt',
|
||||
},
|
||||
{
|
||||
name: 'services',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Services',
|
||||
},
|
||||
{
|
||||
name: 'team',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Team-Mitglieder',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Aktionen
|
||||
{
|
||||
name: 'actions',
|
||||
type: 'group',
|
||||
label: 'Aktionen',
|
||||
fields: [
|
||||
{
|
||||
name: 'showDirectionsLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Route planen" Link',
|
||||
},
|
||||
{
|
||||
name: 'showCallButton',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Anruf-Button (mobil)',
|
||||
},
|
||||
{
|
||||
name: 'showEmailButton',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'E-Mail-Button',
|
||||
},
|
||||
{
|
||||
name: 'showDetailLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Link zur Detail-Seite',
|
||||
},
|
||||
{
|
||||
name: 'detailBasePath',
|
||||
type: 'text',
|
||||
label: 'Detail-Basis-Pfad',
|
||||
defaultValue: '/standorte',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showDetailLink,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'bordered',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Einfach', value: 'simple' },
|
||||
{ label: 'Mit Rahmen', value: 'bordered' },
|
||||
{ label: 'Mit Schatten', value: 'shadow' },
|
||||
{ label: 'Gefüllt', value: 'filled' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '24',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (16px)', value: '16' },
|
||||
{ label: 'Normal (24px)', value: '24' },
|
||||
{ label: 'Groß (32px)', value: '32' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
422
src/blocks/LogoGridBlock.ts
Normal file
422
src/blocks/LogoGridBlock.ts
Normal file
|
|
@ -0,0 +1,422 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* LogoGridBlock
|
||||
*
|
||||
* Zeigt Partner-, Kunden- oder Zertifizierungs-Logos in verschiedenen Layouts.
|
||||
* Kann als Slider, Grid oder Marquee dargestellt werden.
|
||||
*/
|
||||
export const LogoGridBlock: Block = {
|
||||
slug: 'logo-grid-block',
|
||||
labels: {
|
||||
singular: 'Logo-Grid',
|
||||
plural: 'Logo-Grids',
|
||||
},
|
||||
imageURL: '/assets/blocks/logo-grid.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Unsere Partner", "Bekannt aus", "Zertifizierungen"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'collection',
|
||||
label: 'Datenquelle',
|
||||
options: [
|
||||
{ label: 'Aus Partner-Collection', value: 'collection' },
|
||||
{ label: 'Manuell hochladen', value: 'manual' },
|
||||
],
|
||||
},
|
||||
// Collection-basiert
|
||||
{
|
||||
name: 'partnerType',
|
||||
type: 'select',
|
||||
label: 'Partner-Typ',
|
||||
hasMany: true,
|
||||
options: [
|
||||
{ label: 'Alle', value: 'all' },
|
||||
{ label: 'Partner', value: 'partner' },
|
||||
{ label: 'Kunden', value: 'client' },
|
||||
{ label: 'Referenzen', value: 'reference' },
|
||||
{ label: 'Sponsoren', value: 'sponsor' },
|
||||
{ label: 'Zertifizierungen', value: 'certification' },
|
||||
{ label: 'Mitgliedschaften', value: 'membership' },
|
||||
{ label: 'Auszeichnungen', value: 'award' },
|
||||
{ label: 'Lieferanten', value: 'supplier' },
|
||||
{ label: 'Technologien', value: 'technology' },
|
||||
],
|
||||
defaultValue: ['all'],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedPartners',
|
||||
type: 'relationship',
|
||||
relationTo: 'partners',
|
||||
hasMany: true,
|
||||
label: 'Partner auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'collection',
|
||||
description: 'Optional: Bestimmte Partner auswählen (sonst alle vom gewählten Typ)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featuredOnly',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Nur hervorgehobene',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 12,
|
||||
min: 1,
|
||||
max: 50,
|
||||
label: 'Maximale Anzahl',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'collection',
|
||||
},
|
||||
},
|
||||
// Manuell
|
||||
{
|
||||
name: 'logos',
|
||||
type: 'array',
|
||||
label: 'Logos',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
label: 'Logo',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Name',
|
||||
admin: {
|
||||
description: 'Für Alt-Text und Tooltip',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Slider', value: 'slider' },
|
||||
{ label: 'Marquee (endlos)', value: 'marquee' },
|
||||
{ label: 'Inline (flexibel)', value: 'inline' },
|
||||
{ label: 'Masonry', value: 'masonry' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '4',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
{ label: '5 Spalten', value: '5' },
|
||||
{ label: '6 Spalten', value: '6' },
|
||||
{ label: '8 Spalten', value: '8' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'grid',
|
||||
},
|
||||
},
|
||||
// Slider-Optionen
|
||||
{
|
||||
name: 'slider',
|
||||
type: 'group',
|
||||
label: 'Slider-Einstellungen',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'slider' || siblingData?.layout === 'marquee',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'perView',
|
||||
type: 'select',
|
||||
defaultValue: '4',
|
||||
label: 'Sichtbare Logos',
|
||||
options: [
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
{ label: '5', value: '5' },
|
||||
{ label: '6', value: '6' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'autoplay',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Autoplay',
|
||||
},
|
||||
{
|
||||
name: 'speed',
|
||||
type: 'select',
|
||||
defaultValue: '3000',
|
||||
label: 'Geschwindigkeit',
|
||||
options: [
|
||||
{ label: 'Langsam', value: '5000' },
|
||||
{ label: 'Normal', value: '3000' },
|
||||
{ label: 'Schnell', value: '2000' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.autoplay,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'pauseOnHover',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Bei Hover pausieren',
|
||||
},
|
||||
{
|
||||
name: 'showArrows',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Pfeile anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDots',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Punkte anzeigen',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Logo-Darstellung
|
||||
{
|
||||
name: 'logoStyle',
|
||||
type: 'group',
|
||||
label: 'Logo-Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'size',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Größe',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'maxHeight',
|
||||
type: 'select',
|
||||
defaultValue: '60',
|
||||
label: 'Max. Höhe',
|
||||
options: [
|
||||
{ label: '40px', value: '40' },
|
||||
{ label: '50px', value: '50' },
|
||||
{ label: '60px', value: '60' },
|
||||
{ label: '80px', value: '80' },
|
||||
{ label: '100px', value: '100' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'grayscale',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Graustufen',
|
||||
admin: {
|
||||
description: 'Logos in Graustufen anzeigen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'colorOnHover',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Farbe bei Hover',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.grayscale,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'opacity',
|
||||
type: 'select',
|
||||
defaultValue: '70',
|
||||
label: 'Deckkraft',
|
||||
options: [
|
||||
{ label: '50%', value: '50' },
|
||||
{ label: '70%', value: '70' },
|
||||
{ label: '100%', value: '100' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'hoverEffect',
|
||||
type: 'select',
|
||||
defaultValue: 'scale',
|
||||
label: 'Hover-Effekt',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Vergrößern', value: 'scale' },
|
||||
{ label: 'Aufhellen', value: 'brighten' },
|
||||
{ label: 'Schatten', value: 'shadow' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verhalten
|
||||
{
|
||||
name: 'behavior',
|
||||
type: 'group',
|
||||
label: 'Verhalten',
|
||||
fields: [
|
||||
{
|
||||
name: 'linkToWebsite',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Zur Partner-Website verlinken',
|
||||
},
|
||||
{
|
||||
name: 'openInNewTab',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'In neuem Tab öffnen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.linkToWebsite,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showTooltip',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Tooltip mit Namen',
|
||||
},
|
||||
{
|
||||
name: 'showName',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Namen unter Logo anzeigen',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'logoBg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Logo-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'logoPadding',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Logo-Padding',
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '32',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (16px)', value: '16' },
|
||||
{ label: 'Normal (24px)', value: '24' },
|
||||
{ label: 'Groß (32px)', value: '32' },
|
||||
{ label: 'Sehr groß (48px)', value: '48' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Mitte', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'divider',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Trennlinien zwischen Logos',
|
||||
},
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'showCTA',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
label: 'CTA-Text',
|
||||
localized: true,
|
||||
defaultValue: 'Alle Partner ansehen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCTA,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'ctaLink',
|
||||
type: 'text',
|
||||
label: 'CTA-Link',
|
||||
defaultValue: '/partner',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showCTA,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
588
src/blocks/MapBlock.ts
Normal file
588
src/blocks/MapBlock.ts
Normal file
|
|
@ -0,0 +1,588 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* MapBlock
|
||||
*
|
||||
* Zeigt eine interaktive Karte mit konfigurierbaren Markern.
|
||||
* Unterstützt verschiedene Map-Provider und Marker-Typen.
|
||||
*/
|
||||
export const MapBlock: Block = {
|
||||
slug: 'map-block',
|
||||
labels: {
|
||||
singular: 'Karte',
|
||||
plural: 'Karten',
|
||||
},
|
||||
imageURL: '/assets/blocks/map.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Datenquelle
|
||||
{
|
||||
name: 'source',
|
||||
type: 'select',
|
||||
defaultValue: 'locations',
|
||||
label: 'Marker-Quelle',
|
||||
options: [
|
||||
{ label: 'Aus Locations Collection', value: 'locations' },
|
||||
{ label: 'Manuell definieren', value: 'manual' },
|
||||
{ label: 'Einzelne Adresse', value: 'single' },
|
||||
],
|
||||
},
|
||||
// Locations-basiert
|
||||
{
|
||||
name: 'locationType',
|
||||
type: 'select',
|
||||
label: 'Standort-Typ',
|
||||
hasMany: true,
|
||||
options: [
|
||||
{ label: 'Alle', value: 'all' },
|
||||
{ label: 'Hauptsitz', value: 'headquarters' },
|
||||
{ label: 'Filiale', value: 'branch' },
|
||||
{ label: 'Niederlassung', value: 'office' },
|
||||
{ label: 'Lager/Logistik', value: 'warehouse' },
|
||||
{ label: 'Produktion', value: 'production' },
|
||||
{ label: 'Showroom', value: 'showroom' },
|
||||
{ label: 'Service-Center', value: 'service' },
|
||||
{ label: 'Partner', value: 'partner' },
|
||||
],
|
||||
defaultValue: ['all'],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'locations',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedLocations',
|
||||
type: 'relationship',
|
||||
relationTo: 'locations',
|
||||
hasMany: true,
|
||||
label: 'Standorte auswählen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'locations',
|
||||
description: 'Optional: Bestimmte Standorte auswählen (sonst alle vom gewählten Typ)',
|
||||
},
|
||||
},
|
||||
// Manuelle Marker
|
||||
{
|
||||
name: 'markers',
|
||||
type: 'array',
|
||||
label: 'Marker',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'manual',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'address',
|
||||
type: 'textarea',
|
||||
label: 'Adresse',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'lat',
|
||||
type: 'number',
|
||||
required: true,
|
||||
label: 'Breitengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lng',
|
||||
type: 'number',
|
||||
required: true,
|
||||
label: 'Längengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'markerType',
|
||||
type: 'select',
|
||||
label: 'Marker-Typ',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Hauptstandort', value: 'main' },
|
||||
{ label: 'Highlight', value: 'highlight' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customIcon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Custom Icon',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.markerType === 'custom',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'popupContent',
|
||||
type: 'richText',
|
||||
label: 'Popup-Inhalt',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'link',
|
||||
type: 'text',
|
||||
label: 'Link',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Einzelne Adresse
|
||||
{
|
||||
name: 'singleAddress',
|
||||
type: 'group',
|
||||
label: 'Adresse',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.source === 'single',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Name/Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'street',
|
||||
type: 'text',
|
||||
label: 'Straße',
|
||||
},
|
||||
{
|
||||
name: 'zip',
|
||||
type: 'text',
|
||||
label: 'PLZ',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
label: 'Stadt',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
type: 'text',
|
||||
defaultValue: 'Deutschland',
|
||||
label: 'Land',
|
||||
},
|
||||
{
|
||||
name: 'lat',
|
||||
type: 'number',
|
||||
label: 'Breitengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
description: 'Optional - wird sonst per Geocoding ermittelt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lng',
|
||||
type: 'number',
|
||||
label: 'Längengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Map Provider
|
||||
{
|
||||
name: 'provider',
|
||||
type: 'select',
|
||||
defaultValue: 'osm',
|
||||
label: 'Karten-Anbieter',
|
||||
options: [
|
||||
{ label: 'OpenStreetMap', value: 'osm' },
|
||||
{ label: 'Google Maps', value: 'google' },
|
||||
{ label: 'Mapbox', value: 'mapbox' },
|
||||
{ label: 'Leaflet (Custom Tiles)', value: 'leaflet' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'mapStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'default',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Satellite', value: 'satellite' },
|
||||
{ label: 'Hybrid', value: 'hybrid' },
|
||||
{ label: 'Terrain', value: 'terrain' },
|
||||
],
|
||||
},
|
||||
// Karten-Einstellungen
|
||||
{
|
||||
name: 'mapSettings',
|
||||
type: 'group',
|
||||
label: 'Karten-Einstellungen',
|
||||
fields: [
|
||||
{
|
||||
name: 'height',
|
||||
type: 'select',
|
||||
defaultValue: '400',
|
||||
label: 'Höhe',
|
||||
options: [
|
||||
{ label: 'Klein (300px)', value: '300' },
|
||||
{ label: 'Normal (400px)', value: '400' },
|
||||
{ label: 'Groß (500px)', value: '500' },
|
||||
{ label: 'Sehr groß (600px)', value: '600' },
|
||||
{ label: 'Vollbild-Höhe', value: 'fullscreen' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'zoom',
|
||||
type: 'number',
|
||||
defaultValue: 12,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Zoom-Level',
|
||||
admin: {
|
||||
description: '1 = Welt, 20 = Gebäude-Details',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'autoFit',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zoom automatisch anpassen',
|
||||
admin: {
|
||||
description: 'Zoom so anpassen, dass alle Marker sichtbar sind',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'centerLat',
|
||||
type: 'number',
|
||||
label: 'Mittelpunkt Breitengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
description: 'Optional - wird automatisch berechnet wenn leer',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'centerLng',
|
||||
type: 'number',
|
||||
label: 'Mittelpunkt Längengrad',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Interaktion
|
||||
{
|
||||
name: 'interaction',
|
||||
type: 'group',
|
||||
label: 'Interaktion',
|
||||
fields: [
|
||||
{
|
||||
name: 'scrollZoom',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Zoom mit Mausrad',
|
||||
},
|
||||
{
|
||||
name: 'dragging',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Verschieben erlauben',
|
||||
},
|
||||
{
|
||||
name: 'zoomControl',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zoom-Buttons',
|
||||
},
|
||||
{
|
||||
name: 'fullscreenControl',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Vollbild-Button',
|
||||
},
|
||||
{
|
||||
name: 'locateControl',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Standort-Button',
|
||||
admin: {
|
||||
description: 'User-Standort ermitteln',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Marker-Darstellung
|
||||
{
|
||||
name: 'markerStyle',
|
||||
type: 'group',
|
||||
label: 'Marker-Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'pin',
|
||||
label: 'Marker-Typ',
|
||||
options: [
|
||||
{ label: 'Pin', value: 'pin' },
|
||||
{ label: 'Punkt', value: 'dot' },
|
||||
{ label: 'Icon', value: 'icon' },
|
||||
{ label: 'Custom', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
defaultValue: '#3B82F6',
|
||||
label: 'Marker-Farbe',
|
||||
admin: {
|
||||
description: 'HEX-Farbcode, z.B. #3B82F6',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'size',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Größe',
|
||||
options: [
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Mittel', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'clustering',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Marker clustern',
|
||||
admin: {
|
||||
description: 'Nahe beieinander liegende Marker gruppieren',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'clusterRadius',
|
||||
type: 'number',
|
||||
defaultValue: 50,
|
||||
label: 'Cluster-Radius',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.clustering,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Popup-Einstellungen
|
||||
{
|
||||
name: 'popup',
|
||||
type: 'group',
|
||||
label: 'Popup-Einstellungen',
|
||||
fields: [
|
||||
{
|
||||
name: 'show',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Popups anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'trigger',
|
||||
type: 'select',
|
||||
defaultValue: 'click',
|
||||
label: 'Öffnen bei',
|
||||
options: [
|
||||
{ label: 'Klick', value: 'click' },
|
||||
{ label: 'Hover', value: 'hover' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showAddress',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Adresse anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showDirectionsLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Routenplaner-Link',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showPhone',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Telefon anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showOpeningHours',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Öffnungszeiten anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showDetailLink',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Detail-Link anzeigen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Sidebar/Liste
|
||||
{
|
||||
name: 'sidebar',
|
||||
type: 'group',
|
||||
label: 'Standort-Liste',
|
||||
fields: [
|
||||
{
|
||||
name: 'show',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Liste neben Karte',
|
||||
},
|
||||
{
|
||||
name: 'position',
|
||||
type: 'select',
|
||||
defaultValue: 'right',
|
||||
label: 'Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
{ label: 'Unten', value: 'bottom' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'width',
|
||||
type: 'select',
|
||||
defaultValue: '300',
|
||||
label: 'Breite',
|
||||
options: [
|
||||
{ label: 'Schmal (250px)', value: '250' },
|
||||
{ label: 'Normal (300px)', value: '300' },
|
||||
{ label: 'Breit (400px)', value: '400' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.show && siblingData?.position !== 'bottom',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'searchable',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Suchfeld in Liste',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'clickToCenter',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Klick zentriert Karte',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'rounded',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Abgerundete Ecken',
|
||||
},
|
||||
{
|
||||
name: 'shadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Schatten',
|
||||
},
|
||||
{
|
||||
name: 'border',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Rahmen',
|
||||
},
|
||||
{
|
||||
name: 'padding',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Außenabstand',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Normal', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Fallback
|
||||
{
|
||||
name: 'fallback',
|
||||
type: 'group',
|
||||
label: 'Fallback',
|
||||
fields: [
|
||||
{
|
||||
name: 'showStaticImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Statisches Bild als Fallback',
|
||||
},
|
||||
{
|
||||
name: 'staticImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Fallback-Bild',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showStaticImage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'noJsMessage',
|
||||
type: 'text',
|
||||
label: 'Nachricht ohne JavaScript',
|
||||
localized: true,
|
||||
defaultValue: 'Bitte aktivieren Sie JavaScript, um die Karte anzuzeigen.',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
509
src/blocks/PricingBlock.ts
Normal file
509
src/blocks/PricingBlock.ts
Normal file
|
|
@ -0,0 +1,509 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Pricing Block
|
||||
*
|
||||
* Preistabellen für Produkte, Dienstleistungen oder Abonnements.
|
||||
* Unterstützt verschiedene Layouts, Vergleichstabellen und
|
||||
* Highlight-Features für empfohlene Pläne.
|
||||
*/
|
||||
export const PricingBlock: Block = {
|
||||
slug: 'pricing',
|
||||
labels: {
|
||||
singular: 'Preistabelle',
|
||||
plural: 'Preistabellen',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
// Preismodell
|
||||
{
|
||||
name: 'pricingType',
|
||||
type: 'select',
|
||||
defaultValue: 'one-time',
|
||||
label: 'Preismodell',
|
||||
options: [
|
||||
{ label: 'Einmalig', value: 'one-time' },
|
||||
{ label: 'Monatlich', value: 'monthly' },
|
||||
{ label: 'Jährlich', value: 'yearly' },
|
||||
{ label: 'Monatlich/Jährlich Toggle', value: 'toggle' },
|
||||
{ label: 'Individuell', value: 'custom' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'currency',
|
||||
type: 'text',
|
||||
defaultValue: '€',
|
||||
label: 'Währung',
|
||||
},
|
||||
{
|
||||
name: 'showCurrencyBefore',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Währung vor dem Preis',
|
||||
},
|
||||
// Toggle-Optionen für Monatlich/Jährlich
|
||||
{
|
||||
name: 'toggleOptions',
|
||||
type: 'group',
|
||||
label: 'Toggle-Optionen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.pricingType === 'toggle',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'monthlyLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Monatlich',
|
||||
label: 'Monatlich Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'yearlyLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Jährlich',
|
||||
label: 'Jährlich Label',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'yearlyDiscount',
|
||||
type: 'text',
|
||||
label: 'Rabatt-Badge',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "2 Monate gratis"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'defaultToYearly',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Standard: Jährlich',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Preispläne
|
||||
{
|
||||
name: 'plans',
|
||||
type: 'array',
|
||||
label: 'Preispläne',
|
||||
minRows: 1,
|
||||
maxRows: 6,
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Plan-Name',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Basic", "Pro", "Enterprise"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Für Einsteiger"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
// Preis
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
label: 'Preis',
|
||||
admin: {
|
||||
width: '25%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceMonthly',
|
||||
type: 'number',
|
||||
label: 'Preis Monatlich',
|
||||
admin: {
|
||||
width: '25%',
|
||||
condition: (data, siblingData, { blockData }) =>
|
||||
blockData?.pricingType === 'toggle',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceYearly',
|
||||
type: 'number',
|
||||
label: 'Preis Jährlich',
|
||||
admin: {
|
||||
width: '25%',
|
||||
condition: (data, siblingData, { blockData }) =>
|
||||
blockData?.pricingType === 'toggle',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceSuffix',
|
||||
type: 'text',
|
||||
label: 'Preis-Suffix',
|
||||
localized: true,
|
||||
admin: {
|
||||
width: '25%',
|
||||
description: 'z.B. "/Monat", "/Nutzer"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'originalPrice',
|
||||
type: 'number',
|
||||
label: 'Originalpreis (durchgestrichen)',
|
||||
admin: {
|
||||
description: 'Für Rabatt-Darstellung',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'priceNote',
|
||||
type: 'text',
|
||||
label: 'Preis-Hinweis',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "zzgl. MwSt.", "Bei jährlicher Zahlung"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customPriceText',
|
||||
type: 'text',
|
||||
label: 'Individueller Preis-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Auf Anfrage", "Individuell"',
|
||||
condition: (data, siblingData, { blockData }) =>
|
||||
blockData?.pricingType === 'custom',
|
||||
},
|
||||
},
|
||||
// Features
|
||||
{
|
||||
name: 'features',
|
||||
type: 'array',
|
||||
label: 'Features',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Feature',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'included',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Enthalten',
|
||||
},
|
||||
{
|
||||
name: 'highlight',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorheben',
|
||||
},
|
||||
{
|
||||
name: 'tooltip',
|
||||
type: 'text',
|
||||
label: 'Tooltip',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// CTA
|
||||
{
|
||||
name: 'ctaText',
|
||||
type: 'text',
|
||||
defaultValue: 'Jetzt starten',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'ctaLink',
|
||||
type: 'text',
|
||||
label: 'Button-Link',
|
||||
},
|
||||
{
|
||||
name: 'ctaStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'primary',
|
||||
label: 'Button-Stil',
|
||||
options: [
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Sekundär', value: 'secondary' },
|
||||
{ label: 'Outline', value: 'outline' },
|
||||
],
|
||||
},
|
||||
// Hervorhebung
|
||||
{
|
||||
name: 'isPopular',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Als "Beliebt" markieren',
|
||||
},
|
||||
{
|
||||
name: 'popularLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Beliebt',
|
||||
label: 'Beliebt-Label',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isPopular,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isRecommended',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Als "Empfohlen" markieren',
|
||||
},
|
||||
{
|
||||
name: 'recommendedLabel',
|
||||
type: 'text',
|
||||
defaultValue: 'Empfohlen',
|
||||
label: 'Empfohlen-Label',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isRecommended,
|
||||
},
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'accentColor',
|
||||
type: 'select',
|
||||
label: 'Akzentfarbe',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Primär', value: 'primary' },
|
||||
{ label: 'Sekundär', value: 'secondary' },
|
||||
{ label: 'Grün', value: 'green' },
|
||||
{ label: 'Blau', value: 'blue' },
|
||||
{ label: 'Lila', value: 'purple' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Icon/Logo',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Vergleichstabelle
|
||||
{
|
||||
name: 'showComparison',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Vergleichstabelle anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'comparisonFeatures',
|
||||
type: 'array',
|
||||
label: 'Vergleichs-Features',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showComparison,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Kategorie',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'feature',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Feature',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'tooltip',
|
||||
type: 'text',
|
||||
label: 'Tooltip',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'values',
|
||||
type: 'array',
|
||||
label: 'Werte pro Plan',
|
||||
fields: [
|
||||
{
|
||||
name: 'planIndex',
|
||||
type: 'number',
|
||||
label: 'Plan-Index (0-basiert)',
|
||||
},
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
label: 'Wert',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: '✓, ✗, oder Text wie "10 GB", "Unbegrenzt"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'cards',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Karten', value: 'cards' },
|
||||
{ label: 'Tabelle', value: 'table' },
|
||||
{ label: 'Kompakt', value: 'compact' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Zentriert', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'highlightPopular',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Beliebten Plan hervorheben',
|
||||
},
|
||||
// Geld-zurück-Garantie etc.
|
||||
{
|
||||
name: 'guarantee',
|
||||
type: 'group',
|
||||
label: 'Garantie/Trust-Elemente',
|
||||
fields: [
|
||||
{
|
||||
name: 'show',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Garantie anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'text',
|
||||
defaultValue: '30 Tage Geld-zurück-Garantie',
|
||||
label: 'Garantie-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Schild', value: 'shield' },
|
||||
{ label: 'Häkchen', value: 'check' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'Schloss', value: 'lock' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// FAQ
|
||||
{
|
||||
name: 'showFAQ',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'FAQ anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'faqTitle',
|
||||
type: 'text',
|
||||
defaultValue: 'Häufig gestellte Fragen',
|
||||
label: 'FAQ-Überschrift',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showFAQ,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'faqItems',
|
||||
type: 'array',
|
||||
label: 'FAQ-Einträge',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showFAQ,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'question',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Frage',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'answer',
|
||||
type: 'richText',
|
||||
required: true,
|
||||
label: 'Antwort',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Gradient', value: 'gradient' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cardStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'elevated',
|
||||
label: 'Karten-Stil',
|
||||
options: [
|
||||
{ label: 'Erhöht (Schatten)', value: 'elevated' },
|
||||
{ label: 'Umrandet', value: 'bordered' },
|
||||
{ label: 'Flach', value: 'flat' },
|
||||
{ label: 'Glass', value: 'glass' },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
372
src/blocks/StatsBlock.ts
Normal file
372
src/blocks/StatsBlock.ts
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* StatsBlock
|
||||
*
|
||||
* Zeigt Kennzahlen und Statistiken in verschiedenen Layouts.
|
||||
* Unterstützt Animationen und verschiedene Darstellungsformen.
|
||||
*/
|
||||
export const StatsBlock: Block = {
|
||||
slug: 'stats-block',
|
||||
labels: {
|
||||
singular: 'Statistiken',
|
||||
plural: 'Statistiken',
|
||||
},
|
||||
imageURL: '/assets/blocks/stats.png',
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'textarea',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Statistiken
|
||||
{
|
||||
name: 'stats',
|
||||
type: 'array',
|
||||
required: true,
|
||||
minRows: 1,
|
||||
maxRows: 8,
|
||||
label: 'Statistiken',
|
||||
fields: [
|
||||
{
|
||||
name: 'value',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Wert',
|
||||
admin: {
|
||||
description: 'z.B. "500+", "99%", "24/7", "10.000"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'numericValue',
|
||||
type: 'number',
|
||||
label: 'Numerischer Wert',
|
||||
admin: {
|
||||
description: 'Für Zähl-Animation (z.B. 500 für "500+")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'prefix',
|
||||
type: 'text',
|
||||
label: 'Präfix',
|
||||
admin: {
|
||||
description: 'z.B. "€", "+"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'suffix',
|
||||
type: 'text',
|
||||
label: 'Suffix',
|
||||
admin: {
|
||||
description: 'z.B. "+", "%", "k", "Mio."',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Zufriedene Kunden", "Erfolgsquote"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Zusatztext',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Optionaler erklärender Text',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Keins', value: 'none' },
|
||||
{ label: 'Nutzer', value: 'users' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'Herz', value: 'heart' },
|
||||
{ label: 'Check', value: 'check' },
|
||||
{ label: 'Trophäe', value: 'trophy' },
|
||||
{ label: 'Chart', value: 'chart' },
|
||||
{ label: 'Uhr', value: 'clock' },
|
||||
{ label: 'Kalender', value: 'calendar' },
|
||||
{ label: 'Globus', value: 'globe' },
|
||||
{ label: 'Gebäude', value: 'building' },
|
||||
{ label: 'Dokument', value: 'document' },
|
||||
{ label: 'Ziel', value: 'target' },
|
||||
{ label: 'Rakete', value: 'rocket' },
|
||||
{ label: 'Handshake', value: 'handshake' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'select',
|
||||
label: 'Akzentfarbe',
|
||||
options: [
|
||||
{ label: 'Standard', value: 'default' },
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Grün', value: 'green' },
|
||||
{ label: 'Blau', value: 'blue' },
|
||||
{ label: 'Orange', value: 'orange' },
|
||||
{ label: 'Rot', value: 'red' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'row',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Horizontal', value: 'row' },
|
||||
{ label: 'Grid', value: 'grid' },
|
||||
{ label: 'Karten', value: 'cards' },
|
||||
{ label: 'Vertikal', value: 'column' },
|
||||
{ label: 'Inline (kompakt)', value: 'inline' },
|
||||
{ label: 'Feature (groß)', value: 'feature' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'columns',
|
||||
type: 'select',
|
||||
defaultValue: '4',
|
||||
label: 'Spalten',
|
||||
options: [
|
||||
{ label: '2 Spalten', value: '2' },
|
||||
{ label: '3 Spalten', value: '3' },
|
||||
{ label: '4 Spalten', value: '4' },
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.layout === 'grid' || siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'alignment',
|
||||
type: 'select',
|
||||
defaultValue: 'center',
|
||||
label: 'Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Mitte', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
],
|
||||
},
|
||||
// Animation
|
||||
{
|
||||
name: 'animation',
|
||||
type: 'group',
|
||||
label: 'Animation',
|
||||
fields: [
|
||||
{
|
||||
name: 'countUp',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zähl-Animation',
|
||||
admin: {
|
||||
description: 'Zahlen hochzählen (benötigt numericValue)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'duration',
|
||||
type: 'select',
|
||||
defaultValue: '2000',
|
||||
label: 'Dauer',
|
||||
options: [
|
||||
{ label: 'Schnell (1s)', value: '1000' },
|
||||
{ label: 'Normal (2s)', value: '2000' },
|
||||
{ label: 'Langsam (3s)', value: '3000' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.countUp,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'trigger',
|
||||
type: 'select',
|
||||
defaultValue: 'viewport',
|
||||
label: 'Trigger',
|
||||
options: [
|
||||
{ label: 'Beim Scrollen sichtbar', value: 'viewport' },
|
||||
{ label: 'Sofort', value: 'immediate' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.countUp,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'stagger',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Versetzt animieren',
|
||||
admin: {
|
||||
description: 'Statistiken nacheinander einblenden',
|
||||
condition: (_, siblingData) => siblingData?.countUp,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'style',
|
||||
type: 'group',
|
||||
label: 'Darstellung',
|
||||
fields: [
|
||||
{
|
||||
name: 'bg',
|
||||
type: 'select',
|
||||
defaultValue: 'none',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
{ label: 'Gradient', value: 'gradient' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'bgImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Hintergrundbild',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.bg === 'none',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bgOverlay',
|
||||
type: 'select',
|
||||
defaultValue: 'dark',
|
||||
label: 'Overlay',
|
||||
options: [
|
||||
{ label: 'Keins', value: 'none' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
{ label: 'Primary', value: 'primary' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.bgImage,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'textColor',
|
||||
type: 'select',
|
||||
defaultValue: 'auto',
|
||||
label: 'Textfarbe',
|
||||
options: [
|
||||
{ label: 'Auto', value: 'auto' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Hell', value: 'light' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'valueSize',
|
||||
type: 'select',
|
||||
defaultValue: 'xl',
|
||||
label: 'Zahlen-Größe',
|
||||
options: [
|
||||
{ label: 'Normal', value: 'base' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
{ label: 'Sehr groß', value: 'xl' },
|
||||
{ label: 'Riesig', value: '2xl' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'valueWeight',
|
||||
type: 'select',
|
||||
defaultValue: 'bold',
|
||||
label: 'Zahlen-Gewicht',
|
||||
options: [
|
||||
{ label: 'Normal', value: 'normal' },
|
||||
{ label: 'Medium', value: 'medium' },
|
||||
{ label: 'Bold', value: 'bold' },
|
||||
{ label: 'Extra Bold', value: 'extrabold' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showIcon',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Icons anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'iconPosition',
|
||||
type: 'select',
|
||||
defaultValue: 'top',
|
||||
label: 'Icon-Position',
|
||||
options: [
|
||||
{ label: 'Oben', value: 'top' },
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'In Zahl', value: 'inline' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.showIcon,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'dividers',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Trennlinien',
|
||||
},
|
||||
{
|
||||
name: 'cardBorder',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Karten-Rahmen',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'cardShadow',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Karten-Schatten',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.layout === 'cards',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'gap',
|
||||
type: 'select',
|
||||
defaultValue: '32',
|
||||
label: 'Abstand',
|
||||
options: [
|
||||
{ label: 'Klein (16px)', value: '16' },
|
||||
{ label: 'Normal (24px)', value: '24' },
|
||||
{ label: 'Groß (32px)', value: '32' },
|
||||
{ label: 'Sehr groß (48px)', value: '48' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'padding',
|
||||
type: 'select',
|
||||
defaultValue: 'md',
|
||||
label: 'Innenabstand',
|
||||
options: [
|
||||
{ label: 'Keiner', value: 'none' },
|
||||
{ label: 'Klein', value: 'sm' },
|
||||
{ label: 'Normal', value: 'md' },
|
||||
{ label: 'Groß', value: 'lg' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
381
src/blocks/TabsBlock.ts
Normal file
381
src/blocks/TabsBlock.ts
Normal file
|
|
@ -0,0 +1,381 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Tabs Block
|
||||
*
|
||||
* Tabbed Content für organisierte Informationen.
|
||||
* Unterstützt verschiedene Tab-Stile, Icons und
|
||||
* kann andere Blocks als Content enthalten.
|
||||
*/
|
||||
export const TabsBlock: Block = {
|
||||
slug: 'tabs',
|
||||
labels: {
|
||||
singular: 'Tabs',
|
||||
plural: 'Tabs',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
// Tabs
|
||||
{
|
||||
name: 'tabs',
|
||||
type: 'array',
|
||||
label: 'Tabs',
|
||||
minRows: 2,
|
||||
maxRows: 10,
|
||||
fields: [
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Tab-Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Keines', value: 'none' },
|
||||
{ label: 'Info', value: 'info' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'Herz', value: 'heart' },
|
||||
{ label: 'Häkchen', value: 'check' },
|
||||
{ label: 'Einstellungen', value: 'settings' },
|
||||
{ label: 'Benutzer', value: 'user' },
|
||||
{ label: 'Dokument', value: 'document' },
|
||||
{ label: 'Bild', value: 'image' },
|
||||
{ label: 'Video', value: 'video' },
|
||||
{ label: 'Code', value: 'code' },
|
||||
{ label: 'Chart', value: 'chart' },
|
||||
{ label: 'Kalender', value: 'calendar' },
|
||||
{ label: 'Standort', value: 'location' },
|
||||
{ label: 'E-Mail', value: 'email' },
|
||||
{ label: 'Telefon', value: 'phone' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'customIcon',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Eigenes Icon',
|
||||
admin: {
|
||||
description: 'Überschreibt das ausgewählte Icon',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'badge',
|
||||
type: 'text',
|
||||
label: 'Badge',
|
||||
admin: {
|
||||
description: 'z.B. "Neu", "3", "Beta"',
|
||||
},
|
||||
},
|
||||
// Content
|
||||
{
|
||||
name: 'contentType',
|
||||
type: 'select',
|
||||
defaultValue: 'richtext',
|
||||
label: 'Content-Typ',
|
||||
options: [
|
||||
{ label: 'Rich Text', value: 'richtext' },
|
||||
{ label: 'Bild & Text', value: 'image-text' },
|
||||
{ label: 'Feature-Liste', value: 'features' },
|
||||
{ label: 'Code', value: 'code' },
|
||||
{ label: 'Embed', value: 'embed' },
|
||||
],
|
||||
},
|
||||
// Rich Text Content
|
||||
{
|
||||
name: 'content',
|
||||
type: 'richText',
|
||||
label: 'Inhalt',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'richtext',
|
||||
},
|
||||
},
|
||||
// Image & Text Content
|
||||
{
|
||||
name: 'imgTxt',
|
||||
type: 'group',
|
||||
label: 'Bild & Text',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'image-text',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'img',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
},
|
||||
{
|
||||
name: 'imgPos',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Bild-Position',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
{ label: 'Oben', value: 'top' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'text',
|
||||
type: 'richText',
|
||||
label: 'Text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Features List
|
||||
{
|
||||
name: 'features',
|
||||
type: 'array',
|
||||
label: 'Features',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'features',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Häkchen', value: 'check' },
|
||||
{ label: 'Stern', value: 'star' },
|
||||
{ label: 'Pfeil', value: 'arrow' },
|
||||
{ label: 'Punkt', value: 'dot' },
|
||||
{ label: 'Nummer', value: 'number' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Code Content
|
||||
{
|
||||
name: 'code',
|
||||
type: 'group',
|
||||
label: 'Code',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'code',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'language',
|
||||
type: 'select',
|
||||
label: 'Sprache',
|
||||
options: [
|
||||
{ label: 'JavaScript', value: 'javascript' },
|
||||
{ label: 'TypeScript', value: 'typescript' },
|
||||
{ label: 'HTML', value: 'html' },
|
||||
{ label: 'CSS', value: 'css' },
|
||||
{ label: 'JSON', value: 'json' },
|
||||
{ label: 'Python', value: 'python' },
|
||||
{ label: 'Bash', value: 'bash' },
|
||||
{ label: 'SQL', value: 'sql' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'code',
|
||||
type: 'code',
|
||||
label: 'Code',
|
||||
},
|
||||
{
|
||||
name: 'showLineNumbers',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zeilennummern anzeigen',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Embed Content
|
||||
{
|
||||
name: 'embed',
|
||||
type: 'group',
|
||||
label: 'Embed',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.contentType === 'embed',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'YouTube', value: 'youtube' },
|
||||
{ label: 'Vimeo', value: 'vimeo' },
|
||||
{ label: 'iFrame', value: 'iframe' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'url',
|
||||
type: 'text',
|
||||
label: 'URL',
|
||||
},
|
||||
{
|
||||
name: 'aspectRatio',
|
||||
type: 'select',
|
||||
defaultValue: '16:9',
|
||||
label: 'Seitenverhältnis',
|
||||
options: [
|
||||
{ label: '16:9', value: '16:9' },
|
||||
{ label: '4:3', value: '4:3' },
|
||||
{ label: '1:1', value: '1:1' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Tab Style
|
||||
{
|
||||
name: 'tabStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'underline',
|
||||
label: 'Tab-Stil',
|
||||
options: [
|
||||
{ label: 'Unterstrichen', value: 'underline' },
|
||||
{ label: 'Boxed', value: 'boxed' },
|
||||
{ label: 'Pills', value: 'pills' },
|
||||
{ label: 'Buttons', value: 'buttons' },
|
||||
{ label: 'Vertikal', value: 'vertical' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tabPosition',
|
||||
type: 'select',
|
||||
defaultValue: 'top',
|
||||
label: 'Tab-Position',
|
||||
options: [
|
||||
{ label: 'Oben', value: 'top' },
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
{ label: 'Unten', value: 'bottom' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.tabStyle !== 'vertical',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tabAlignment',
|
||||
type: 'select',
|
||||
defaultValue: 'left',
|
||||
label: 'Tab-Ausrichtung',
|
||||
options: [
|
||||
{ label: 'Links', value: 'left' },
|
||||
{ label: 'Zentriert', value: 'center' },
|
||||
{ label: 'Rechts', value: 'right' },
|
||||
{ label: 'Gleichmäßig verteilt', value: 'stretch' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.tabPosition === 'top' || siblingData?.tabPosition === 'bottom',
|
||||
},
|
||||
},
|
||||
// Verhalten
|
||||
{
|
||||
name: 'defaultTab',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
min: 0,
|
||||
label: 'Standard-Tab (Index)',
|
||||
admin: {
|
||||
description: '0 = erster Tab',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allowKeyboardNavigation',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Tastatur-Navigation',
|
||||
},
|
||||
{
|
||||
name: 'animated',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Animierter Übergang',
|
||||
},
|
||||
{
|
||||
name: 'lazy',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Lazy Loading',
|
||||
admin: {
|
||||
description: 'Tab-Inhalte erst bei Aktivierung laden',
|
||||
},
|
||||
},
|
||||
// Mobile
|
||||
{
|
||||
name: 'mobileStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'scroll',
|
||||
label: 'Mobile-Ansicht',
|
||||
options: [
|
||||
{ label: 'Scrollbar', value: 'scroll' },
|
||||
{ label: 'Dropdown', value: 'dropdown' },
|
||||
{ label: 'Accordion', value: 'accordion' },
|
||||
{ label: 'Gestapelt', value: 'stacked' },
|
||||
],
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
name: 'backgroundColor',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Dunkel', value: 'dark' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'contentBackground',
|
||||
type: 'select',
|
||||
defaultValue: 'white',
|
||||
label: 'Content-Hintergrund',
|
||||
options: [
|
||||
{ label: 'Weiß', value: 'white' },
|
||||
{ label: 'Hell (Grau)', value: 'light' },
|
||||
{ label: 'Transparent', value: 'transparent' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'showBorder',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Rahmen anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'fullWidth',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Volle Breite',
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -29,3 +29,18 @@ export { TableOfContentsBlock } from './TableOfContentsBlock'
|
|||
// Team Blocks
|
||||
export { TeamFilterBlock } from './TeamFilterBlock'
|
||||
export { OrgChartBlock } from './OrgChartBlock'
|
||||
|
||||
// New Feature Blocks
|
||||
export { LocationsBlock } from './LocationsBlock'
|
||||
export { LogoGridBlock } from './LogoGridBlock'
|
||||
export { StatsBlock } from './StatsBlock'
|
||||
export { JobsBlock } from './JobsBlock'
|
||||
export { DownloadsBlock } from './DownloadsBlock'
|
||||
export { MapBlock } from './MapBlock'
|
||||
|
||||
// Events & Interactive Blocks
|
||||
export { EventsBlock } from './EventsBlock'
|
||||
export { PricingBlock } from './PricingBlock'
|
||||
export { TabsBlock } from './TabsBlock'
|
||||
export { AccordionBlock } from './AccordionBlock'
|
||||
export { ComparisonBlock } from './ComparisonBlock'
|
||||
|
|
|
|||
254
src/collections/Downloads.ts
Normal file
254
src/collections/Downloads.ts
Normal file
|
|
@ -0,0 +1,254 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Downloads Collection
|
||||
*
|
||||
* Downloadbare Dateien wie PDFs, Broschüren, Formulare, etc.
|
||||
* Mit Kategorisierung, Tracking und Zugriffskontrolle.
|
||||
*/
|
||||
export const Downloads: CollectionConfig = {
|
||||
slug: 'downloads',
|
||||
labels: {
|
||||
singular: 'Download',
|
||||
plural: 'Downloads',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
group: 'Content',
|
||||
defaultColumns: ['title', 'category', 'fileType', 'downloadCount', 'isActive'],
|
||||
description: 'Downloadbare Dateien und Dokumente',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'file',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
label: 'Datei',
|
||||
},
|
||||
{
|
||||
name: 'fileType',
|
||||
type: 'select',
|
||||
label: 'Dateityp',
|
||||
options: [
|
||||
{ label: 'PDF', value: 'pdf' },
|
||||
{ label: 'Word', value: 'doc' },
|
||||
{ label: 'Excel', value: 'xls' },
|
||||
{ label: 'PowerPoint', value: 'ppt' },
|
||||
{ label: 'Bild', value: 'image' },
|
||||
{ label: 'Video', value: 'video' },
|
||||
{ label: 'Audio', value: 'audio' },
|
||||
{ label: 'ZIP/Archiv', value: 'archive' },
|
||||
{ label: 'Sonstiges', value: 'other' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Wird automatisch erkannt, kann überschrieben werden',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'fileSize',
|
||||
type: 'text',
|
||||
label: 'Dateigröße',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'z.B. "2.5 MB" - wird automatisch berechnet',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
// Kategorisierung
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
label: 'Kategorie',
|
||||
options: [
|
||||
{ label: 'Broschüre', value: 'brochure' },
|
||||
{ label: 'Flyer', value: 'flyer' },
|
||||
{ label: 'Katalog', value: 'catalog' },
|
||||
{ label: 'Preisliste', value: 'pricelist' },
|
||||
{ label: 'Formular', value: 'form' },
|
||||
{ label: 'Anleitung', value: 'manual' },
|
||||
{ label: 'Datenblatt', value: 'datasheet' },
|
||||
{ label: 'Zertifikat', value: 'certificate' },
|
||||
{ label: 'Pressematerial', value: 'press' },
|
||||
{ label: 'Whitepaper', value: 'whitepaper' },
|
||||
{ label: 'Präsentation', value: 'presentation' },
|
||||
{ label: 'Vertrag/AGB', value: 'legal' },
|
||||
{ label: 'Sonstiges', value: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'array',
|
||||
label: 'Tags',
|
||||
fields: [
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Vorschau
|
||||
{
|
||||
name: 'thumbnail',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Vorschaubild',
|
||||
admin: {
|
||||
description: 'Optionales Thumbnail (sonst wird Standard-Icon verwendet)',
|
||||
},
|
||||
},
|
||||
// Verknüpfungen
|
||||
{
|
||||
name: 'relatedServices',
|
||||
type: 'relationship',
|
||||
relationTo: 'services',
|
||||
hasMany: true,
|
||||
label: 'Zugehörige Services',
|
||||
},
|
||||
{
|
||||
name: 'relatedProducts',
|
||||
type: 'relationship',
|
||||
relationTo: 'products',
|
||||
hasMany: true,
|
||||
label: 'Zugehörige Produkte',
|
||||
},
|
||||
// Zugriff
|
||||
{
|
||||
name: 'access',
|
||||
type: 'group',
|
||||
label: 'Zugriff',
|
||||
fields: [
|
||||
{
|
||||
name: 'requireLogin',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Login erforderlich',
|
||||
},
|
||||
{
|
||||
name: 'requireForm',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Formular vor Download',
|
||||
admin: {
|
||||
description: 'User müssen Kontaktdaten eingeben',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'formFields',
|
||||
type: 'select',
|
||||
hasMany: true,
|
||||
label: 'Formular-Felder',
|
||||
options: [
|
||||
{ label: 'Name', value: 'name' },
|
||||
{ label: 'E-Mail', value: 'email' },
|
||||
{ label: 'Firma', value: 'company' },
|
||||
{ label: 'Telefon', value: 'phone' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.requireForm,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Tracking
|
||||
{
|
||||
name: 'downloadCount',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Downloads',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
// Version
|
||||
{
|
||||
name: 'version',
|
||||
type: 'text',
|
||||
label: 'Version',
|
||||
admin: {
|
||||
description: 'z.B. "2024-01", "v2.0"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lastUpdated',
|
||||
type: 'date',
|
||||
label: 'Letzte Aktualisierung',
|
||||
},
|
||||
// Status
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isFeatured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
if (data && !data.slug && data.title) {
|
||||
const titleStr = typeof data.title === 'string' ? data.title : ''
|
||||
data.slug = titleStr
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
757
src/collections/Events.ts
Normal file
757
src/collections/Events.ts
Normal file
|
|
@ -0,0 +1,757 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Events Collection
|
||||
*
|
||||
* Veranstaltungen, Workshops, Seminare, Messen etc.
|
||||
* Unterstützt einmalige und wiederkehrende Events, Online/Offline,
|
||||
* Ticketing und Anmeldungen.
|
||||
*/
|
||||
export const Events: CollectionConfig = {
|
||||
slug: 'events',
|
||||
labels: {
|
||||
singular: 'Veranstaltung',
|
||||
plural: 'Veranstaltungen',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
group: 'Content',
|
||||
defaultColumns: ['title', 'eventType', 'startDate', 'location', 'status'],
|
||||
description: 'Veranstaltungen und Events',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
// Basis-Informationen
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-Pfad für das Event',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'excerpt',
|
||||
type: 'textarea',
|
||||
label: 'Kurzbeschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Für Übersichtsseiten und SEO',
|
||||
},
|
||||
},
|
||||
// Event-Typ
|
||||
{
|
||||
name: 'eventType',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'event',
|
||||
label: 'Event-Typ',
|
||||
options: [
|
||||
{ label: 'Veranstaltung', value: 'event' },
|
||||
{ label: 'Workshop', value: 'workshop' },
|
||||
{ label: 'Seminar', value: 'seminar' },
|
||||
{ label: 'Webinar', value: 'webinar' },
|
||||
{ label: 'Konferenz', value: 'conference' },
|
||||
{ label: 'Messe', value: 'tradeshow' },
|
||||
{ label: 'Networking', value: 'networking' },
|
||||
{ label: 'Kurs', value: 'course' },
|
||||
{ label: 'Vortrag', value: 'talk' },
|
||||
{ label: 'Sonstiges', value: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Kategorie',
|
||||
admin: {
|
||||
description: 'Freies Textfeld für Kategorisierung',
|
||||
},
|
||||
},
|
||||
// Format
|
||||
{
|
||||
name: 'format',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'onsite',
|
||||
label: 'Format',
|
||||
options: [
|
||||
{ label: 'Vor Ort', value: 'onsite' },
|
||||
{ label: 'Online', value: 'online' },
|
||||
{ label: 'Hybrid', value: 'hybrid' },
|
||||
],
|
||||
},
|
||||
// Datum & Zeit
|
||||
{
|
||||
type: 'row',
|
||||
fields: [
|
||||
{
|
||||
name: 'startDate',
|
||||
type: 'date',
|
||||
required: true,
|
||||
label: 'Startdatum',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
width: '50%',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'endDate',
|
||||
type: 'date',
|
||||
label: 'Enddatum',
|
||||
admin: {
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
width: '50%',
|
||||
description: 'Leer lassen für eintägige Events',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'isAllDay',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Ganztägig',
|
||||
},
|
||||
{
|
||||
name: 'timezone',
|
||||
type: 'text',
|
||||
defaultValue: 'Europe/Berlin',
|
||||
label: 'Zeitzone',
|
||||
admin: {
|
||||
description: 'z.B. Europe/Berlin, America/New_York',
|
||||
},
|
||||
},
|
||||
// Wiederkehrende Events
|
||||
{
|
||||
name: 'isRecurring',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Wiederkehrendes Event',
|
||||
},
|
||||
{
|
||||
name: 'recurrence',
|
||||
type: 'group',
|
||||
label: 'Wiederholung',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isRecurring,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'frequency',
|
||||
type: 'select',
|
||||
label: 'Häufigkeit',
|
||||
options: [
|
||||
{ label: 'Täglich', value: 'daily' },
|
||||
{ label: 'Wöchentlich', value: 'weekly' },
|
||||
{ label: 'Monatlich', value: 'monthly' },
|
||||
{ label: 'Jährlich', value: 'yearly' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'interval',
|
||||
type: 'number',
|
||||
min: 1,
|
||||
defaultValue: 1,
|
||||
label: 'Intervall',
|
||||
admin: {
|
||||
description: 'Alle X Tage/Wochen/Monate/Jahre',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'endRecurrence',
|
||||
type: 'date',
|
||||
label: 'Ende der Wiederholung',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
label: 'Wiederholungs-Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Jeden ersten Montag im Monat"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Ort (für Vor-Ort Events)
|
||||
{
|
||||
name: 'location',
|
||||
type: 'group',
|
||||
label: 'Veranstaltungsort',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.format === 'onsite' || siblingData?.format === 'hybrid',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'locationRef',
|
||||
type: 'relationship',
|
||||
relationTo: 'locations' as 'users',
|
||||
label: 'Standort auswählen',
|
||||
admin: {
|
||||
description: 'Aus bestehenden Standorten wählen',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'customLocation',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Eigene Adresse angeben',
|
||||
},
|
||||
{
|
||||
name: 'venueName',
|
||||
type: 'text',
|
||||
label: 'Veranstaltungsort Name',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.customLocation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'street',
|
||||
type: 'text',
|
||||
label: 'Straße',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.customLocation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'zip',
|
||||
type: 'text',
|
||||
label: 'PLZ',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.customLocation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
label: 'Stadt',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.customLocation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
type: 'text',
|
||||
defaultValue: 'Deutschland',
|
||||
label: 'Land',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.customLocation,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'room',
|
||||
type: 'text',
|
||||
label: 'Raum/Saal',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'directions',
|
||||
type: 'textarea',
|
||||
label: 'Anfahrt',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Online-Details
|
||||
{
|
||||
name: 'online',
|
||||
type: 'group',
|
||||
label: 'Online-Details',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.format === 'online' || siblingData?.format === 'hybrid',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'platform',
|
||||
type: 'select',
|
||||
label: 'Plattform',
|
||||
options: [
|
||||
{ label: 'Zoom', value: 'zoom' },
|
||||
{ label: 'Microsoft Teams', value: 'teams' },
|
||||
{ label: 'Google Meet', value: 'google-meet' },
|
||||
{ label: 'Webex', value: 'webex' },
|
||||
{ label: 'GoToWebinar', value: 'gotowebinar' },
|
||||
{ label: 'Livestream', value: 'livestream' },
|
||||
{ label: 'Sonstiges', value: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'accessLink',
|
||||
type: 'text',
|
||||
label: 'Zugangslink',
|
||||
admin: {
|
||||
description: 'Wird nur angemeldeten Teilnehmern angezeigt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'accessInfo',
|
||||
type: 'textarea',
|
||||
label: 'Zugangsinformationen',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. Meeting-ID, Passwort',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Medien
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Event-Bild',
|
||||
},
|
||||
{
|
||||
name: 'gallery',
|
||||
type: 'array',
|
||||
label: 'Galerie',
|
||||
fields: [
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: 'caption',
|
||||
type: 'text',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Anmeldung & Tickets
|
||||
{
|
||||
name: 'registration',
|
||||
type: 'group',
|
||||
label: 'Anmeldung',
|
||||
fields: [
|
||||
{
|
||||
name: 'required',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Anmeldung erforderlich',
|
||||
},
|
||||
{
|
||||
name: 'method',
|
||||
type: 'select',
|
||||
label: 'Anmeldemethode',
|
||||
defaultValue: 'form',
|
||||
options: [
|
||||
{ label: 'Internes Formular', value: 'form' },
|
||||
{ label: 'E-Mail', value: 'email' },
|
||||
{ label: 'Externer Link', value: 'external' },
|
||||
{ label: 'Telefon', value: 'phone' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'formId',
|
||||
type: 'relationship',
|
||||
relationTo: 'forms',
|
||||
label: 'Anmeldeformular',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required && siblingData?.method === 'form',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'Anmelde-E-Mail',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required && siblingData?.method === 'email',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalUrl',
|
||||
type: 'text',
|
||||
label: 'Externer Anmeldelink',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required && siblingData?.method === 'external',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Telefonnummer',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required && siblingData?.method === 'phone',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'deadline',
|
||||
type: 'date',
|
||||
label: 'Anmeldeschluss',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required,
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'maxParticipants',
|
||||
type: 'number',
|
||||
min: 0,
|
||||
label: 'Maximale Teilnehmer',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required,
|
||||
description: '0 = unbegrenzt',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'currentParticipants',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Aktuelle Anmeldungen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required,
|
||||
readOnly: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'waitlistEnabled',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Warteliste aktivieren',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.required && siblingData?.maxParticipants > 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Preise
|
||||
{
|
||||
name: 'pricing',
|
||||
type: 'group',
|
||||
label: 'Preise',
|
||||
fields: [
|
||||
{
|
||||
name: 'isFree',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kostenlos',
|
||||
},
|
||||
{
|
||||
name: 'prices',
|
||||
type: 'array',
|
||||
label: 'Preisoptionen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => !siblingData?.isFree,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Bezeichnung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Standard", "Frühbucher", "Ermäßigt"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'price',
|
||||
type: 'number',
|
||||
required: true,
|
||||
min: 0,
|
||||
label: 'Preis',
|
||||
},
|
||||
{
|
||||
name: 'currency',
|
||||
type: 'text',
|
||||
defaultValue: 'EUR',
|
||||
label: 'Währung',
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'text',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'validUntil',
|
||||
type: 'date',
|
||||
label: 'Gültig bis',
|
||||
admin: {
|
||||
description: 'Für Frühbucher-Preise',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'priceNote',
|
||||
type: 'text',
|
||||
label: 'Preis-Hinweis',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "zzgl. MwSt.", "inkl. Verpflegung"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Sprecher/Referenten
|
||||
{
|
||||
name: 'speakers',
|
||||
type: 'array',
|
||||
label: 'Referenten',
|
||||
fields: [
|
||||
{
|
||||
name: 'teamMember',
|
||||
type: 'relationship',
|
||||
relationTo: 'team' as 'users',
|
||||
label: 'Aus Team wählen',
|
||||
},
|
||||
{
|
||||
name: 'isExternal',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Externer Referent',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Name',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isExternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
label: 'Titel/Position',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isExternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'company',
|
||||
type: 'text',
|
||||
label: 'Unternehmen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isExternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'photo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Foto',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isExternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'bio',
|
||||
type: 'textarea',
|
||||
label: 'Kurz-Bio',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.isExternal,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'role',
|
||||
type: 'text',
|
||||
label: 'Rolle beim Event',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Hauptredner", "Workshop-Leiter"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Agenda/Programm
|
||||
{
|
||||
name: 'agenda',
|
||||
type: 'array',
|
||||
label: 'Programm',
|
||||
fields: [
|
||||
{
|
||||
name: 'time',
|
||||
type: 'text',
|
||||
label: 'Uhrzeit',
|
||||
admin: {
|
||||
description: 'z.B. "09:00 - 10:30"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'speaker',
|
||||
type: 'text',
|
||||
label: 'Referent',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'Vortrag', value: 'talk' },
|
||||
{ label: 'Workshop', value: 'workshop' },
|
||||
{ label: 'Pause', value: 'break' },
|
||||
{ label: 'Networking', value: 'networking' },
|
||||
{ label: 'Panel', value: 'panel' },
|
||||
{ label: 'Q&A', value: 'qa' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Kontakt
|
||||
{
|
||||
name: 'contact',
|
||||
type: 'group',
|
||||
label: 'Ansprechpartner',
|
||||
fields: [
|
||||
{
|
||||
name: 'teamMember',
|
||||
type: 'relationship',
|
||||
relationTo: 'team' as 'users',
|
||||
label: 'Aus Team wählen',
|
||||
},
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
label: 'Name',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Telefon',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Verknüpfungen
|
||||
{
|
||||
name: 'relatedServices',
|
||||
type: 'relationship',
|
||||
relationTo: 'services' as 'users',
|
||||
hasMany: true,
|
||||
label: 'Verwandte Leistungen',
|
||||
},
|
||||
{
|
||||
name: 'relatedEvents',
|
||||
type: 'relationship',
|
||||
relationTo: 'events' as 'users',
|
||||
hasMany: true,
|
||||
label: 'Verwandte Events',
|
||||
},
|
||||
// SEO
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta-Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta-Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'ogImage',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Social Media Bild',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Status
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'draft',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: 'Entwurf', value: 'draft' },
|
||||
{ label: 'Veröffentlicht', value: 'published' },
|
||||
{ label: 'Ausgebucht', value: 'soldout' },
|
||||
{ label: 'Abgesagt', value: 'cancelled' },
|
||||
{ label: 'Verschoben', value: 'postponed' },
|
||||
{ label: 'Beendet', value: 'past' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isFeatured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'publishedAt',
|
||||
type: 'date',
|
||||
label: 'Veröffentlichungsdatum',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
date: {
|
||||
pickerAppearance: 'dayAndTime',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
455
src/collections/Jobs.ts
Normal file
455
src/collections/Jobs.ts
Normal file
|
|
@ -0,0 +1,455 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Jobs Collection
|
||||
*
|
||||
* Stellenanzeigen und Karriere-Seite.
|
||||
* Unterstützt verschiedene Beschäftigungsarten, Standorte und Bewerbungsoptionen.
|
||||
*/
|
||||
export const Jobs: CollectionConfig = {
|
||||
slug: 'jobs',
|
||||
labels: {
|
||||
singular: 'Stellenanzeige',
|
||||
plural: 'Stellenanzeigen',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'title',
|
||||
group: 'Content',
|
||||
defaultColumns: ['title', 'department', 'type', 'location', 'status', 'publishedAt'],
|
||||
description: 'Stellenanzeigen und Karriere',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
// Basis-Informationen
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Stellentitel',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Pflegefachkraft (m/w/d)", "Senior Developer"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
},
|
||||
{
|
||||
name: 'reference',
|
||||
type: 'text',
|
||||
label: 'Referenznummer',
|
||||
admin: {
|
||||
description: 'Interne Stellennummer',
|
||||
},
|
||||
},
|
||||
// Kategorisierung
|
||||
{
|
||||
name: 'department',
|
||||
type: 'text',
|
||||
label: 'Abteilung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Pflege", "IT", "Marketing"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'select',
|
||||
label: 'Kategorie',
|
||||
options: [
|
||||
{ label: 'Pflege & Betreuung', value: 'care' },
|
||||
{ label: 'Medizin', value: 'medical' },
|
||||
{ label: 'Verwaltung', value: 'admin' },
|
||||
{ label: 'IT & Technik', value: 'it' },
|
||||
{ label: 'Marketing', value: 'marketing' },
|
||||
{ label: 'Vertrieb', value: 'sales' },
|
||||
{ label: 'Finanzen', value: 'finance' },
|
||||
{ label: 'Personal', value: 'hr' },
|
||||
{ label: 'Produktion', value: 'production' },
|
||||
{ label: 'Logistik', value: 'logistics' },
|
||||
{ label: 'Sonstiges', value: 'other' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'fulltime',
|
||||
label: 'Beschäftigungsart',
|
||||
options: [
|
||||
{ label: 'Vollzeit', value: 'fulltime' },
|
||||
{ label: 'Teilzeit', value: 'parttime' },
|
||||
{ label: 'Minijob', value: 'minijob' },
|
||||
{ label: 'Werkstudent', value: 'working_student' },
|
||||
{ label: 'Praktikum', value: 'internship' },
|
||||
{ label: 'Ausbildung', value: 'apprenticeship' },
|
||||
{ label: 'Duales Studium', value: 'dual_study' },
|
||||
{ label: 'Freelance', value: 'freelance' },
|
||||
{ label: 'Befristet', value: 'temporary' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'workModel',
|
||||
type: 'select',
|
||||
label: 'Arbeitsmodell',
|
||||
options: [
|
||||
{ label: 'Vor Ort', value: 'onsite' },
|
||||
{ label: 'Remote', value: 'remote' },
|
||||
{ label: 'Hybrid', value: 'hybrid' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'experienceLevel',
|
||||
type: 'select',
|
||||
label: 'Erfahrungslevel',
|
||||
options: [
|
||||
{ label: 'Berufseinsteiger', value: 'entry' },
|
||||
{ label: 'Mit Berufserfahrung', value: 'experienced' },
|
||||
{ label: 'Senior', value: 'senior' },
|
||||
{ label: 'Führungskraft', value: 'management' },
|
||||
],
|
||||
},
|
||||
// Standort
|
||||
{
|
||||
name: 'location',
|
||||
type: 'group',
|
||||
label: 'Standort',
|
||||
fields: [
|
||||
{
|
||||
name: 'locationRef',
|
||||
type: 'relationship',
|
||||
relationTo: 'locations',
|
||||
label: 'Standort (aus Collection)',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
label: 'Stadt',
|
||||
admin: {
|
||||
description: 'Falls kein Standort aus Collection',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'region',
|
||||
type: 'text',
|
||||
label: 'Region/Bundesland',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
type: 'text',
|
||||
defaultValue: 'Deutschland',
|
||||
label: 'Land',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Beschreibung
|
||||
{
|
||||
name: 'summary',
|
||||
type: 'textarea',
|
||||
label: 'Kurzbeschreibung',
|
||||
localized: true,
|
||||
maxLength: 300,
|
||||
admin: {
|
||||
description: 'Für Übersichten und SEO (max. 300 Zeichen)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
},
|
||||
// Strukturierte Abschnitte
|
||||
{
|
||||
name: 'responsibilities',
|
||||
type: 'richText',
|
||||
label: 'Aufgaben',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'requirements',
|
||||
type: 'richText',
|
||||
label: 'Anforderungen',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'qualifications',
|
||||
type: 'richText',
|
||||
label: 'Wünschenswerte Qualifikationen',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'benefits',
|
||||
type: 'richText',
|
||||
label: 'Was wir bieten',
|
||||
localized: true,
|
||||
},
|
||||
// Benefits als Liste
|
||||
{
|
||||
name: 'benefitsList',
|
||||
type: 'array',
|
||||
label: 'Benefits (Stichpunkte)',
|
||||
admin: {
|
||||
description: 'Für visuelle Darstellung mit Icons',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'benefit',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Benefit',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'select',
|
||||
label: 'Icon',
|
||||
options: [
|
||||
{ label: 'Gehalt', value: 'money' },
|
||||
{ label: 'Urlaub', value: 'vacation' },
|
||||
{ label: 'Homeoffice', value: 'home' },
|
||||
{ label: 'Weiterbildung', value: 'education' },
|
||||
{ label: 'Gesundheit', value: 'health' },
|
||||
{ label: 'Team', value: 'team' },
|
||||
{ label: 'Flexibel', value: 'flexible' },
|
||||
{ label: 'Jobticket', value: 'transport' },
|
||||
{ label: 'Essen', value: 'food' },
|
||||
{ label: 'Kinderbetreuung', value: 'childcare' },
|
||||
{ label: 'Fitness', value: 'fitness' },
|
||||
{ label: 'Parkplatz', value: 'parking' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Gehalt
|
||||
{
|
||||
name: 'salary',
|
||||
type: 'group',
|
||||
label: 'Gehalt',
|
||||
fields: [
|
||||
{
|
||||
name: 'show',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Gehalt anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'Jahresgehalt', value: 'yearly' },
|
||||
{ label: 'Monatsgehalt', value: 'monthly' },
|
||||
{ label: 'Stundenlohn', value: 'hourly' },
|
||||
],
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'min',
|
||||
type: 'number',
|
||||
label: 'Von',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'max',
|
||||
type: 'number',
|
||||
label: 'Bis',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'currency',
|
||||
type: 'text',
|
||||
defaultValue: 'EUR',
|
||||
label: 'Währung',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'text',
|
||||
label: 'Hinweis',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.show,
|
||||
description: 'z.B. "je nach Qualifikation"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Bewerbung
|
||||
{
|
||||
name: 'application',
|
||||
type: 'group',
|
||||
label: 'Bewerbung',
|
||||
fields: [
|
||||
{
|
||||
name: 'method',
|
||||
type: 'select',
|
||||
defaultValue: 'email',
|
||||
label: 'Bewerbungsweg',
|
||||
options: [
|
||||
{ label: 'Per E-Mail', value: 'email' },
|
||||
{ label: 'Online-Formular', value: 'form' },
|
||||
{ label: 'Externes Portal', value: 'external' },
|
||||
{ label: 'Per Post', value: 'mail' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'Bewerbungs-E-Mail',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.method === 'email',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'formId',
|
||||
type: 'relationship',
|
||||
relationTo: 'forms',
|
||||
label: 'Bewerbungsformular',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.method === 'form',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'externalUrl',
|
||||
type: 'text',
|
||||
label: 'Externes Portal (URL)',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.method === 'external',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'contact',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
label: 'Ansprechpartner',
|
||||
},
|
||||
{
|
||||
name: 'deadline',
|
||||
type: 'date',
|
||||
label: 'Bewerbungsfrist',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Media
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
admin: {
|
||||
description: 'Bild für die Stellenanzeige',
|
||||
},
|
||||
},
|
||||
// Datum & Status
|
||||
{
|
||||
name: 'status',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'draft',
|
||||
label: 'Status',
|
||||
options: [
|
||||
{ label: 'Entwurf', value: 'draft' },
|
||||
{ label: 'Veröffentlicht', value: 'published' },
|
||||
{ label: 'Besetzt', value: 'filled' },
|
||||
{ label: 'Archiviert', value: 'archived' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'publishedAt',
|
||||
type: 'date',
|
||||
label: 'Veröffentlicht am',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
date: {
|
||||
pickerAppearance: 'dayOnly',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isFeatured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isUrgent',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Dringend zu besetzen',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
// SEO
|
||||
{
|
||||
name: 'seo',
|
||||
type: 'group',
|
||||
label: 'SEO',
|
||||
fields: [
|
||||
{
|
||||
name: 'metaTitle',
|
||||
type: 'text',
|
||||
label: 'Meta-Titel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'metaDescription',
|
||||
type: 'textarea',
|
||||
label: 'Meta-Beschreibung',
|
||||
localized: true,
|
||||
maxLength: 160,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
if (data && !data.slug && data.title) {
|
||||
const titleStr = typeof data.title === 'string' ? data.title : ''
|
||||
data.slug = titleStr
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/\(m\/w\/d\)/gi, '')
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
419
src/collections/Locations.ts
Normal file
419
src/collections/Locations.ts
Normal file
|
|
@ -0,0 +1,419 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Locations Collection
|
||||
*
|
||||
* Firmenstandorte mit Adresse, Öffnungszeiten, Kontaktdaten und Kartenposition.
|
||||
* Tenant-scoped für Multi-Tenant-Betrieb.
|
||||
*/
|
||||
export const Locations: CollectionConfig = {
|
||||
slug: 'locations',
|
||||
labels: {
|
||||
singular: 'Standort',
|
||||
plural: 'Standorte',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
group: 'Content',
|
||||
defaultColumns: ['name', 'city', 'type', 'isMain', 'isActive'],
|
||||
description: 'Firmenstandorte und Niederlassungen',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
// Basis-Informationen
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Hauptsitz München", "Filiale Hamburg"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
admin: {
|
||||
description: 'URL-freundlicher Identifier',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
defaultValue: 'office',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'Hauptsitz', value: 'headquarters' },
|
||||
{ label: 'Büro', value: 'office' },
|
||||
{ label: 'Filiale', value: 'branch' },
|
||||
{ label: 'Lager', value: 'warehouse' },
|
||||
{ label: 'Showroom', value: 'showroom' },
|
||||
{ label: 'Studio', value: 'studio' },
|
||||
{ label: 'Praxis', value: 'practice' },
|
||||
{ label: 'Werkstatt', value: 'workshop' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'richText',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Optionale Beschreibung des Standorts',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
admin: {
|
||||
description: 'Foto des Gebäudes oder Standorts',
|
||||
},
|
||||
},
|
||||
// Adresse
|
||||
{
|
||||
name: 'address',
|
||||
type: 'group',
|
||||
label: 'Adresse',
|
||||
fields: [
|
||||
{
|
||||
name: 'street',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Straße & Hausnummer',
|
||||
},
|
||||
{
|
||||
name: 'additionalLine',
|
||||
type: 'text',
|
||||
label: 'Adresszusatz',
|
||||
admin: {
|
||||
description: 'z.B. "Gebäude B, 3. Stock"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'zip',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'PLZ',
|
||||
},
|
||||
{
|
||||
name: 'city',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Stadt',
|
||||
},
|
||||
{
|
||||
name: 'state',
|
||||
type: 'text',
|
||||
label: 'Bundesland/Region',
|
||||
},
|
||||
{
|
||||
name: 'country',
|
||||
type: 'text',
|
||||
defaultValue: 'Deutschland',
|
||||
label: 'Land',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Geo-Koordinaten
|
||||
{
|
||||
name: 'geo',
|
||||
type: 'group',
|
||||
label: 'Geo-Koordinaten',
|
||||
admin: {
|
||||
description: 'Für Kartenanzeige',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'lat',
|
||||
type: 'number',
|
||||
label: 'Breitengrad (Latitude)',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
description: 'z.B. 48.137154',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'lng',
|
||||
type: 'number',
|
||||
label: 'Längengrad (Longitude)',
|
||||
admin: {
|
||||
step: 0.000001,
|
||||
description: 'z.B. 11.576124',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'zoom',
|
||||
type: 'number',
|
||||
defaultValue: 15,
|
||||
min: 1,
|
||||
max: 20,
|
||||
label: 'Zoom-Level',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Kontaktdaten
|
||||
{
|
||||
name: 'contact',
|
||||
type: 'group',
|
||||
label: 'Kontaktdaten',
|
||||
fields: [
|
||||
{
|
||||
name: 'phone',
|
||||
type: 'text',
|
||||
label: 'Telefon',
|
||||
},
|
||||
{
|
||||
name: 'fax',
|
||||
type: 'text',
|
||||
label: 'Fax',
|
||||
},
|
||||
{
|
||||
name: 'email',
|
||||
type: 'email',
|
||||
label: 'E-Mail',
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'text',
|
||||
label: 'Website',
|
||||
admin: {
|
||||
description: 'Falls abweichend von Hauptwebsite',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Öffnungszeiten
|
||||
{
|
||||
name: 'hours',
|
||||
type: 'group',
|
||||
label: 'Öffnungszeiten',
|
||||
fields: [
|
||||
{
|
||||
name: 'display',
|
||||
type: 'textarea',
|
||||
label: 'Freitext-Anzeige',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Mo-Fr: 9-18 Uhr, Sa: 10-14 Uhr"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'structured',
|
||||
type: 'array',
|
||||
label: 'Strukturierte Zeiten',
|
||||
admin: {
|
||||
description: 'Detaillierte Öffnungszeiten pro Tag',
|
||||
initCollapsed: true,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'day',
|
||||
type: 'select',
|
||||
required: true,
|
||||
label: 'Tag',
|
||||
options: [
|
||||
{ label: 'Montag', value: 'monday' },
|
||||
{ label: 'Dienstag', value: 'tuesday' },
|
||||
{ label: 'Mittwoch', value: 'wednesday' },
|
||||
{ label: 'Donnerstag', value: 'thursday' },
|
||||
{ label: 'Freitag', value: 'friday' },
|
||||
{ label: 'Samstag', value: 'saturday' },
|
||||
{ label: 'Sonntag', value: 'sunday' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'closed',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Geschlossen',
|
||||
},
|
||||
{
|
||||
name: 'open',
|
||||
type: 'text',
|
||||
label: 'Öffnet',
|
||||
admin: {
|
||||
condition: (_, siblingData) => !siblingData?.closed,
|
||||
description: 'z.B. "09:00"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'close',
|
||||
type: 'text',
|
||||
label: 'Schließt',
|
||||
admin: {
|
||||
condition: (_, siblingData) => !siblingData?.closed,
|
||||
description: 'z.B. "18:00"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'break',
|
||||
type: 'group',
|
||||
label: 'Mittagspause',
|
||||
admin: {
|
||||
condition: (_, siblingData) => !siblingData?.closed,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'hasBreak',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Mittagspause',
|
||||
},
|
||||
{
|
||||
name: 'start',
|
||||
type: 'text',
|
||||
label: 'Von',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.hasBreak,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'end',
|
||||
type: 'text',
|
||||
label: 'Bis',
|
||||
admin: {
|
||||
condition: (_, siblingData) => siblingData?.hasBreak,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'note',
|
||||
type: 'text',
|
||||
label: 'Hinweis',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Termine nach Vereinbarung"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
// Anfahrt
|
||||
{
|
||||
name: 'directions',
|
||||
type: 'group',
|
||||
label: 'Anfahrt',
|
||||
fields: [
|
||||
{
|
||||
name: 'text',
|
||||
type: 'richText',
|
||||
label: 'Anfahrtsbeschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'parking',
|
||||
type: 'text',
|
||||
label: 'Parkmöglichkeiten',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'publicTransport',
|
||||
type: 'text',
|
||||
label: 'Öffentliche Verkehrsmittel',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "U-Bahn U3 Haltestelle Münchner Freiheit"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'accessibility',
|
||||
type: 'text',
|
||||
label: 'Barrierefreiheit',
|
||||
localized: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Services an diesem Standort
|
||||
{
|
||||
name: 'services',
|
||||
type: 'relationship',
|
||||
relationTo: 'services',
|
||||
hasMany: true,
|
||||
label: 'Services an diesem Standort',
|
||||
admin: {
|
||||
description: 'Welche Dienstleistungen werden hier angeboten?',
|
||||
},
|
||||
},
|
||||
// Team an diesem Standort
|
||||
{
|
||||
name: 'teamMembers',
|
||||
type: 'relationship',
|
||||
relationTo: 'team',
|
||||
hasMany: true,
|
||||
label: 'Team-Mitglieder',
|
||||
admin: {
|
||||
description: 'Mitarbeiter an diesem Standort',
|
||||
},
|
||||
},
|
||||
// Status & Sortierung
|
||||
{
|
||||
name: 'isMain',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hauptstandort',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Als primärer Standort markieren',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showInFooter',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Im Footer anzeigen',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
// Auto-generate slug from name if not provided
|
||||
if (data && !data.slug && data.name) {
|
||||
data.slug = data.name
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
|
@ -28,6 +28,19 @@ import {
|
|||
// Team Blocks
|
||||
TeamFilterBlock,
|
||||
OrgChartBlock,
|
||||
// New Feature Blocks
|
||||
LocationsBlock,
|
||||
LogoGridBlock,
|
||||
StatsBlock,
|
||||
JobsBlock,
|
||||
DownloadsBlock,
|
||||
MapBlock,
|
||||
// Events & Interactive Blocks
|
||||
EventsBlock,
|
||||
PricingBlock,
|
||||
TabsBlock,
|
||||
AccordionBlock,
|
||||
ComparisonBlock,
|
||||
} from '../blocks'
|
||||
import { pagesAccess } from '../lib/access'
|
||||
|
||||
|
|
@ -110,6 +123,19 @@ export const Pages: CollectionConfig = {
|
|||
// Team Blocks
|
||||
TeamFilterBlock,
|
||||
OrgChartBlock,
|
||||
// New Feature Blocks
|
||||
LocationsBlock,
|
||||
LogoGridBlock,
|
||||
StatsBlock,
|
||||
JobsBlock,
|
||||
DownloadsBlock,
|
||||
MapBlock,
|
||||
// Events & Interactive Blocks
|
||||
EventsBlock,
|
||||
PricingBlock,
|
||||
TabsBlock,
|
||||
AccordionBlock,
|
||||
ComparisonBlock,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
274
src/collections/Partners.ts
Normal file
274
src/collections/Partners.ts
Normal file
|
|
@ -0,0 +1,274 @@
|
|||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* Partners Collection
|
||||
*
|
||||
* Partner, Kunden, Zertifizierungen und Referenzen mit Logos.
|
||||
* Vielseitig einsetzbar für Logo-Walls, Referenzen, Zertifikate.
|
||||
*/
|
||||
export const Partners: CollectionConfig = {
|
||||
slug: 'partners',
|
||||
labels: {
|
||||
singular: 'Partner',
|
||||
plural: 'Partner',
|
||||
},
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
group: 'Content',
|
||||
defaultColumns: ['name', 'type', 'isFeatured', 'isActive', 'order'],
|
||||
description: 'Partner, Kunden, Referenzen und Zertifizierungen',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
admin: {
|
||||
description: 'Firmen-/Partnername',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
unique: true,
|
||||
label: 'Slug',
|
||||
},
|
||||
{
|
||||
name: 'type',
|
||||
type: 'select',
|
||||
required: true,
|
||||
defaultValue: 'partner',
|
||||
label: 'Typ',
|
||||
options: [
|
||||
{ label: 'Partner', value: 'partner' },
|
||||
{ label: 'Kunde', value: 'client' },
|
||||
{ label: 'Referenz', value: 'reference' },
|
||||
{ label: 'Sponsor', value: 'sponsor' },
|
||||
{ label: 'Zertifizierung', value: 'certification' },
|
||||
{ label: 'Mitgliedschaft', value: 'membership' },
|
||||
{ label: 'Auszeichnung', value: 'award' },
|
||||
{ label: 'Lieferant', value: 'supplier' },
|
||||
{ label: 'Technologie', value: 'technology' },
|
||||
],
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'logo',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
required: true,
|
||||
label: 'Logo',
|
||||
admin: {
|
||||
description: 'Logo (empfohlen: SVG oder PNG mit transparentem Hintergrund)',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'logoLight',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Logo (Hell)',
|
||||
admin: {
|
||||
description: 'Alternative Version für dunkle Hintergründe',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Kurze Beschreibung der Partnerschaft',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'website',
|
||||
type: 'text',
|
||||
label: 'Website',
|
||||
},
|
||||
// Für Referenzen/Case Studies
|
||||
{
|
||||
name: 'caseStudy',
|
||||
type: 'group',
|
||||
label: 'Case Study / Referenz',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.type === 'client' || siblingData?.type === 'reference',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'quote',
|
||||
type: 'textarea',
|
||||
label: 'Zitat',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'quotePerson',
|
||||
type: 'text',
|
||||
label: 'Zitatgeber',
|
||||
},
|
||||
{
|
||||
name: 'quoteRole',
|
||||
type: 'text',
|
||||
label: 'Position',
|
||||
},
|
||||
{
|
||||
name: 'projectDescription',
|
||||
type: 'richText',
|
||||
label: 'Projektbeschreibung',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'results',
|
||||
type: 'array',
|
||||
label: 'Ergebnisse/Kennzahlen',
|
||||
fields: [
|
||||
{
|
||||
name: 'metric',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Kennzahl',
|
||||
admin: {
|
||||
description: 'z.B. "+50%", "10.000+"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'label',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Umsatzsteigerung", "Neue Kunden"',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
// Für Zertifizierungen
|
||||
{
|
||||
name: 'certification',
|
||||
type: 'group',
|
||||
label: 'Zertifizierungs-Details',
|
||||
admin: {
|
||||
condition: (_, siblingData) =>
|
||||
siblingData?.type === 'certification' ||
|
||||
siblingData?.type === 'membership' ||
|
||||
siblingData?.type === 'award',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'issuer',
|
||||
type: 'text',
|
||||
label: 'Aussteller',
|
||||
},
|
||||
{
|
||||
name: 'validFrom',
|
||||
type: 'date',
|
||||
label: 'Gültig ab',
|
||||
},
|
||||
{
|
||||
name: 'validUntil',
|
||||
type: 'date',
|
||||
label: 'Gültig bis',
|
||||
},
|
||||
{
|
||||
name: 'certNumber',
|
||||
type: 'text',
|
||||
label: 'Zertifikatsnummer',
|
||||
},
|
||||
{
|
||||
name: 'document',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Zertifikat (PDF)',
|
||||
},
|
||||
],
|
||||
},
|
||||
// Kategorisierung
|
||||
{
|
||||
name: 'category',
|
||||
type: 'text',
|
||||
label: 'Kategorie',
|
||||
admin: {
|
||||
description: 'z.B. "Technologie", "Finanzen", "Pflege"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'tags',
|
||||
type: 'array',
|
||||
label: 'Tags',
|
||||
fields: [
|
||||
{
|
||||
name: 'tag',
|
||||
type: 'text',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
// Status
|
||||
{
|
||||
name: 'isFeatured',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Hervorgehoben',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'since',
|
||||
type: 'date',
|
||||
label: 'Partner seit',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
],
|
||||
hooks: {
|
||||
beforeChange: [
|
||||
({ data }) => {
|
||||
if (data && !data.slug && data.name) {
|
||||
data.slug = data.name
|
||||
.toLowerCase()
|
||||
.replace(/[äöüß]/g, (match: string) => {
|
||||
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
|
||||
return map[match] || match
|
||||
})
|
||||
.replace(/[^a-z0-9]+/g, '-')
|
||||
.replace(/^-|-$/g, '')
|
||||
}
|
||||
return data
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
604
src/migrations/20251214_000000_add_priority_collections.ts
Normal file
604
src/migrations/20251214_000000_add_priority_collections.ts
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
// Create enums for locations
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_locations_hours_structured_day" AS ENUM('monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_locations_type" AS ENUM('headquarters', 'office', 'branch', 'warehouse', 'showroom', 'studio', 'practice', 'workshop');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// Create enums for partners
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_partners_type" AS ENUM('partner', 'client', 'reference', 'sponsor', 'certification', 'membership', 'award', 'supplier', 'technology');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// Create enums for jobs
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_benefits_list_icon" AS ENUM('money', 'vacation', 'home', 'education', 'health', 'team', 'flexible', 'transport', 'food', 'childcare', 'fitness', 'parking');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_category" AS ENUM('care', 'medical', 'admin', 'it', 'marketing', 'sales', 'finance', 'hr', 'production', 'logistics', 'other');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_type" AS ENUM('fulltime', 'parttime', 'minijob', 'working_student', 'internship', 'apprenticeship', 'dual_study', 'freelance', 'temporary');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_work_model" AS ENUM('onsite', 'remote', 'hybrid');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_experience_level" AS ENUM('entry', 'experienced', 'senior', 'management');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_salary_type" AS ENUM('yearly', 'monthly', 'hourly');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_application_method" AS ENUM('email', 'form', 'external', 'mail');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_jobs_status" AS ENUM('draft', 'published', 'filled', 'archived');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// Create enums for downloads
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_downloads_access_form_fields" AS ENUM('name', 'email', 'company', 'phone');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_downloads_file_type" AS ENUM('pdf', 'doc', 'xls', 'ppt', 'image', 'video', 'audio', 'archive', 'other');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE "public"."enum_downloads_category" AS ENUM('brochure', 'flyer', 'catalog', 'pricelist', 'form', 'manual', 'datasheet', 'certificate', 'press', 'whitepaper', 'presentation', 'legal', 'other');
|
||||
EXCEPTION
|
||||
WHEN duplicate_object THEN null;
|
||||
END $$;
|
||||
`)
|
||||
|
||||
// Create locations table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "locations" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer REFERENCES "tenants"("id") ON DELETE SET NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"type" "enum_locations_type" DEFAULT 'office',
|
||||
"image_id" integer REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"address_street" varchar NOT NULL,
|
||||
"address_additional_line" varchar,
|
||||
"address_zip" varchar NOT NULL,
|
||||
"address_city" varchar NOT NULL,
|
||||
"address_state" varchar,
|
||||
"address_country" varchar DEFAULT 'Deutschland',
|
||||
"geo_lat" numeric,
|
||||
"geo_lng" numeric,
|
||||
"geo_zoom" numeric DEFAULT 15,
|
||||
"contact_phone" varchar,
|
||||
"contact_fax" varchar,
|
||||
"contact_email" varchar,
|
||||
"contact_website" varchar,
|
||||
"is_main" boolean DEFAULT false,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"show_in_footer" boolean DEFAULT false,
|
||||
"order" numeric DEFAULT 0,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_tenant_idx" ON "locations" USING btree ("tenant_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "locations_slug_idx" ON "locations" USING btree ("slug");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_image_idx" ON "locations" USING btree ("image_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_updated_at_idx" ON "locations" USING btree ("updated_at");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_created_at_idx" ON "locations" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Create locations_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "locations_locales" (
|
||||
"name" varchar NOT NULL,
|
||||
"description" jsonb,
|
||||
"hours_display" varchar,
|
||||
"hours_note" varchar,
|
||||
"directions_text" jsonb,
|
||||
"directions_parking" varchar,
|
||||
"directions_public_transport" varchar,
|
||||
"directions_accessibility" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "locations"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "locations_locales_locale_parent_id_unique" ON "locations_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create locations_rels table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "locations_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL REFERENCES "locations"("id") ON DELETE CASCADE,
|
||||
"path" varchar NOT NULL,
|
||||
"services_id" integer REFERENCES "services"("id") ON DELETE CASCADE,
|
||||
"team_id" integer REFERENCES "team"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_rels_order_idx" ON "locations_rels" USING btree ("order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_rels_parent_idx" ON "locations_rels" USING btree ("parent_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_rels_path_idx" ON "locations_rels" USING btree ("path");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_rels_services_id_idx" ON "locations_rels" USING btree ("services_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "locations_rels_team_id_idx" ON "locations_rels" USING btree ("team_id");
|
||||
`)
|
||||
|
||||
// Create partners table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "partners" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer REFERENCES "tenants"("id") ON DELETE SET NULL,
|
||||
"name" varchar NOT NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"type" "enum_partners_type" NOT NULL DEFAULT 'partner',
|
||||
"logo_id" integer NOT NULL REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"logo_light_id" integer REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"website" varchar,
|
||||
"case_study_quote_person" varchar,
|
||||
"case_study_quote_role" varchar,
|
||||
"certification_issuer" varchar,
|
||||
"certification_valid_from" timestamp(3) with time zone,
|
||||
"certification_valid_until" timestamp(3) with time zone,
|
||||
"certification_cert_number" varchar,
|
||||
"certification_document_id" integer REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"category" varchar,
|
||||
"is_featured" boolean DEFAULT false,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"order" numeric DEFAULT 0,
|
||||
"since" timestamp(3) with time zone,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_tenant_idx" ON "partners" USING btree ("tenant_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "partners_slug_idx" ON "partners" USING btree ("slug");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_logo_idx" ON "partners" USING btree ("logo_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_logo_light_idx" ON "partners" USING btree ("logo_light_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_certification_certification_document_idx" ON "partners" USING btree ("certification_document_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_updated_at_idx" ON "partners" USING btree ("updated_at");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_created_at_idx" ON "partners" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Create partners_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "partners_locales" (
|
||||
"description" varchar,
|
||||
"case_study_quote" varchar,
|
||||
"case_study_project_description" jsonb,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "partners"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "partners_locales_locale_parent_id_unique" ON "partners_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create partners_case_study_results table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "partners_case_study_results" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "partners"("id") ON DELETE CASCADE,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"metric" varchar
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_case_study_results_order_idx" ON "partners_case_study_results" USING btree ("_order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_case_study_results_parent_id_idx" ON "partners_case_study_results" USING btree ("_parent_id");
|
||||
`)
|
||||
|
||||
// Create partners_case_study_results_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "partners_case_study_results_locales" (
|
||||
"label" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL REFERENCES "partners_case_study_results"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "partners_case_study_results_locales_locale_parent_id_unique" ON "partners_case_study_results_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create partners_tags table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "partners_tags" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "partners"("id") ON DELETE CASCADE,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"tag" varchar NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_tags_order_idx" ON "partners_tags" USING btree ("_order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "partners_tags_parent_id_idx" ON "partners_tags" USING btree ("_parent_id");
|
||||
`)
|
||||
|
||||
// Create jobs table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "jobs" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer REFERENCES "tenants"("id") ON DELETE SET NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"reference" varchar,
|
||||
"category" "enum_jobs_category",
|
||||
"type" "enum_jobs_type" NOT NULL DEFAULT 'fulltime',
|
||||
"work_model" "enum_jobs_work_model",
|
||||
"experience_level" "enum_jobs_experience_level",
|
||||
"location_location_ref_id" integer REFERENCES "locations"("id") ON DELETE SET NULL,
|
||||
"location_city" varchar,
|
||||
"location_region" varchar,
|
||||
"location_country" varchar DEFAULT 'Deutschland',
|
||||
"salary_show" boolean DEFAULT false,
|
||||
"salary_type" "enum_jobs_salary_type",
|
||||
"salary_min" numeric,
|
||||
"salary_max" numeric,
|
||||
"salary_currency" varchar DEFAULT 'EUR',
|
||||
"application_method" "enum_jobs_application_method" DEFAULT 'email',
|
||||
"application_email" varchar,
|
||||
"application_form_id_id" integer REFERENCES "forms"("id") ON DELETE SET NULL,
|
||||
"application_external_url" varchar,
|
||||
"application_contact_id" integer REFERENCES "team"("id") ON DELETE SET NULL,
|
||||
"application_deadline" timestamp(3) with time zone,
|
||||
"image_id" integer REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"status" "enum_jobs_status" NOT NULL DEFAULT 'draft',
|
||||
"published_at" timestamp(3) with time zone,
|
||||
"is_featured" boolean DEFAULT false,
|
||||
"is_urgent" boolean DEFAULT false,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_tenant_idx" ON "jobs" USING btree ("tenant_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "jobs_slug_idx" ON "jobs" USING btree ("slug");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_location_location_location_ref_idx" ON "jobs" USING btree ("location_location_ref_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_application_application_form_id_idx" ON "jobs" USING btree ("application_form_id_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_application_application_contact_idx" ON "jobs" USING btree ("application_contact_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_image_idx" ON "jobs" USING btree ("image_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_updated_at_idx" ON "jobs" USING btree ("updated_at");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_created_at_idx" ON "jobs" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Create jobs_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "jobs_locales" (
|
||||
"title" varchar NOT NULL,
|
||||
"department" varchar,
|
||||
"summary" varchar,
|
||||
"description" jsonb,
|
||||
"responsibilities" jsonb,
|
||||
"requirements" jsonb,
|
||||
"qualifications" jsonb,
|
||||
"benefits" jsonb,
|
||||
"salary_note" varchar,
|
||||
"seo_meta_title" varchar,
|
||||
"seo_meta_description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "jobs"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "jobs_locales_locale_parent_id_unique" ON "jobs_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create jobs_benefits_list table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "jobs_benefits_list" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "jobs"("id") ON DELETE CASCADE,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"icon" "enum_jobs_benefits_list_icon"
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_benefits_list_order_idx" ON "jobs_benefits_list" USING btree ("_order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "jobs_benefits_list_parent_id_idx" ON "jobs_benefits_list" USING btree ("_parent_id");
|
||||
`)
|
||||
|
||||
// Create jobs_benefits_list_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "jobs_benefits_list_locales" (
|
||||
"benefit" varchar NOT NULL,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL REFERENCES "jobs_benefits_list"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "jobs_benefits_list_locales_locale_parent_id_unique" ON "jobs_benefits_list_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create downloads table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "downloads" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer REFERENCES "tenants"("id") ON DELETE SET NULL,
|
||||
"slug" varchar NOT NULL,
|
||||
"file_id" integer NOT NULL REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"file_type" "enum_downloads_file_type",
|
||||
"file_size" varchar,
|
||||
"category" "enum_downloads_category",
|
||||
"thumbnail_id" integer REFERENCES "media"("id") ON DELETE SET NULL,
|
||||
"access_require_login" boolean DEFAULT false,
|
||||
"access_require_form" boolean DEFAULT false,
|
||||
"download_count" numeric DEFAULT 0,
|
||||
"version" varchar,
|
||||
"last_updated" timestamp(3) with time zone,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"is_featured" boolean DEFAULT false,
|
||||
"order" numeric DEFAULT 0,
|
||||
"updated_at" timestamp(3) with time zone DEFAULT now() NOT NULL,
|
||||
"created_at" timestamp(3) with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_tenant_idx" ON "downloads" USING btree ("tenant_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "downloads_slug_idx" ON "downloads" USING btree ("slug");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_file_idx" ON "downloads" USING btree ("file_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_thumbnail_idx" ON "downloads" USING btree ("thumbnail_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_updated_at_idx" ON "downloads" USING btree ("updated_at");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_created_at_idx" ON "downloads" USING btree ("created_at");
|
||||
`)
|
||||
|
||||
// Create downloads_locales table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "downloads_locales" (
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "downloads"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "downloads_locales_locale_parent_id_unique" ON "downloads_locales" USING btree ("_locale", "_parent_id");
|
||||
`)
|
||||
|
||||
// Create downloads_tags table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "downloads_tags" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL REFERENCES "downloads"("id") ON DELETE CASCADE,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"tag" varchar NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_tags_order_idx" ON "downloads_tags" USING btree ("_order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_tags_parent_id_idx" ON "downloads_tags" USING btree ("_parent_id");
|
||||
`)
|
||||
|
||||
// Create downloads_access_form_fields table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "downloads_access_form_fields" (
|
||||
"order" integer NOT NULL,
|
||||
"parent_id" integer NOT NULL REFERENCES "downloads"("id") ON DELETE CASCADE,
|
||||
"value" "enum_downloads_access_form_fields",
|
||||
"id" serial PRIMARY KEY NOT NULL
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_access_form_fields_order_idx" ON "downloads_access_form_fields" USING btree ("order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_access_form_fields_parent_idx" ON "downloads_access_form_fields" USING btree ("parent_id");
|
||||
`)
|
||||
|
||||
// Create downloads_rels table
|
||||
await db.execute(sql`
|
||||
CREATE TABLE IF NOT EXISTS "downloads_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL REFERENCES "downloads"("id") ON DELETE CASCADE,
|
||||
"path" varchar NOT NULL,
|
||||
"services_id" integer REFERENCES "services"("id") ON DELETE CASCADE,
|
||||
"products_id" integer REFERENCES "products"("id") ON DELETE CASCADE
|
||||
);
|
||||
`)
|
||||
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_rels_order_idx" ON "downloads_rels" USING btree ("order");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_rels_parent_idx" ON "downloads_rels" USING btree ("parent_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_rels_path_idx" ON "downloads_rels" USING btree ("path");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_rels_services_id_idx" ON "downloads_rels" USING btree ("services_id");
|
||||
`)
|
||||
await db.execute(sql`
|
||||
CREATE INDEX IF NOT EXISTS "downloads_rels_products_id_idx" ON "downloads_rels" USING btree ("products_id");
|
||||
`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
// Drop tables in reverse order (respecting foreign key constraints)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "downloads_rels" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "downloads_access_form_fields" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "downloads_tags" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "downloads_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "downloads" CASCADE;`)
|
||||
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "jobs_benefits_list_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "jobs_benefits_list" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "jobs_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "jobs" CASCADE;`)
|
||||
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "partners_tags" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "partners_case_study_results_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "partners_case_study_results" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "partners_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "partners" CASCADE;`)
|
||||
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "locations_rels" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "locations_locales" CASCADE;`)
|
||||
await db.execute(sql`DROP TABLE IF EXISTS "locations" CASCADE;`)
|
||||
|
||||
// Drop enums
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_downloads_category" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_downloads_file_type" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_downloads_access_form_fields" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_status" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_application_method" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_salary_type" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_experience_level" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_work_model" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_type" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_category" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_jobs_benefits_list_icon" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_partners_type" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_locations_type" CASCADE;`)
|
||||
await db.execute(sql`DROP TYPE IF EXISTS "enum_locations_hours_structured_day" CASCADE;`)
|
||||
}
|
||||
|
|
@ -12,6 +12,11 @@ import * as migration_20251212_211506_add_products_collections from './20251212_
|
|||
import * as migration_20251213_100753_add_timelines_collection from './20251213_100753_add_timelines_collection';
|
||||
import * as migration_20251213_104523_add_workflows_and_timeline_process_fields from './20251213_104523_add_workflows_and_timeline_process_fields';
|
||||
import * as migration_20251213_145438_hero_slider_block from './20251213_145438_hero_slider_block';
|
||||
import * as migration_20251213_160000_testimonials_slider_v2 from './20251213_160000_testimonials_slider_v2';
|
||||
import * as migration_20251213_210000_image_slider_block from './20251213_210000_image_slider_block';
|
||||
import * as migration_20251213_220000_blogging_collections from './20251213_220000_blogging_collections';
|
||||
import * as migration_20251213_230000_team_extensions from './20251213_230000_team_extensions';
|
||||
import * as migration_20251214_000000_add_priority_collections from './20251214_000000_add_priority_collections';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -82,6 +87,31 @@ export const migrations = [
|
|||
{
|
||||
up: migration_20251213_145438_hero_slider_block.up,
|
||||
down: migration_20251213_145438_hero_slider_block.down,
|
||||
name: '20251213_145438_hero_slider_block'
|
||||
name: '20251213_145438_hero_slider_block',
|
||||
},
|
||||
{
|
||||
up: migration_20251213_160000_testimonials_slider_v2.up,
|
||||
down: migration_20251213_160000_testimonials_slider_v2.down,
|
||||
name: '20251213_160000_testimonials_slider_v2',
|
||||
},
|
||||
{
|
||||
up: migration_20251213_210000_image_slider_block.up,
|
||||
down: migration_20251213_210000_image_slider_block.down,
|
||||
name: '20251213_210000_image_slider_block',
|
||||
},
|
||||
{
|
||||
up: migration_20251213_220000_blogging_collections.up,
|
||||
down: migration_20251213_220000_blogging_collections.down,
|
||||
name: '20251213_220000_blogging_collections',
|
||||
},
|
||||
{
|
||||
up: migration_20251213_230000_team_extensions.up,
|
||||
down: migration_20251213_230000_team_extensions.down,
|
||||
name: '20251213_230000_team_extensions',
|
||||
},
|
||||
{
|
||||
up: migration_20251214_000000_add_priority_collections.up,
|
||||
down: migration_20251214_000000_add_priority_collections.down,
|
||||
name: '20251214_000000_add_priority_collections',
|
||||
},
|
||||
];
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
3776
src/payload-types.ts
3776
src/payload-types.ts
File diff suppressed because it is too large
Load diff
|
|
@ -48,6 +48,13 @@ import { Workflows } from './collections/Workflows'
|
|||
import { Tags } from './collections/Tags'
|
||||
import { Authors } from './collections/Authors'
|
||||
|
||||
// New Feature Collections
|
||||
import { Locations } from './collections/Locations'
|
||||
import { Partners } from './collections/Partners'
|
||||
import { Jobs } from './collections/Jobs'
|
||||
import { Downloads } from './collections/Downloads'
|
||||
import { Events } from './collections/Events'
|
||||
|
||||
// Consent Management Collections
|
||||
import { CookieConfigurations } from './collections/CookieConfigurations'
|
||||
import { CookieInventory } from './collections/CookieInventory'
|
||||
|
|
@ -168,6 +175,12 @@ export default buildConfig({
|
|||
// Blogging
|
||||
Tags,
|
||||
Authors,
|
||||
// New Feature Collections
|
||||
Locations,
|
||||
Partners,
|
||||
Jobs,
|
||||
Downloads,
|
||||
Events,
|
||||
// Consent Management
|
||||
CookieConfigurations,
|
||||
CookieInventory,
|
||||
|
|
@ -187,9 +200,8 @@ export default buildConfig({
|
|||
pool: {
|
||||
connectionString: env.DATABASE_URI,
|
||||
},
|
||||
// Deaktiviere automatisches Schema-Push
|
||||
// Änderungen sollten nur über Migrationen erfolgen
|
||||
push: false,
|
||||
// Temporär aktiviert für Events Collection
|
||||
push: true,
|
||||
}),
|
||||
// Sharp für Bildoptimierung
|
||||
sharp,
|
||||
|
|
@ -222,6 +234,12 @@ export default buildConfig({
|
|||
// Blogging Collections
|
||||
tags: {},
|
||||
authors: {},
|
||||
// New Feature Collections
|
||||
locations: {},
|
||||
partners: {},
|
||||
jobs: {},
|
||||
downloads: {},
|
||||
events: {},
|
||||
// Consent Management Collections - customTenantField: true weil sie bereits ein tenant-Feld haben
|
||||
'cookie-configurations': { customTenantField: true },
|
||||
'cookie-inventory': { customTenantField: true },
|
||||
|
|
|
|||
Loading…
Reference in a new issue