mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
- docs/zweitmeinung/: Migration guide (Strapi → Payload), content inventory, website guide, and reference screenshots - scripts/setup-github-protection.sh: Branch protection + Dependabot auto-merge setup for cms.c2sgmbh repo Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1618 lines
45 KiB
Markdown
1618 lines
45 KiB
Markdown
# Migration: Strapi zu Payload CMS
|
|
## Projekt: zweitmeinu.ng
|
|
|
|
**Erstellt:** 2026-02-20
|
|
**Strapi Version:** 5.x
|
|
**Ziel:** Payload CMS 3.x
|
|
|
|
---
|
|
|
|
## Inhaltsverzeichnis
|
|
|
|
1. [Executive Summary](#1-executive-summary)
|
|
2. [Content-Types / Collections](#2-content-types--collections)
|
|
3. [Components / Blocks](#3-components--blocks)
|
|
4. [API-Struktur](#4-api-struktur)
|
|
5. [Plugins & Extensions](#5-plugins--extensions)
|
|
6. [Datenbank & Relationen](#6-datenbank--relationen)
|
|
7. [Migrationsplan](#7-migrationsplan)
|
|
8. [Payload CMS Schema-Entwürfe](#8-payload-cms-schema-entwürfe)
|
|
|
|
---
|
|
|
|
## 1. Executive Summary
|
|
|
|
### Projektumfang
|
|
|
|
| Kategorie | Anzahl |
|
|
|-----------|--------|
|
|
| Content-Types | 16 |
|
|
| Components | 43 |
|
|
| API-Module | 18 |
|
|
| Plugins | 16 |
|
|
| Custom Controller Methods | 14 |
|
|
| Custom Services | 10 |
|
|
| Relationen | 25+ |
|
|
|
|
### Komplexitätsbewertung: **MITTEL-HOCH**
|
|
|
|
Hauptkomplexität liegt im:
|
|
- Termin-Buchungssystem (Appointment)
|
|
- Multi-Site-Konfiguration
|
|
- Dynamic Zone Page Builder
|
|
- Verschachtelte Component-Hierarchien
|
|
|
|
---
|
|
|
|
## 2. Content-Types / Collections
|
|
|
|
### 2.1 Übersicht aller Content-Types
|
|
|
|
| # | Content-Type | Draft/Publish | Felder | Relationen | Komplexität |
|
|
|---|--------------|---------------|--------|------------|-------------|
|
|
| 1 | appointment | Nein | 26 | 3 | Hoch |
|
|
| 2 | appointment-exception | Nein | 15 | 2 | Mittel |
|
|
| 3 | appointment-slot | Nein | 16 | 3 | Mittel |
|
|
| 4 | blog-category | Ja | 11 | 2 | Niedrig |
|
|
| 5 | blog-post | Ja | 15 | 2 | Mittel |
|
|
| 6 | contact-message | Nein | 13 | 1 | Niedrig |
|
|
| 7 | expert | Ja | 16 | 2 | Mittel |
|
|
| 8 | faq | Ja | 17 | 5 | Mittel |
|
|
| 9 | faq-category | Nein | 8 | 2 | Niedrig |
|
|
| 10 | faq-feedback | Nein | 15 | 3 | Niedrig |
|
|
| 11 | legal-page | Ja | 7 | 0 | Niedrig |
|
|
| 12 | news-item | Ja | 15 | 2 | Mittel |
|
|
| 13 | page | Ja | 5 | 1 | Hoch (Dynamic Zone) |
|
|
| 14 | service | Ja | 14 | 2 | Mittel |
|
|
| 15 | site-configuration | Ja | 25 | 5 | Hoch |
|
|
| 16 | waiting-list | Nein | 20 | 3 | Mittel |
|
|
|
|
---
|
|
|
|
### 2.2 Detaillierte Content-Type Definitionen
|
|
|
|
#### APPOINTMENT (Terminbuchungen)
|
|
|
|
```
|
|
Felder:
|
|
├── appointmentId (UUID, unique)
|
|
├── patientName (string, required, max 100)
|
|
├── patientEmail (email, required)
|
|
├── patientPhone (string, required, max 30)
|
|
├── patientDateOfBirth (date)
|
|
├── patientInsurance (string, max 100)
|
|
├── appointmentDate (date, required)
|
|
├── appointmentTime (time, required)
|
|
├── duration (integer, 15-240, default 30)
|
|
├── appointmentType (enum: consultation, examination, treatment, followup, vaccination, other)
|
|
├── status (enum: requested, confirmed, cancelled, completed, no-show)
|
|
├── confirmationToken (string, unique, private)
|
|
├── cancellationToken (string, unique, private)
|
|
├── cancellationReason (text, max 500)
|
|
├── notes (text, max 1000)
|
|
├── internalNotes (text, private)
|
|
├── reminderSent (boolean, private)
|
|
├── confirmationSent (boolean, private)
|
|
├── patientConsent (boolean, required)
|
|
├── requestedAt (datetime, private)
|
|
├── confirmedAt (datetime, private)
|
|
├── cancelledAt (datetime, private)
|
|
├── completedAt (datetime, private)
|
|
├── ipAddress (string, private)
|
|
└── userAgent (string, private)
|
|
|
|
Relationen:
|
|
├── expert → Expert (manyToOne)
|
|
├── service → Service (manyToOne)
|
|
└── site → Site-Configuration (manyToOne)
|
|
```
|
|
|
|
#### APPOINTMENT-EXCEPTION (Ausnahmen/Urlaub)
|
|
|
|
```
|
|
Felder:
|
|
├── type (enum: holiday, vacation, sick, conference, training, blocked, other)
|
|
├── startDate (datetime, required)
|
|
├── endDate (datetime, required)
|
|
├── allDay (boolean, default false)
|
|
├── reason (string, required, max 200)
|
|
├── description (text, max 500)
|
|
├── affectsAllSites (boolean, default false)
|
|
├── recurring (boolean, default false)
|
|
├── recurringPattern (enum: daily, weekly, monthly, yearly)
|
|
├── recurringEndDate (date)
|
|
├── isActive (boolean, default true)
|
|
├── notifyPatients (boolean, default true)
|
|
└── color (string, hex format)
|
|
|
|
Relationen:
|
|
├── expert → Expert (manyToOne, required)
|
|
└── site → Site-Configuration (manyToOne)
|
|
```
|
|
|
|
#### APPOINTMENT-SLOT (Verfügbare Zeitslots)
|
|
|
|
```
|
|
Felder:
|
|
├── dayOfWeek (enum: Mo-So, required)
|
|
├── startTime (time, required)
|
|
├── endTime (time, required)
|
|
├── slotDuration (integer, 15-240, default 30)
|
|
├── maxAppointments (integer, 1-10, default 1)
|
|
├── isActive (boolean, default true)
|
|
├── validFrom (date)
|
|
├── validUntil (date)
|
|
├── breakAfter (integer, 0-60, default 0)
|
|
├── bufferBefore (integer, 0-60, default 0)
|
|
├── bufferAfter (integer, 0-60, default 0)
|
|
├── color (string, hex)
|
|
└── notes (text, max 500)
|
|
|
|
Relationen:
|
|
├── expert → Expert (manyToOne, required)
|
|
├── site → Site-Configuration (manyToOne, required)
|
|
└── services ↔ Service (manyToMany)
|
|
```
|
|
|
|
#### BLOG-CATEGORY
|
|
|
|
```
|
|
Felder:
|
|
├── name (string, required, unique, max 100)
|
|
├── slug (uid, required, targetField: name)
|
|
├── description (text, max 500)
|
|
├── color (string, hex, default #0070f3)
|
|
├── icon (string, max 50)
|
|
├── order (integer, default 0)
|
|
├── isActive (boolean, default true)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── blogPosts ← Blog-Post (oneToMany)
|
|
├── parent → Blog-Category (manyToOne, self-referential)
|
|
└── children ← Blog-Category (oneToMany, self-referential)
|
|
```
|
|
|
|
#### BLOG-POST
|
|
|
|
```
|
|
Felder:
|
|
├── title (string, required, max 200)
|
|
├── slug (uid, required, targetField: title)
|
|
├── excerpt (text, required, max 300)
|
|
├── content (richtext, required)
|
|
├── featuredImage (media, images only)
|
|
├── author (component: blog.author)
|
|
├── tags (json)
|
|
├── publishedAt (datetime)
|
|
├── readingTime (integer, min 1)
|
|
├── featured (boolean, default false)
|
|
├── relatedPosts (json)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── category → Blog-Category (manyToOne)
|
|
└── sites ↔ Site-Configuration (manyToMany)
|
|
```
|
|
|
|
#### CONTACT-MESSAGE
|
|
|
|
```
|
|
Felder:
|
|
├── messageId (UUID, unique)
|
|
├── name (string, required, max 100)
|
|
├── email (email, required)
|
|
├── phone (string, max 30)
|
|
├── subject (string, required, max 200)
|
|
├── message (text, required, max 5000)
|
|
├── status (enum: new, read, replied, spam)
|
|
├── consentGiven (boolean, required)
|
|
├── submittedAt (datetime, private)
|
|
├── ipAddress (string, private)
|
|
└── userAgent (string, private)
|
|
|
|
Relationen:
|
|
└── site → Site-Configuration (manyToOne)
|
|
```
|
|
|
|
#### EXPERT (Ärzte/Spezialisten)
|
|
|
|
```
|
|
Felder:
|
|
├── expertId (UUID, unique)
|
|
├── firstName (string, required)
|
|
├── lastName (string, required)
|
|
├── slug (uid, required, targetField: lastName)
|
|
├── title (string)
|
|
├── specialties (json)
|
|
├── bio (richtext)
|
|
├── image (media, images only)
|
|
├── qualifications (component: expert.qualification, repeatable)
|
|
├── availability (component: expert.availability)
|
|
├── contactInfo (json)
|
|
├── isActive (boolean, default true)
|
|
├── order (integer, default 0)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── services ↔ Service (manyToMany)
|
|
└── sites ↔ Site-Configuration (manyToMany)
|
|
```
|
|
|
|
#### FAQ
|
|
|
|
```
|
|
Felder:
|
|
├── question (string, required, max 300)
|
|
├── slug (uid, required, targetField: question)
|
|
├── answer (richtext, required)
|
|
├── shortAnswer (text, max 500)
|
|
├── tags (json)
|
|
├── priority (enum: low, medium, high, featured)
|
|
├── viewCount (integer, private)
|
|
├── helpfulCount (integer)
|
|
├── notHelpfulCount (integer)
|
|
├── attachments (media, multiple)
|
|
├── videoUrl (string)
|
|
├── searchKeywords (text, private)
|
|
├── lastUpdated (datetime)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── category → FAQ-Category (manyToOne)
|
|
├── relatedFaqs ↔ FAQ (manyToMany, self-referential)
|
|
├── relatedServices ↔ Service (manyToMany)
|
|
├── relatedExperts ↔ Expert (manyToMany)
|
|
└── sites ↔ Site-Configuration (manyToMany)
|
|
```
|
|
|
|
#### FAQ-CATEGORY
|
|
|
|
```
|
|
Felder:
|
|
├── name (string, required, unique, max 100)
|
|
├── slug (uid, required, targetField: name)
|
|
├── description (text, max 300)
|
|
├── icon (string, default "question-circle")
|
|
├── color (string, hex, default #3B82F6)
|
|
└── order (integer, default 0)
|
|
|
|
Relationen:
|
|
├── faqs ← FAQ (oneToMany)
|
|
└── sites ↔ Site-Configuration (manyToMany)
|
|
```
|
|
|
|
#### FAQ-FEEDBACK
|
|
|
|
```
|
|
Felder:
|
|
├── isHelpful (boolean, required)
|
|
├── comment (text, max 1000)
|
|
├── additionalFeedback (enum: too_technical, not_detailed_enough, outdated, confusing, perfect, needs_examples)
|
|
├── userEmail (email)
|
|
├── ipAddress (string, private)
|
|
├── userAgent (string, private)
|
|
├── sessionId (string, private)
|
|
├── followUpRequested (boolean, default false)
|
|
├── resolved (boolean, default false)
|
|
├── resolvedAt (datetime)
|
|
└── internalNotes (text, private)
|
|
|
|
Relationen:
|
|
├── faq → FAQ (manyToOne, required)
|
|
├── site → Site-Configuration (manyToOne)
|
|
└── resolvedBy → Admin-User (manyToOne)
|
|
```
|
|
|
|
#### LEGAL-PAGE
|
|
|
|
```
|
|
Felder:
|
|
├── type (enum: impressum, datenschutz, agb, cookie-policy, required)
|
|
├── content (richtext, required)
|
|
├── country (enum: global, de, at, ch, nl, es, uk)
|
|
├── language (enum: de, en, nl, es, fr, it)
|
|
├── validFrom (date)
|
|
├── validUntil (date)
|
|
└── version (string)
|
|
|
|
Relationen: Keine
|
|
```
|
|
|
|
#### NEWS-ITEM
|
|
|
|
```
|
|
Felder:
|
|
├── title (string, required, max 150)
|
|
├── slug (uid, required, targetField: title)
|
|
├── summary (text, required, max 250)
|
|
├── content (richtext, required)
|
|
├── featuredImage (media, images only)
|
|
├── newsType (enum: announcement, update, event, press_release, medical_news)
|
|
├── priority (enum: normal, important, urgent)
|
|
├── publishDate (datetime, required)
|
|
├── expiryDate (datetime)
|
|
├── author (component: blog.author)
|
|
├── tags (json)
|
|
├── attachments (media, multiple)
|
|
├── externalLink (string)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── sites ↔ Site-Configuration (manyToMany)
|
|
└── relatedServices ↔ Service (manyToMany)
|
|
```
|
|
|
|
#### PAGE (Dynamischer Page Builder)
|
|
|
|
```
|
|
Felder:
|
|
├── title (string, required)
|
|
├── slug (uid, required, targetField: title)
|
|
├── isSharedContent (boolean, default false)
|
|
├── sections (dynamiczone) ← KRITISCH FÜR MIGRATION
|
|
└── seo (component: shared.seo)
|
|
|
|
Dynamic Zone Components (11):
|
|
├── sections.hero
|
|
├── sections.text-block
|
|
├── sections.cta-banner
|
|
├── sections.services-grid
|
|
├── sections.team-grid
|
|
├── sections.testimonials
|
|
├── sections.expert-grid
|
|
├── sections.faq
|
|
├── sections.contact-form
|
|
├── sections.blog-list
|
|
└── sections.faq-reference
|
|
|
|
Relationen:
|
|
└── sites ↔ Site-Configuration (manyToMany)
|
|
```
|
|
|
|
#### SERVICE (Medizinische Dienstleistungen)
|
|
|
|
```
|
|
Felder:
|
|
├── name (string, required)
|
|
├── slug (uid, required, targetField: name)
|
|
├── description (richtext, required)
|
|
├── shortDescription (text, max 300)
|
|
├── icon (media, images only)
|
|
├── featuredImage (media, images only)
|
|
├── price (component: service.pricing)
|
|
├── features (component: service.feature, repeatable)
|
|
├── duration (string)
|
|
├── category (enum: diagnostic, therapeutic, preventive, surgical, emergency, consultation)
|
|
├── isActive (boolean, default true)
|
|
├── order (integer, default 0)
|
|
└── seo (component: shared.seo)
|
|
|
|
Relationen:
|
|
├── sites ↔ Site-Configuration (manyToMany)
|
|
└── experts ↔ Expert (manyToMany)
|
|
```
|
|
|
|
#### SITE-CONFIGURATION (Zentrale Domain-Konfiguration)
|
|
|
|
```
|
|
Felder:
|
|
├── siteIdentifier (string, required, unique)
|
|
├── domain (string, required)
|
|
├── siteName (string, required, max 100)
|
|
├── tagline (string, max 200)
|
|
├── logo (media, images only)
|
|
├── favicon (media, images only)
|
|
├── aliases (json)
|
|
├── brand (enum: complexcare, zweitmeinung, portal, required)
|
|
├── specialty (enum: general, intensiv, kardiologie, onkologie, chirurgie, radiologie, nephrologie, orthopaedie, polypharmazie, pflege)
|
|
├── locales (json, default: de-DE)
|
|
├── navigation (json)
|
|
├── footer (json)
|
|
├── theme (component: site.theme)
|
|
├── contact (component: site.contact)
|
|
├── features (json)
|
|
├── socialMedia (component: site.social-media)
|
|
├── analytics (component: site.analytics)
|
|
├── seo (component: shared.seo)
|
|
├── portalSettings (component: site.portal-settings)
|
|
├── maintenanceMode (component: site.maintenance-mode)
|
|
├── emailSettings (component: site.email-settings, required)
|
|
└── appointmentSettings (component: appointment.appointment-settings)
|
|
|
|
Relationen:
|
|
├── newsItems ↔ News-Item (manyToMany)
|
|
├── blogPosts ↔ Blog-Post (manyToMany)
|
|
├── services ↔ Service (manyToMany)
|
|
├── experts ↔ Expert (manyToMany)
|
|
└── pages ↔ Page (manyToMany)
|
|
```
|
|
|
|
#### WAITING-LIST
|
|
|
|
```
|
|
Felder:
|
|
├── waitingListId (UUID, unique)
|
|
├── patientName (string, required, max 100)
|
|
├── patientEmail (email, required)
|
|
├── patientPhone (string, required, max 30)
|
|
├── preferredDates (json, default [])
|
|
├── preferredTimeOfDay (enum: morning, afternoon, evening, any)
|
|
├── flexibleDates (boolean, default false)
|
|
├── priority (enum: normal, urgent, emergency)
|
|
├── status (enum: waiting, contacted, appointed, expired, cancelled)
|
|
├── notes (text, max 1000)
|
|
├── internalNotes (text, private)
|
|
├── addedAt (datetime, private)
|
|
├── contactedAt (datetime, private)
|
|
├── appointedAt (datetime, private)
|
|
├── expiresAt (datetime)
|
|
├── contactAttempts (integer, private)
|
|
├── lastContactAttempt (datetime, private)
|
|
├── patientConsent (boolean, required)
|
|
└── ipAddress (string, private)
|
|
|
|
Relationen:
|
|
├── expert → Expert (manyToOne)
|
|
├── service → Service (manyToOne)
|
|
└── site → Site-Configuration (manyToOne)
|
|
```
|
|
|
|
---
|
|
|
|
## 3. Components / Blocks
|
|
|
|
### 3.1 Component-Kategorien Übersicht
|
|
|
|
| Kategorie | Anzahl | Verwendungszweck |
|
|
|-----------|--------|------------------|
|
|
| admin | 1 | Analytics Dashboard |
|
|
| appointment | 5 | Terminverwaltung-Einstellungen |
|
|
| blog | 2 | Blog-Autoren & Kategorien |
|
|
| elements | 1 | Wiederverwendbare UI-Elemente |
|
|
| expert | 2 | Qualifikationen & Verfügbarkeit |
|
|
| layout | 3 | Navigation & Footer |
|
|
| sections | 11 | Page Builder Blöcke |
|
|
| service | 2 | Preise & Features |
|
|
| shared | 3 | SEO & Meta-Daten |
|
|
| site | 12 | Site-Konfiguration |
|
|
|
|
**Gesamt: 43 Components**
|
|
|
|
---
|
|
|
|
### 3.2 Detaillierte Component-Definitionen
|
|
|
|
#### SHARED (Wiederverwendbar in mehreren Content-Types)
|
|
|
|
**shared.seo** (Verwendet in 7 Content-Types)
|
|
```
|
|
├── metaTitle (string, max 60)
|
|
├── metaDescription (text, max 160)
|
|
├── keywords (text)
|
|
├── canonicalURL (string)
|
|
├── structuredData (json)
|
|
├── metaImage (media, images only)
|
|
├── openGraph (component: shared.open-graph)
|
|
└── metaSocial (component: shared.meta-social, repeatable)
|
|
```
|
|
|
|
**shared.open-graph**
|
|
```
|
|
├── type (enum: website, article, profile, book, music, video)
|
|
├── title (string, max 95)
|
|
├── description (text, max 200)
|
|
├── image (media, images only)
|
|
├── url (string)
|
|
├── siteName (string, max 50)
|
|
└── locale (string, default "de_DE")
|
|
```
|
|
|
|
**shared.meta-social**
|
|
```
|
|
├── socialNetwork (enum: Facebook, Twitter, LinkedIn, required)
|
|
├── title (string, max 60)
|
|
├── description (text, max 160)
|
|
└── image (media, images only)
|
|
```
|
|
|
|
---
|
|
|
|
#### ELEMENTS (Basis-Bausteine)
|
|
|
|
**elements.button** (Verwendet in 7 Components)
|
|
```
|
|
├── label (string, required)
|
|
├── url (string)
|
|
├── style (enum: primary, secondary, outline)
|
|
├── icon (string)
|
|
└── openInNewTab (boolean, default false)
|
|
```
|
|
|
|
---
|
|
|
|
#### SECTIONS (Page Builder Blöcke)
|
|
|
|
**sections.hero**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── backgroundImage (media)
|
|
├── alignment (enum: left, center, right)
|
|
└── buttons (component: elements.button, repeatable)
|
|
```
|
|
|
|
**sections.text-block**
|
|
```
|
|
├── content (richtext, required)
|
|
├── layout (enum: full-width, centered, two-columns)
|
|
└── backgroundColor (enum: white, light, dark, primary)
|
|
```
|
|
|
|
**sections.cta-banner**
|
|
```
|
|
├── title (string, required)
|
|
├── description (text)
|
|
├── button (component: elements.button)
|
|
└── backgroundStyle (enum: gradient, solid, image)
|
|
```
|
|
|
|
**sections.services-grid**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── serviceIds (json)
|
|
├── displayMode (enum: all, selected, category)
|
|
├── category (enum: diagnostic, therapeutic, preventive, surgical, emergency, consultation)
|
|
├── columns (integer, 2-4)
|
|
├── showPrices (boolean)
|
|
└── cta (component: elements.button)
|
|
```
|
|
|
|
**sections.team-grid**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── members (component: sections.team-member, repeatable)
|
|
└── columns (integer, 2-4)
|
|
```
|
|
|
|
**sections.team-member**
|
|
```
|
|
├── name (string, required)
|
|
├── role (string, required)
|
|
├── bio (text)
|
|
├── image (media, images only)
|
|
└── socialLinks (json)
|
|
```
|
|
|
|
**sections.testimonials**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
└── testimonials (component: sections.testimonial, repeatable)
|
|
```
|
|
|
|
**sections.testimonial**
|
|
```
|
|
├── content (text, required)
|
|
├── author (string, required)
|
|
├── role (string)
|
|
├── company (string)
|
|
├── rating (integer, 1-5)
|
|
└── image (media, images only)
|
|
```
|
|
|
|
**sections.expert-grid**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── expertIds (json)
|
|
├── displayMode (enum: all, selected, specialty)
|
|
├── specialty (string)
|
|
├── columns (integer, 2-4)
|
|
├── showAvailability (boolean)
|
|
└── cta (component: elements.button)
|
|
```
|
|
|
|
**sections.faq**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
└── questions (component: sections.faq-item, repeatable)
|
|
```
|
|
|
|
**sections.faq-item**
|
|
```
|
|
├── question (string, required)
|
|
└── answer (richtext, required)
|
|
```
|
|
|
|
**sections.faq-reference** (Referenziert zentrales FAQ-System)
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── displayMode (enum: accordion, list, cards, compact)
|
|
├── source (enum: selected, category, featured, popular)
|
|
├── selectedFaqs (json)
|
|
├── categorySlug (string)
|
|
├── limit (integer, 1-50)
|
|
├── showSearch (boolean)
|
|
├── showCategories (boolean)
|
|
└── cta (component: elements.button)
|
|
```
|
|
|
|
**sections.blog-list**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── contentType (enum: blog_posts, news_items, both)
|
|
├── displayMode (enum: grid, list, featured)
|
|
├── itemsToShow (integer, 1-12)
|
|
├── filterByCategory (string)
|
|
├── showPagination (boolean)
|
|
└── cta (component: elements.button)
|
|
```
|
|
|
|
**sections.contact-form**
|
|
```
|
|
├── title (string)
|
|
├── subtitle (text)
|
|
├── formFields (json)
|
|
├── submitButtonText (string)
|
|
├── successMessage (text)
|
|
├── recipientEmail (email)
|
|
└── consentText (text)
|
|
```
|
|
|
|
---
|
|
|
|
#### SITE (Konfiguration)
|
|
|
|
**site.theme**
|
|
```
|
|
├── primaryColor (string, hex, default #2563eb)
|
|
├── secondaryColor (string, hex, default #10b981)
|
|
├── accentColor (string, hex, default #f59e0b)
|
|
├── darkColor (string, hex, default #1f2937)
|
|
├── backgroundColor (string, hex, default #f3f4f6)
|
|
├── fontFamily (string, default "Inter, sans-serif")
|
|
└── customCSS (text)
|
|
```
|
|
|
|
**site.contact**
|
|
```
|
|
├── email (email, required)
|
|
├── phone (string)
|
|
├── fax (string)
|
|
├── address (text)
|
|
├── emergencyHotline (string)
|
|
└── openingHours (json)
|
|
```
|
|
|
|
**site.social-media**
|
|
```
|
|
├── facebook (string, regex validated)
|
|
├── twitter (string, regex validated)
|
|
├── linkedin (string, regex validated)
|
|
├── instagram (string, regex validated)
|
|
├── youtube (string, regex validated)
|
|
├── xing (string, regex validated)
|
|
├── tiktok (string, regex validated)
|
|
├── whatsapp (string)
|
|
└── telegram (string)
|
|
```
|
|
|
|
**site.analytics**
|
|
```
|
|
├── googleAnalyticsId (string, regex: UA-* or G-*)
|
|
├── googleTagManagerId (string, regex: GTM-*)
|
|
├── facebookPixelId (string)
|
|
├── matomo (json)
|
|
├── hotjar (json)
|
|
├── clarity (string)
|
|
├── cookieConsent (json)
|
|
└── customScripts (json)
|
|
```
|
|
|
|
**site.email-settings**
|
|
```
|
|
├── fromEmail (email, required)
|
|
├── fromName (string, required)
|
|
├── replyToEmail (email, required)
|
|
├── notificationEmail (email, required)
|
|
├── ccEmails (json)
|
|
├── emailSignature (richtext)
|
|
├── emailFooter (text)
|
|
├── notificationSettings (json)
|
|
└── emailTemplateStyle (json)
|
|
```
|
|
|
|
**site.maintenance-mode**
|
|
```
|
|
├── enabled (boolean, required)
|
|
├── startDate (datetime)
|
|
├── endDate (datetime)
|
|
├── title (string)
|
|
├── message (richtext, required)
|
|
├── allowedIPs (json, private)
|
|
├── allowedRoles (json)
|
|
├── showProgressBar (boolean)
|
|
├── progressPercentage (integer, 0-100)
|
|
├── contactInfo (json)
|
|
├── customCSS (text)
|
|
└── redirectUrl (string)
|
|
```
|
|
|
|
**site.portal-settings**
|
|
```
|
|
├── isPortal (boolean, required)
|
|
├── aggregatedDomains (component: site.domain-reference, repeatable)
|
|
├── showDomainGrid (boolean)
|
|
├── showLiveStatistics (boolean)
|
|
├── showExpertCarousel (boolean)
|
|
├── enableGlobalSearch (boolean)
|
|
├── showTrustIndicators (boolean)
|
|
├── statisticsConfig (component: site.statistics-config)
|
|
├── quickAccessCategories (json)
|
|
├── featuredExperts (json)
|
|
└── highlightedServices (json)
|
|
```
|
|
|
|
**site.domain-reference**
|
|
```
|
|
├── domain (string, required)
|
|
├── siteIdentifier (string, required)
|
|
├── displayName (string)
|
|
├── category (enum)
|
|
├── order (integer)
|
|
└── isActive (boolean)
|
|
```
|
|
|
|
**site.statistics-config**
|
|
```
|
|
├── showTotalOpinions (boolean)
|
|
├── showActiveExperts (boolean)
|
|
├── showSatisfactionRate (boolean)
|
|
├── showResponseTime (boolean)
|
|
├── refreshInterval (integer)
|
|
├── animateNumbers (boolean)
|
|
└── displayFormat (enum: compact, detailed, minimal)
|
|
```
|
|
|
|
---
|
|
|
|
#### APPOINTMENT (Terminverwaltung)
|
|
|
|
**appointment.appointment-settings**
|
|
```
|
|
├── bookingAdvanceDays (integer, 1-365)
|
|
├── minBookingHours (integer, 0-168)
|
|
├── maxBookingDays (integer, 1-365)
|
|
├── cancellationHours (integer, 0-168)
|
|
├── defaultDuration (integer, 15-240)
|
|
├── workingHours (component: appointment.working-hours)
|
|
├── bookingRules (json)
|
|
├── reminderHours (integer, 0-168)
|
|
├── allowWaitingList (boolean)
|
|
├── requireConfirmation (boolean)
|
|
├── requirePayment (boolean)
|
|
├── autoConfirmHours (integer)
|
|
├── maxAppointmentsPerDay (integer)
|
|
├── bufferBetweenAppointments (integer)
|
|
├── allowOnlineBooking (boolean)
|
|
├── showPrices (boolean)
|
|
├── termsAndConditions (richtext)
|
|
├── emailTemplates (component: appointment.email-templates)
|
|
├── smsEnabled (boolean)
|
|
└── notificationChannels (json)
|
|
```
|
|
|
|
**appointment.working-hours**
|
|
```
|
|
├── monday (component: appointment.day-schedule)
|
|
├── tuesday (component: appointment.day-schedule)
|
|
├── wednesday (component: appointment.day-schedule)
|
|
├── thursday (component: appointment.day-schedule)
|
|
├── friday (component: appointment.day-schedule)
|
|
├── saturday (component: appointment.day-schedule)
|
|
└── sunday (component: appointment.day-schedule)
|
|
```
|
|
|
|
**appointment.day-schedule**
|
|
```
|
|
├── isOpen (boolean)
|
|
├── openTime (time)
|
|
├── closeTime (time)
|
|
└── breaks (component: appointment.break-time, repeatable)
|
|
```
|
|
|
|
**appointment.break-time**
|
|
```
|
|
├── startTime (time, required)
|
|
├── endTime (time, required)
|
|
└── description (string)
|
|
```
|
|
|
|
**appointment.email-templates**
|
|
```
|
|
├── bookingConfirmation (richtext)
|
|
├── appointmentReminder (richtext)
|
|
├── cancellationConfirmation (richtext)
|
|
├── waitingListNotification (richtext)
|
|
├── noShowFollowUp (richtext)
|
|
├── appointmentRescheduled (richtext)
|
|
├── senderName (string)
|
|
├── senderEmail (email, required)
|
|
└── replyToEmail (email)
|
|
```
|
|
|
|
---
|
|
|
|
#### EXPERT
|
|
|
|
**expert.qualification**
|
|
```
|
|
├── title (string, required)
|
|
├── institution (string)
|
|
├── year (integer)
|
|
├── description (text)
|
|
└── type (enum: degree, certification, specialization, award, membership)
|
|
```
|
|
|
|
**expert.availability**
|
|
```
|
|
├── schedule (json)
|
|
├── consultationTypes (json)
|
|
├── locations (json)
|
|
├── bookingUrl (string)
|
|
├── responseTime (string)
|
|
└── languages (json)
|
|
```
|
|
|
|
---
|
|
|
|
#### BLOG
|
|
|
|
**blog.author**
|
|
```
|
|
├── name (string, required)
|
|
├── bio (text, max 500)
|
|
├── avatar (media, images only)
|
|
├── role (string)
|
|
└── socialLinks (json)
|
|
```
|
|
|
|
---
|
|
|
|
#### SERVICE
|
|
|
|
**service.pricing**
|
|
```
|
|
├── basePrice (decimal, required)
|
|
├── currency (string, default EUR)
|
|
├── priceNote (text)
|
|
├── isStartingPrice (boolean)
|
|
└── paymentOptions (json)
|
|
```
|
|
|
|
**service.feature**
|
|
```
|
|
├── title (string, required)
|
|
├── description (text)
|
|
└── icon (string)
|
|
```
|
|
|
|
---
|
|
|
|
## 4. API-Struktur
|
|
|
|
### 4.1 API-Endpoints Übersicht
|
|
|
|
| Modul | Custom Logic | Endpoints | Authentifizierung |
|
|
|-------|--------------|-----------|-------------------|
|
|
| appointment | Ja (14 Methods) | 13 | Nein (Token-basiert) |
|
|
| appointment-exception | Nein | 5 | Nein |
|
|
| appointment-slot | Nein | 5 | Nein |
|
|
| blog-category | Ja (1 Method) | 6 | Nein |
|
|
| blog-post | Ja (2 Methods) | 7 | Optional |
|
|
| contact-message | Ja (4 Methods) | 8 | Nein |
|
|
| expert | Nein | 5 | Optional |
|
|
| faq | Nein | 5 | Nein |
|
|
| faq-category | Nein | 5 | Nein |
|
|
| faq-feedback | Nein | 5 | Nein |
|
|
| legal-page | Nein | 5 | Optional |
|
|
| news-item | Nein | 5 | Optional |
|
|
| page | Nein | 5 | Optional |
|
|
| service | Nein | 5 | Optional |
|
|
| site-configuration | Nein | 5 | Optional |
|
|
| test-email | Ja (2 Methods) | 2 | Nein |
|
|
| waiting-list | Nein | 5 | Nein |
|
|
|
|
---
|
|
|
|
### 4.2 Custom API-Logik (Migration erforderlich)
|
|
|
|
#### Appointment Controller (14 Custom Methods)
|
|
|
|
```typescript
|
|
// Kritische Business Logic für Payload Migration
|
|
|
|
1. getAvailability(expert, service, startDate, endDate)
|
|
- Berechnet verfügbare Zeitslots
|
|
- Berücksichtigt: Slots, Exceptions, bestehende Termine
|
|
- Filtert nach Buchungsfenster (24h-90d)
|
|
|
|
2. bookAppointment(patientData, appointmentDetails)
|
|
- Validiert alle Felder
|
|
- Prüft Slot-Verfügbarkeit
|
|
- Generiert Confirmation/Cancellation Tokens
|
|
- Sendet Bestätigungs-Email
|
|
|
|
3. confirmAppointment(token)
|
|
- Validiert Token
|
|
- Setzt Status auf 'confirmed'
|
|
- Sendet Bestätigungs-Email
|
|
|
|
4. cancelAppointment(token, reason)
|
|
- Prüft Stornierungsrichtlinie (24h)
|
|
- Setzt Status auf 'cancelled'
|
|
- Verarbeitet Warteliste
|
|
- Benachrichtigt nächsten Patienten
|
|
|
|
5. rescheduleAppointment(appointmentId, newSlot)
|
|
- Validiert neuen Slot
|
|
- Aktualisiert Termin
|
|
- Sendet Benachrichtigung
|
|
|
|
6. addToWaitingList(patientData, preferences)
|
|
- Erstellt Wartelisten-Eintrag
|
|
- Sendet Bestätigung
|
|
|
|
7. getCalendarView(expert, view, date)
|
|
- Erstellt Kalender-Übersicht
|
|
- Gruppiert nach Tag/Woche/Monat
|
|
|
|
8. getStatistics(period, expert)
|
|
- Berechnet Statistiken
|
|
- Status-Verteilung, No-Show-Rate, etc.
|
|
|
|
9. sendReminders()
|
|
- Daily Job für morgige Termine
|
|
- Setzt reminderSent Flag
|
|
|
|
10. exportAppointments(format, filters)
|
|
- CSV-Export
|
|
- Gefiltert nach Datum/Expert/Status
|
|
|
|
11-14. Email Services
|
|
- sendConfirmationEmail()
|
|
- sendCancellationEmail()
|
|
- sendReminderEmail()
|
|
- sendWaitingListNotification()
|
|
```
|
|
|
|
#### Contact-Message Controller (4 Custom Methods)
|
|
|
|
```typescript
|
|
1. create(data) - mit Spam-Schutz
|
|
- Honeypot-Feld Prüfung
|
|
- Minimum 3 Sekunden Ausfüllzeit
|
|
- Email-Benachrichtigung an Site-Admin
|
|
|
|
2. markAsRead(id)
|
|
- Setzt Status auf 'read'
|
|
|
|
3. markAsSpam(id)
|
|
- Setzt Status auf 'spam'
|
|
|
|
4. stats()
|
|
- Statistiken der letzten 30 Tage
|
|
```
|
|
|
|
#### Blog-Post Controller (2 Custom Methods)
|
|
|
|
```typescript
|
|
1. findRelated(postId)
|
|
- Findet ähnliche Posts nach Kategorie
|
|
|
|
2. findByCategory(categorySlug, pagination)
|
|
- Filtert Posts nach Kategorie-Slug
|
|
```
|
|
|
|
---
|
|
|
|
### 4.3 Middleware
|
|
|
|
**Contact Rate Limiting** (Optional)
|
|
```typescript
|
|
// /src/middlewares/contact-rate-limit.ts
|
|
{
|
|
limit: 5, // Anfragen
|
|
interval: 60, // Sekunden
|
|
tracking: 'ip+path'
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 5. Plugins & Extensions
|
|
|
|
### 5.1 Installierte Plugins
|
|
|
|
| Plugin | Version | Payload Äquivalent |
|
|
|--------|---------|-------------------|
|
|
| @strapi/plugin-documentation | 5.18.0 | Built-in API Docs |
|
|
| @strapi/plugin-graphql | 5.18.0 | @payloadcms/plugin-graphql |
|
|
| @strapi/plugin-users-permissions | 5.18.0 | Built-in Auth |
|
|
| @strapi/plugin-seo | 2.0.8 | @payloadcms/plugin-seo |
|
|
| @strapi/provider-email-nodemailer | 5.18.0 | Nodemailer via Hooks |
|
|
| @_sh/strapi-plugin-ckeditor | 6.0.2 | Lexical Editor (built-in) |
|
|
| strapi-plugin-navigation | 3.0.16 | @payloadcms/plugin-nested-docs |
|
|
| strapi-plugin-superfields | 5.8.2 | Custom Field Types |
|
|
| strapi-plugin-multi-select | 2.1.1 | Select Field (built-in) |
|
|
| strapi-plugin-country-select | 2.1.0 | Custom Select |
|
|
| strapi-plugin-bold-title-editor | 2.0.0 | Custom RichText Config |
|
|
| strapi-5-sitemap-plugin | 1.0.7 | afterChange Hook |
|
|
| strapi-cache | 1.5.5 | Redis Plugin |
|
|
| strapi-advanced-uuid | 2.1.1 | Custom ID Field |
|
|
| strapi-5-plugin-responsive-backend | 0.0.4 | Built-in Responsive UI |
|
|
| @chartbrew/plugin-strapi | 3.0.0 | External Integration |
|
|
|
|
### 5.2 Plugin-Konfigurationen
|
|
|
|
**CKEditor 5 Toolbar:**
|
|
```
|
|
Heading | Bold, Italic, Underline, Strikethrough |
|
|
BulletedList, NumberedList | Blockquote |
|
|
Table | Code, CodeBlock | Link | Image
|
|
```
|
|
|
|
**Navigation Plugin:**
|
|
```typescript
|
|
{
|
|
contentTypes: ["page", "service", "expert", "blog-post", "faq"],
|
|
allowedLevels: 3
|
|
}
|
|
```
|
|
|
|
**Cache Plugin:**
|
|
```typescript
|
|
{
|
|
type: "mem",
|
|
maxAge: 3600000,
|
|
cacheRoutes: ["/api/services", "/api/experts", "/api/faq", "/api/blog-posts"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Datenbank & Relationen
|
|
|
|
### 6.1 Relationen-Diagramm
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ SITE-CONFIGURATION │
|
|
│ (Zentrale Konfiguration) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
│ │ │ │ │
|
|
▼ ▼ ▼ ▼ ▼
|
|
┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐ ┌────────┐
|
|
│ Expert │ │Service │ │ Page │ │BlogPost│ │NewsItem│
|
|
└────────┘ └────────┘ └────────┘ └────────┘ └────────┘
|
|
│ │ │
|
|
│ │ ▼
|
|
│ │ ┌─────────────┐
|
|
│ │ │BlogCategory │
|
|
│ │ └─────────────┘
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────────────────────────┐
|
|
│ APPOINTMENT │
|
|
│ (Termin-Buchungen) │
|
|
└─────────────────────────────────────────┘
|
|
│
|
|
├──────────────────┐
|
|
▼ ▼
|
|
┌───────────────┐ ┌─────────────────────┐
|
|
│Appointment- │ │ Appointment- │
|
|
│Exception │ │ Slot │
|
|
└───────────────┘ └─────────────────────┘
|
|
|
|
┌─────────────────────────────────────────┐
|
|
│ FAQ SYSTEM │
|
|
└─────────────────────────────────────────┘
|
|
┌────────────┐ ┌────────┐ ┌─────────────┐
|
|
│FAQ-Category│ ──► │ FAQ │ ◄── │FAQ-Feedback │
|
|
└────────────┘ └────────┘ └─────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────┐
|
|
│ Related: Service, │
|
|
│ Expert, FAQ │
|
|
└─────────────────────┘
|
|
|
|
┌─────────────────────────────────────────┐
|
|
│ CONTACT & WAITING │
|
|
└─────────────────────────────────────────┘
|
|
┌────────────────┐ ┌─────────────┐
|
|
│Contact-Message │ │Waiting-List │
|
|
│ → Site │ │→Expert,Svc │
|
|
└────────────────┘ └─────────────┘
|
|
```
|
|
|
|
### 6.2 Bidirektionale Relationen (ManyToMany)
|
|
|
|
| Relation | Seite A | Seite B |
|
|
|----------|---------|---------|
|
|
| 1 | Site-Configuration | Expert |
|
|
| 2 | Site-Configuration | Service |
|
|
| 3 | Site-Configuration | Page |
|
|
| 4 | Site-Configuration | Blog-Post |
|
|
| 5 | Site-Configuration | News-Item |
|
|
| 6 | Site-Configuration | FAQ |
|
|
| 7 | Site-Configuration | FAQ-Category |
|
|
| 8 | Expert | Service |
|
|
| 9 | FAQ | FAQ (self-referential) |
|
|
| 10 | FAQ | Service |
|
|
| 11 | FAQ | Expert |
|
|
| 12 | Blog-Category | Blog-Category (self-referential) |
|
|
|
|
---
|
|
|
|
## 7. Migrationsplan
|
|
|
|
### 7.1 Phase 1: Payload CMS Setup (Woche 1)
|
|
|
|
```bash
|
|
# Installation
|
|
npx create-payload-app@latest zweitmeinu-ng-payload
|
|
cd zweitmeinu-ng-payload
|
|
|
|
# Plugins
|
|
npm install @payloadcms/plugin-graphql
|
|
npm install @payloadcms/plugin-seo
|
|
npm install @payloadcms/plugin-nested-docs
|
|
npm install @payloadcms/plugin-form-builder
|
|
npm install @payloadcms/richtext-lexical
|
|
```
|
|
|
|
### 7.2 Phase 2: Collections erstellen (Woche 2-3)
|
|
|
|
**Priorität 1 - Basis Collections:**
|
|
1. Users (Admin)
|
|
2. Media
|
|
3. Site-Configuration → Sites
|
|
4. Legal-Page → LegalPages
|
|
|
|
**Priorität 2 - Content Collections:**
|
|
5. Expert → Experts
|
|
6. Service → Services
|
|
7. FAQ-Category → FAQCategories
|
|
8. FAQ → FAQs
|
|
9. Blog-Category → BlogCategories
|
|
10. Blog-Post → BlogPosts
|
|
11. News-Item → NewsItems
|
|
|
|
**Priorität 3 - Page Builder:**
|
|
12. Page → Pages (mit Blocks)
|
|
|
|
**Priorität 4 - Appointment System:**
|
|
13. Appointment → Appointments
|
|
14. Appointment-Slot → AppointmentSlots
|
|
15. Appointment-Exception → AppointmentExceptions
|
|
16. Waiting-List → WaitingList
|
|
|
|
**Priorität 5 - Formulare:**
|
|
17. Contact-Message → ContactMessages
|
|
18. FAQ-Feedback → FAQFeedback
|
|
|
|
### 7.3 Phase 3: Blocks erstellen (Woche 3)
|
|
|
|
```typescript
|
|
// /src/blocks/index.ts
|
|
export const blocks = [
|
|
HeroBlock,
|
|
TextBlock,
|
|
CTABanner,
|
|
ServicesGrid,
|
|
TeamGrid,
|
|
Testimonials,
|
|
ExpertGrid,
|
|
FAQSection,
|
|
FAQReference,
|
|
BlogList,
|
|
ContactForm,
|
|
];
|
|
```
|
|
|
|
### 7.4 Phase 4: Custom Endpoints (Woche 4)
|
|
|
|
```typescript
|
|
// /src/endpoints/appointments.ts
|
|
export const appointmentEndpoints = {
|
|
getAvailability,
|
|
bookAppointment,
|
|
confirmAppointment,
|
|
cancelAppointment,
|
|
rescheduleAppointment,
|
|
getCalendarView,
|
|
getStatistics,
|
|
sendReminders,
|
|
exportAppointments,
|
|
};
|
|
```
|
|
|
|
### 7.5 Phase 5: Daten-Migration (Woche 5)
|
|
|
|
```typescript
|
|
// Migration Script Struktur
|
|
const migrationOrder = [
|
|
'site-configuration',
|
|
'expert',
|
|
'service',
|
|
'faq-category',
|
|
'faq',
|
|
'blog-category',
|
|
'blog-post',
|
|
'news-item',
|
|
'legal-page',
|
|
'page',
|
|
'appointment-slot',
|
|
'appointment-exception',
|
|
'appointment',
|
|
'waiting-list',
|
|
'contact-message',
|
|
'faq-feedback',
|
|
];
|
|
```
|
|
|
|
### 7.6 Phase 6: Testing & Go-Live (Woche 6)
|
|
|
|
1. API-Kompatibilitätstests
|
|
2. Frontend-Integration
|
|
3. Performance-Tests
|
|
4. Staging-Deployment
|
|
5. Production-Migration
|
|
|
|
---
|
|
|
|
## 8. Payload CMS Schema-Entwürfe
|
|
|
|
### 8.1 Beispiel: Appointments Collection
|
|
|
|
```typescript
|
|
// /src/collections/Appointments.ts
|
|
import { CollectionConfig } from 'payload/types';
|
|
|
|
export const Appointments: CollectionConfig = {
|
|
slug: 'appointments',
|
|
admin: {
|
|
useAsTitle: 'patientName',
|
|
defaultColumns: ['patientName', 'appointmentDate', 'status', 'expert'],
|
|
group: 'Termine',
|
|
},
|
|
access: {
|
|
read: () => true,
|
|
create: () => true,
|
|
update: ({ req: { user } }) => Boolean(user),
|
|
delete: ({ req: { user } }) => Boolean(user),
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'appointmentId',
|
|
type: 'text',
|
|
unique: true,
|
|
admin: { readOnly: true },
|
|
hooks: {
|
|
beforeChange: [({ value }) => value || crypto.randomUUID()],
|
|
},
|
|
},
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{ name: 'patientName', type: 'text', required: true, maxLength: 100 },
|
|
{ name: 'patientEmail', type: 'email', required: true },
|
|
{ name: 'patientPhone', type: 'text', required: true, maxLength: 30 },
|
|
],
|
|
},
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{ name: 'appointmentDate', type: 'date', required: true },
|
|
{ name: 'appointmentTime', type: 'text', required: true }, // HH:MM format
|
|
{ name: 'duration', type: 'number', min: 15, max: 240, defaultValue: 30 },
|
|
],
|
|
},
|
|
{
|
|
name: 'appointmentType',
|
|
type: 'select',
|
|
required: true,
|
|
options: [
|
|
{ label: 'Beratung', value: 'consultation' },
|
|
{ label: 'Untersuchung', value: 'examination' },
|
|
{ label: 'Behandlung', value: 'treatment' },
|
|
{ label: 'Nachsorge', value: 'followup' },
|
|
{ label: 'Impfung', value: 'vaccination' },
|
|
{ label: 'Sonstiges', value: 'other' },
|
|
],
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'select',
|
|
required: true,
|
|
defaultValue: 'requested',
|
|
options: [
|
|
{ label: 'Angefragt', value: 'requested' },
|
|
{ label: 'Bestätigt', value: 'confirmed' },
|
|
{ label: 'Storniert', value: 'cancelled' },
|
|
{ label: 'Abgeschlossen', value: 'completed' },
|
|
{ label: 'Nicht erschienen', value: 'no-show' },
|
|
],
|
|
},
|
|
{
|
|
name: 'expert',
|
|
type: 'relationship',
|
|
relationTo: 'experts',
|
|
},
|
|
{
|
|
name: 'service',
|
|
type: 'relationship',
|
|
relationTo: 'services',
|
|
},
|
|
{
|
|
name: 'site',
|
|
type: 'relationship',
|
|
relationTo: 'sites',
|
|
},
|
|
{
|
|
name: 'tokens',
|
|
type: 'group',
|
|
admin: { condition: () => false }, // Hidden in admin
|
|
fields: [
|
|
{ name: 'confirmationToken', type: 'text' },
|
|
{ name: 'cancellationToken', type: 'text' },
|
|
],
|
|
},
|
|
{
|
|
name: 'notes',
|
|
type: 'textarea',
|
|
maxLength: 1000,
|
|
},
|
|
{
|
|
name: 'internalNotes',
|
|
type: 'textarea',
|
|
admin: { condition: ({ req: { user } }) => Boolean(user) },
|
|
},
|
|
{
|
|
name: 'patientConsent',
|
|
type: 'checkbox',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'timestamps',
|
|
type: 'group',
|
|
admin: { readOnly: true },
|
|
fields: [
|
|
{ name: 'requestedAt', type: 'date' },
|
|
{ name: 'confirmedAt', type: 'date' },
|
|
{ name: 'cancelledAt', type: 'date' },
|
|
{ name: 'completedAt', type: 'date' },
|
|
],
|
|
},
|
|
],
|
|
hooks: {
|
|
beforeChange: [
|
|
async ({ data, operation }) => {
|
|
if (operation === 'create') {
|
|
data.timestamps = { requestedAt: new Date() };
|
|
data.tokens = {
|
|
confirmationToken: crypto.randomUUID(),
|
|
cancellationToken: crypto.randomUUID(),
|
|
};
|
|
}
|
|
return data;
|
|
},
|
|
],
|
|
afterChange: [
|
|
async ({ doc, operation }) => {
|
|
if (operation === 'create') {
|
|
// Send confirmation email
|
|
await sendAppointmentConfirmationEmail(doc);
|
|
}
|
|
},
|
|
],
|
|
},
|
|
};
|
|
```
|
|
|
|
### 8.2 Beispiel: Page Collection mit Blocks
|
|
|
|
```typescript
|
|
// /src/collections/Pages.ts
|
|
import { CollectionConfig } from 'payload/types';
|
|
import { blocks } from '../blocks';
|
|
|
|
export const Pages: CollectionConfig = {
|
|
slug: 'pages',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
group: 'Content',
|
|
},
|
|
versions: {
|
|
drafts: true,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
},
|
|
{
|
|
name: 'slug',
|
|
type: 'text',
|
|
required: true,
|
|
unique: true,
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
hooks: {
|
|
beforeValidate: [({ value, data }) =>
|
|
value || data?.title?.toLowerCase().replace(/\s+/g, '-')
|
|
],
|
|
},
|
|
},
|
|
{
|
|
name: 'isSharedContent',
|
|
type: 'checkbox',
|
|
defaultValue: false,
|
|
admin: { position: 'sidebar' },
|
|
},
|
|
{
|
|
name: 'sections',
|
|
type: 'blocks',
|
|
blocks,
|
|
},
|
|
{
|
|
name: 'sites',
|
|
type: 'relationship',
|
|
relationTo: 'sites',
|
|
hasMany: true,
|
|
admin: { position: 'sidebar' },
|
|
},
|
|
{
|
|
name: 'seo',
|
|
type: 'group',
|
|
fields: [
|
|
{ name: 'metaTitle', type: 'text', maxLength: 60 },
|
|
{ name: 'metaDescription', type: 'textarea', maxLength: 160 },
|
|
{ name: 'metaImage', type: 'upload', relationTo: 'media' },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
```
|
|
|
|
### 8.3 Beispiel: Hero Block
|
|
|
|
```typescript
|
|
// /src/blocks/Hero.ts
|
|
import { Block } from 'payload/types';
|
|
|
|
export const HeroBlock: Block = {
|
|
slug: 'hero',
|
|
labels: {
|
|
singular: 'Hero',
|
|
plural: 'Heroes',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
},
|
|
{
|
|
name: 'subtitle',
|
|
type: 'textarea',
|
|
},
|
|
{
|
|
name: 'backgroundImage',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
},
|
|
{
|
|
name: 'alignment',
|
|
type: 'select',
|
|
defaultValue: 'center',
|
|
options: [
|
|
{ label: 'Links', value: 'left' },
|
|
{ label: 'Zentriert', value: 'center' },
|
|
{ label: 'Rechts', value: 'right' },
|
|
],
|
|
},
|
|
{
|
|
name: 'buttons',
|
|
type: 'array',
|
|
fields: [
|
|
{ name: 'label', type: 'text', required: true },
|
|
{ name: 'url', type: 'text' },
|
|
{
|
|
name: 'style',
|
|
type: 'select',
|
|
options: ['primary', 'secondary', 'outline'],
|
|
},
|
|
{ name: 'openInNewTab', type: 'checkbox' },
|
|
],
|
|
},
|
|
],
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Anhang: Checkliste für Migration
|
|
|
|
### Content-Types → Collections
|
|
- [ ] appointment → Appointments
|
|
- [ ] appointment-exception → AppointmentExceptions
|
|
- [ ] appointment-slot → AppointmentSlots
|
|
- [ ] blog-category → BlogCategories
|
|
- [ ] blog-post → BlogPosts
|
|
- [ ] contact-message → ContactMessages
|
|
- [ ] expert → Experts
|
|
- [ ] faq → FAQs
|
|
- [ ] faq-category → FAQCategories
|
|
- [ ] faq-feedback → FAQFeedback
|
|
- [ ] legal-page → LegalPages
|
|
- [ ] news-item → NewsItems
|
|
- [ ] page → Pages
|
|
- [ ] service → Services
|
|
- [ ] site-configuration → Sites
|
|
- [ ] waiting-list → WaitingList
|
|
|
|
### Components → Blocks/Fields
|
|
- [ ] shared.seo → SEO Group Field
|
|
- [ ] elements.button → Button Array Field
|
|
- [ ] sections.hero → HeroBlock
|
|
- [ ] sections.text-block → TextBlock
|
|
- [ ] sections.cta-banner → CTABannerBlock
|
|
- [ ] sections.services-grid → ServicesGridBlock
|
|
- [ ] sections.team-grid → TeamGridBlock
|
|
- [ ] sections.testimonials → TestimonialsBlock
|
|
- [ ] sections.expert-grid → ExpertGridBlock
|
|
- [ ] sections.faq → FAQBlock
|
|
- [ ] sections.faq-reference → FAQReferenceBlock
|
|
- [ ] sections.blog-list → BlogListBlock
|
|
- [ ] sections.contact-form → ContactFormBlock
|
|
- [ ] site.theme → ThemeGroup
|
|
- [ ] site.contact → ContactGroup
|
|
- [ ] site.social-media → SocialMediaGroup
|
|
- [ ] site.analytics → AnalyticsGroup
|
|
- [ ] site.email-settings → EmailSettingsGroup
|
|
- [ ] appointment.appointment-settings → AppointmentSettingsGroup
|
|
- [ ] appointment.working-hours → WorkingHoursGroup
|
|
- [ ] appointment.email-templates → EmailTemplatesGroup
|
|
|
|
### Custom API Logic → Hooks/Endpoints
|
|
- [ ] Appointment Availability Calculation
|
|
- [ ] Appointment Booking Flow
|
|
- [ ] Appointment Confirmation (Token-based)
|
|
- [ ] Appointment Cancellation
|
|
- [ ] Waiting List Processing
|
|
- [ ] Email Notifications
|
|
- [ ] Statistics Calculation
|
|
- [ ] CSV Export
|
|
- [ ] Contact Form Spam Protection
|
|
- [ ] Blog Related Posts
|
|
|
|
### Plugins → Payload Äquivalente
|
|
- [ ] GraphQL Plugin
|
|
- [ ] SEO Plugin
|
|
- [ ] Nested Docs (Navigation)
|
|
- [ ] Rich Text Editor Configuration
|
|
- [ ] Caching Strategy
|
|
- [ ] Sitemap Generation
|
|
|
|
---
|
|
|
|
*Dokument generiert am 2026-02-20*
|