mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
- Add ServiceCategories collection for grouping services - Add Services collection with comprehensive service profiles: - Title, slug, descriptions (short + full) - Icon (text or image) and image gallery - Category relationship for grouping - Features/benefits array - Flexible pricing (on-request default, fixed, hourly, range, etc.) - CTA buttons (primary + secondary) - Related services, team members, and FAQs relationships - Detail page sections with testimonials - SEO fields (meta title, description, OG image) - Status flags (active, featured, new badge) - Add ServicesBlock with 8 layouts: - Grid, List, Tabs, Accordion, Featured+Grid, Slider, Compact, Masonry - Multi-tenant enabled via plugin configuration - Update documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
488 lines
12 KiB
TypeScript
488 lines
12 KiB
TypeScript
// src/collections/Services.ts
|
|
|
|
import type { CollectionConfig } from 'payload'
|
|
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
|
|
|
/**
|
|
* Services Collection
|
|
*
|
|
* Dienstleistungen und Leistungsangebote mit Detail-Seiten.
|
|
* Unterstützt Kategorisierung, Preisangaben und Verknüpfungen.
|
|
* Tenant-scoped für Multi-Tenant-Betrieb.
|
|
*/
|
|
export const Services: CollectionConfig = {
|
|
slug: 'services',
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
group: 'Content',
|
|
defaultColumns: ['title', 'category', 'pricingType', 'order', 'isActive'],
|
|
description: 'Leistungen und Dienstleistungen',
|
|
},
|
|
access: {
|
|
read: tenantScopedPublicRead,
|
|
create: authenticatedOnly,
|
|
update: authenticatedOnly,
|
|
delete: authenticatedOnly,
|
|
},
|
|
fields: [
|
|
// Basis-Informationen
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
label: 'Titel',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Name der Leistung (z.B. "Intensivpflege", "Beratungsgespräch")',
|
|
},
|
|
},
|
|
{
|
|
name: 'slug',
|
|
type: 'text',
|
|
required: true,
|
|
label: 'Slug',
|
|
unique: false,
|
|
admin: {
|
|
description: 'URL-Pfad für Detail-Seite (z.B. "intensivpflege")',
|
|
},
|
|
},
|
|
{
|
|
name: 'subtitle',
|
|
type: 'text',
|
|
label: 'Untertitel',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Optionaler Untertitel oder Slogan',
|
|
},
|
|
},
|
|
{
|
|
name: 'shortDescription',
|
|
type: 'textarea',
|
|
required: true,
|
|
label: 'Kurzbeschreibung',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Kurze Beschreibung für Übersichtsseiten (1-2 Sätze)',
|
|
},
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'richText',
|
|
label: 'Ausführliche Beschreibung',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Detaillierte Beschreibung für die Detail-Seite',
|
|
},
|
|
},
|
|
// Medien
|
|
{
|
|
type: 'row',
|
|
fields: [
|
|
{
|
|
name: 'icon',
|
|
type: 'text',
|
|
label: 'Icon',
|
|
admin: {
|
|
width: '50%',
|
|
description: 'Icon-Name (z.B. "heart", "shield", "clock")',
|
|
},
|
|
},
|
|
{
|
|
name: 'iconImage',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
label: 'Icon als Bild',
|
|
admin: {
|
|
width: '50%',
|
|
description: 'Alternativ: Icon als hochgeladenes Bild',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'image',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
label: 'Hauptbild',
|
|
admin: {
|
|
description: 'Bild für Karten und Header der Detail-Seite',
|
|
},
|
|
},
|
|
{
|
|
name: 'gallery',
|
|
type: 'array',
|
|
label: 'Bildergalerie',
|
|
admin: {
|
|
description: 'Zusätzliche Bilder für die Detail-Seite',
|
|
initCollapsed: true,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'image',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
required: true,
|
|
label: 'Bild',
|
|
},
|
|
{
|
|
name: 'caption',
|
|
type: 'text',
|
|
label: 'Bildunterschrift',
|
|
localized: true,
|
|
},
|
|
],
|
|
},
|
|
// Kategorie
|
|
{
|
|
name: 'category',
|
|
type: 'relationship',
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
relationTo: 'service-categories' as any,
|
|
label: 'Kategorie',
|
|
admin: {
|
|
description: 'Kategorie zur Gruppierung der Leistung',
|
|
},
|
|
},
|
|
// Features/Leistungsmerkmale
|
|
{
|
|
name: 'features',
|
|
type: 'array',
|
|
label: 'Leistungsmerkmale',
|
|
admin: {
|
|
description: 'Vorteile und Merkmale dieser Leistung',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
label: 'Merkmal',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'textarea',
|
|
label: 'Beschreibung',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'icon',
|
|
type: 'text',
|
|
label: 'Icon',
|
|
admin: {
|
|
description: 'Optionales Icon für das Merkmal',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
// Preisgestaltung
|
|
{
|
|
type: 'collapsible',
|
|
label: 'Preisgestaltung',
|
|
admin: {
|
|
initCollapsed: false,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'pricingType',
|
|
type: 'select',
|
|
defaultValue: 'on-request',
|
|
label: 'Preistyp',
|
|
options: [
|
|
{ label: 'Auf Anfrage', value: 'on-request' },
|
|
{ label: 'Festpreis', value: 'fixed' },
|
|
{ label: 'Ab-Preis', value: 'from' },
|
|
{ label: 'Preisspanne', value: 'range' },
|
|
{ label: 'Stundensatz', value: 'hourly' },
|
|
{ label: 'Monatlich', value: 'monthly' },
|
|
{ label: 'Kostenlos', value: 'free' },
|
|
],
|
|
},
|
|
{
|
|
name: 'price',
|
|
type: 'number',
|
|
label: 'Preis (€)',
|
|
admin: {
|
|
condition: (data, siblingData) =>
|
|
['fixed', 'from', 'hourly', 'monthly'].includes(siblingData?.pricingType),
|
|
description: 'Preis in Euro',
|
|
},
|
|
},
|
|
{
|
|
name: 'priceMax',
|
|
type: 'number',
|
|
label: 'Preis bis (€)',
|
|
admin: {
|
|
condition: (data, siblingData) => siblingData?.pricingType === 'range',
|
|
description: 'Maximaler Preis bei Preisspanne',
|
|
},
|
|
},
|
|
{
|
|
name: 'priceUnit',
|
|
type: 'text',
|
|
label: 'Preiseinheit',
|
|
localized: true,
|
|
admin: {
|
|
condition: (data, siblingData) =>
|
|
['fixed', 'from', 'range', 'hourly', 'monthly'].includes(siblingData?.pricingType),
|
|
description: 'z.B. "pro Stunde", "pro Monat", "pro Behandlung"',
|
|
},
|
|
},
|
|
{
|
|
name: 'priceNote',
|
|
type: 'text',
|
|
label: 'Preishinweis',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Zusätzliche Info (z.B. "zzgl. MwSt.", "inkl. Anfahrt")',
|
|
},
|
|
},
|
|
{
|
|
name: 'pricingDetails',
|
|
type: 'richText',
|
|
label: 'Preisdetails',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Ausführliche Preisinformationen für Detail-Seite',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
// Call-to-Action
|
|
{
|
|
type: 'collapsible',
|
|
label: 'Call-to-Action',
|
|
admin: {
|
|
initCollapsed: true,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'ctaText',
|
|
type: 'text',
|
|
defaultValue: 'Jetzt anfragen',
|
|
label: 'Button-Text',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'ctaLink',
|
|
type: 'text',
|
|
defaultValue: '/kontakt',
|
|
label: 'Button-Link',
|
|
admin: {
|
|
description: 'Ziel-URL oder Pfad (z.B. "/kontakt", "#formular")',
|
|
},
|
|
},
|
|
{
|
|
name: 'ctaStyle',
|
|
type: 'select',
|
|
defaultValue: 'primary',
|
|
label: 'Button-Stil',
|
|
options: [
|
|
{ label: 'Primär', value: 'primary' },
|
|
{ label: 'Sekundär', value: 'secondary' },
|
|
{ label: 'Outline', value: 'outline' },
|
|
],
|
|
},
|
|
{
|
|
name: 'secondaryCta',
|
|
type: 'group',
|
|
label: 'Sekundärer Button',
|
|
fields: [
|
|
{
|
|
name: 'enabled',
|
|
type: 'checkbox',
|
|
defaultValue: false,
|
|
label: 'Aktivieren',
|
|
},
|
|
{
|
|
name: 'text',
|
|
type: 'text',
|
|
label: 'Text',
|
|
localized: true,
|
|
admin: {
|
|
condition: (data, siblingData) => siblingData?.enabled,
|
|
},
|
|
},
|
|
{
|
|
name: 'link',
|
|
type: 'text',
|
|
label: 'Link',
|
|
admin: {
|
|
condition: (data, siblingData) => siblingData?.enabled,
|
|
},
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
// Verknüpfungen
|
|
{
|
|
type: 'collapsible',
|
|
label: 'Verknüpfungen',
|
|
admin: {
|
|
initCollapsed: true,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'relatedServices',
|
|
type: 'relationship',
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
relationTo: 'services' as any,
|
|
hasMany: true,
|
|
label: 'Verwandte Leistungen',
|
|
admin: {
|
|
description: 'Andere Leistungen die thematisch passen',
|
|
},
|
|
},
|
|
{
|
|
name: 'teamMembers',
|
|
type: 'relationship',
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
relationTo: 'team' as any,
|
|
hasMany: true,
|
|
label: 'Zuständige Team-Mitglieder',
|
|
admin: {
|
|
description: 'Ansprechpartner für diese Leistung',
|
|
},
|
|
},
|
|
{
|
|
name: 'faqs',
|
|
type: 'relationship',
|
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
relationTo: 'faqs' as any,
|
|
hasMany: true,
|
|
label: 'Häufige Fragen',
|
|
admin: {
|
|
description: 'FAQs zu dieser Leistung',
|
|
},
|
|
},
|
|
],
|
|
},
|
|
// Detail-Seiten-Inhalte
|
|
{
|
|
type: 'collapsible',
|
|
label: 'Detail-Seite',
|
|
admin: {
|
|
initCollapsed: true,
|
|
description: 'Zusätzliche Inhalte für die Detail-Seite',
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'detailSections',
|
|
type: 'array',
|
|
label: 'Zusätzliche Abschnitte',
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
label: 'Überschrift',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'content',
|
|
type: 'richText',
|
|
required: true,
|
|
label: 'Inhalt',
|
|
localized: true,
|
|
},
|
|
{
|
|
name: 'icon',
|
|
type: 'text',
|
|
label: 'Icon',
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: 'testimonialQuote',
|
|
type: 'textarea',
|
|
label: 'Kundenzitat',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Optionales Zitat zur Leistung',
|
|
},
|
|
},
|
|
{
|
|
name: 'testimonialAuthor',
|
|
type: 'text',
|
|
label: 'Zitat-Autor',
|
|
},
|
|
],
|
|
},
|
|
// SEO
|
|
{
|
|
type: 'collapsible',
|
|
label: 'SEO',
|
|
admin: {
|
|
initCollapsed: true,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'metaTitle',
|
|
type: 'text',
|
|
label: 'Meta-Titel',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Titel für Suchmaschinen (falls anders als Titel)',
|
|
},
|
|
},
|
|
{
|
|
name: 'metaDescription',
|
|
type: 'textarea',
|
|
label: 'Meta-Beschreibung',
|
|
localized: true,
|
|
admin: {
|
|
description: 'Beschreibung für Suchmaschinen',
|
|
},
|
|
},
|
|
{
|
|
name: 'ogImage',
|
|
type: 'upload',
|
|
relationTo: 'media',
|
|
label: 'Social Media Bild',
|
|
},
|
|
],
|
|
},
|
|
// Status & Sortierung
|
|
{
|
|
name: 'isActive',
|
|
type: 'checkbox',
|
|
defaultValue: true,
|
|
label: 'Aktiv/Sichtbar',
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Inaktive Leistungen werden nicht angezeigt',
|
|
},
|
|
},
|
|
{
|
|
name: 'isFeatured',
|
|
type: 'checkbox',
|
|
defaultValue: false,
|
|
label: 'Hervorgehoben',
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Für Startseiten-Anzeige oder besondere Hervorhebung',
|
|
},
|
|
},
|
|
{
|
|
name: 'isNew',
|
|
type: 'checkbox',
|
|
defaultValue: false,
|
|
label: 'Neu',
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Zeigt "Neu"-Badge an',
|
|
},
|
|
},
|
|
{
|
|
name: 'order',
|
|
type: 'number',
|
|
defaultValue: 0,
|
|
label: 'Sortierung',
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Niedrigere Zahlen werden zuerst angezeigt',
|
|
},
|
|
},
|
|
],
|
|
}
|