mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 20:54:11 +00:00
feat: add Services collection and block
- 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>
This commit is contained in:
parent
261a126f08
commit
8868a5be30
10 changed files with 20412 additions and 1 deletions
|
|
@ -50,6 +50,8 @@ Internet → 37.24.237.181 → Caddy (443) → Payload (3000)
|
|||
│ │ ├── PortfolioCategories.ts
|
||||
│ │ ├── FAQs.ts
|
||||
│ │ ├── Team.ts
|
||||
│ │ ├── ServiceCategories.ts
|
||||
│ │ ├── Services.ts
|
||||
│ │ ├── EmailLogs.ts
|
||||
│ │ ├── AuditLogs.ts
|
||||
│ │ └── ...
|
||||
|
|
@ -404,6 +406,8 @@ SELECT * FROM audit_logs ORDER BY created_at DESC LIMIT 10;
|
|||
| Testimonials | testimonials | Kundenbewertungen |
|
||||
| FAQs | faqs | Häufig gestellte Fragen (FAQ) |
|
||||
| Team | team | Team-Mitglieder und Mitarbeiter |
|
||||
| ServiceCategories | service-categories | Kategorien für Leistungen |
|
||||
| Services | services | Leistungen und Dienstleistungen |
|
||||
| NewsletterSubscribers | newsletter-subscribers | Newsletter mit Double Opt-In |
|
||||
| SocialLinks | social-links | Social Media Links |
|
||||
| Forms | forms | Formular-Builder |
|
||||
|
|
|
|||
391
src/blocks/ServicesBlock.ts
Normal file
391
src/blocks/ServicesBlock.ts
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
import type { Block } from 'payload'
|
||||
|
||||
/**
|
||||
* Services Block
|
||||
*
|
||||
* Zeigt Leistungen/Dienstleistungen aus der Services Collection.
|
||||
* Unterstützt verschiedene Layouts und Filteroptionen.
|
||||
*/
|
||||
export const ServicesBlock: Block = {
|
||||
slug: 'services-block',
|
||||
labels: {
|
||||
singular: 'Leistungen',
|
||||
plural: 'Leistungen',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'title',
|
||||
type: 'text',
|
||||
defaultValue: 'Unsere Leistungen',
|
||||
label: 'Überschrift',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'subtitle',
|
||||
type: 'text',
|
||||
label: 'Untertitel',
|
||||
localized: true,
|
||||
},
|
||||
{
|
||||
name: 'introduction',
|
||||
type: 'richText',
|
||||
label: 'Einleitungstext',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Optionaler Text vor der Leistungsübersicht',
|
||||
},
|
||||
},
|
||||
// Auswahl-Modus
|
||||
{
|
||||
name: 'displayMode',
|
||||
type: 'select',
|
||||
defaultValue: 'all',
|
||||
label: 'Auswahl',
|
||||
options: [
|
||||
{ label: 'Alle aktiven Leistungen', value: 'all' },
|
||||
{ label: 'Nur hervorgehobene', value: 'featured' },
|
||||
{ label: 'Nach Kategorie', value: 'category' },
|
||||
{ label: 'Handverlesene Auswahl', value: 'selected' },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'category',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'service-categories' as any,
|
||||
label: 'Kategorie filtern',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.displayMode === 'category',
|
||||
description: 'Zeigt nur Leistungen dieser Kategorie',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'selectedServices',
|
||||
type: 'relationship',
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
relationTo: 'services' as any,
|
||||
hasMany: true,
|
||||
label: 'Leistungen auswählen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.displayMode === 'selected',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
defaultValue: 12,
|
||||
min: 1,
|
||||
max: 50,
|
||||
label: 'Maximale Anzahl',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.displayMode === 'all' ||
|
||||
siblingData?.displayMode === 'featured' ||
|
||||
siblingData?.displayMode === 'category',
|
||||
},
|
||||
},
|
||||
// Layout
|
||||
{
|
||||
name: 'layout',
|
||||
type: 'select',
|
||||
defaultValue: 'grid',
|
||||
label: 'Layout',
|
||||
options: [
|
||||
{ label: 'Grid (Karten)', value: 'grid' },
|
||||
{ label: 'Liste mit Icons', value: 'list' },
|
||||
{ label: 'Tabs (nach Kategorie)', value: 'tabs' },
|
||||
{ label: 'Accordion', value: 'accordion' },
|
||||
{ label: 'Featured + Grid', value: 'featured-grid' },
|
||||
{ label: 'Slider/Karussell', value: 'slider' },
|
||||
{ label: 'Kompakt (nur Titel + Icon)', value: 'compact' },
|
||||
{ label: 'Masonry', value: 'masonry' },
|
||||
],
|
||||
},
|
||||
{
|
||||
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) =>
|
||||
['grid', 'compact', 'masonry', 'featured-grid'].includes(siblingData?.layout),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featuredCount',
|
||||
type: 'number',
|
||||
defaultValue: 1,
|
||||
min: 1,
|
||||
max: 3,
|
||||
label: 'Anzahl Featured',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'featured-grid',
|
||||
description: 'Anzahl der hervorgehobenen Leistungen oben',
|
||||
},
|
||||
},
|
||||
// Tabs-Optionen
|
||||
{
|
||||
name: 'tabsStyle',
|
||||
type: 'select',
|
||||
defaultValue: 'horizontal',
|
||||
label: 'Tabs-Stil',
|
||||
options: [
|
||||
{ label: 'Horizontal', value: 'horizontal' },
|
||||
{ label: 'Vertikal (Sidebar)', value: 'vertical' },
|
||||
{ label: 'Pills', value: 'pills' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'tabs',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showAllTab',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Alle" Tab anzeigen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'tabs',
|
||||
},
|
||||
},
|
||||
// Accordion-Optionen
|
||||
{
|
||||
name: 'expandFirst',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Erste Leistung automatisch öffnen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'accordion',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'allowMultipleOpen',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Mehrere gleichzeitig öffnen erlauben',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'accordion',
|
||||
},
|
||||
},
|
||||
// Slider-Optionen
|
||||
{
|
||||
name: 'autoplay',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Automatisch wechseln',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'slider',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'autoplaySpeed',
|
||||
type: 'number',
|
||||
defaultValue: 5000,
|
||||
min: 2000,
|
||||
max: 15000,
|
||||
label: 'Wechselintervall (ms)',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.layout === 'slider' && siblingData?.autoplay,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slidesPerView',
|
||||
type: 'select',
|
||||
defaultValue: '3',
|
||||
label: 'Sichtbare Slides',
|
||||
options: [
|
||||
{ label: '1', value: '1' },
|
||||
{ label: '2', value: '2' },
|
||||
{ label: '3', value: '3' },
|
||||
{ label: '4', value: '4' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout === 'slider',
|
||||
},
|
||||
},
|
||||
// Anzeigeoptionen
|
||||
{
|
||||
name: 'showImage',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Bild anzeigen',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
!['compact', 'list'].includes(siblingData?.layout),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showIcon',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Icon anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showDescription',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Kurzbeschreibung anzeigen',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.layout !== 'compact',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showCategory',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Kategorie anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showPricing',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Preis anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showFeatures',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Leistungsmerkmale anzeigen',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
['list', 'accordion', 'featured-grid'].includes(siblingData?.layout),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'featuresLimit',
|
||||
type: 'number',
|
||||
defaultValue: 3,
|
||||
min: 1,
|
||||
max: 10,
|
||||
label: 'Max. Anzahl Merkmale',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showFeatures,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'showCTA',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'CTA-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'showNewBadge',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: '"Neu"-Badge anzeigen',
|
||||
},
|
||||
// Gruppierung
|
||||
{
|
||||
name: 'groupByCategory',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'Nach Kategorie gruppieren',
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
siblingData?.displayMode !== 'category' &&
|
||||
!['tabs', 'slider'].includes(siblingData?.layout),
|
||||
description: 'Zeigt Kategorie-Überschriften zwischen den Leistungen',
|
||||
},
|
||||
},
|
||||
// Detail-Link
|
||||
{
|
||||
name: 'linkToDetail',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Zur Detail-Seite verlinken',
|
||||
admin: {
|
||||
description: 'Karten/Einträge verlinken zur Leistungs-Detail-Seite',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'detailLinkText',
|
||||
type: 'text',
|
||||
defaultValue: 'Mehr erfahren',
|
||||
label: 'Detail-Link Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkToDetail,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'servicesBasePath',
|
||||
type: 'text',
|
||||
defaultValue: '/leistungen',
|
||||
label: 'Basis-Pfad für Detail-Seiten',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.linkToDetail,
|
||||
description: 'z.B. "/leistungen" → "/leistungen/intensivpflege"',
|
||||
},
|
||||
},
|
||||
// Styling
|
||||
{
|
||||
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: 'Gefüllt', value: 'filled' },
|
||||
],
|
||||
admin: {
|
||||
condition: (data, siblingData) =>
|
||||
['grid', 'masonry', 'featured-grid', 'slider'].includes(siblingData?.layout),
|
||||
},
|
||||
},
|
||||
{
|
||||
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' },
|
||||
],
|
||||
},
|
||||
// Globaler CTA
|
||||
{
|
||||
type: 'collapsible',
|
||||
label: 'Abschnitt-CTA',
|
||||
admin: {
|
||||
initCollapsed: true,
|
||||
description: 'Optionaler Button am Ende des gesamten Blocks',
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'showSectionCTA',
|
||||
type: 'checkbox',
|
||||
defaultValue: false,
|
||||
label: 'CTA-Button anzeigen',
|
||||
},
|
||||
{
|
||||
name: 'sectionCTAText',
|
||||
type: 'text',
|
||||
defaultValue: 'Alle Leistungen ansehen',
|
||||
label: 'Button-Text',
|
||||
localized: true,
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showSectionCTA,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'sectionCTALink',
|
||||
type: 'text',
|
||||
defaultValue: '/leistungen',
|
||||
label: 'Button-Link',
|
||||
admin: {
|
||||
condition: (data, siblingData) => siblingData?.showSectionCTA,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
|
@ -16,3 +16,4 @@ export { NewsletterBlock } from './NewsletterBlock'
|
|||
export { ProcessStepsBlock } from './ProcessStepsBlock'
|
||||
export { FAQBlock } from './FAQBlock'
|
||||
export { TeamBlock } from './TeamBlock'
|
||||
export { ServicesBlock } from './ServicesBlock'
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import {
|
|||
ProcessStepsBlock,
|
||||
FAQBlock,
|
||||
TeamBlock,
|
||||
ServicesBlock,
|
||||
} from '../blocks'
|
||||
|
||||
export const Pages: CollectionConfig = {
|
||||
|
|
@ -101,6 +102,7 @@ export const Pages: CollectionConfig = {
|
|||
ProcessStepsBlock,
|
||||
FAQBlock,
|
||||
TeamBlock,
|
||||
ServicesBlock,
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
102
src/collections/ServiceCategories.ts
Normal file
102
src/collections/ServiceCategories.ts
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
// src/collections/ServiceCategories.ts
|
||||
|
||||
import type { CollectionConfig } from 'payload'
|
||||
import { authenticatedOnly, tenantScopedPublicRead } from '../lib/tenantAccess'
|
||||
|
||||
/**
|
||||
* ServiceCategories Collection
|
||||
*
|
||||
* Kategorien für Dienstleistungen/Services.
|
||||
* Ermöglicht Gruppierung und Filterung von Services.
|
||||
* Tenant-scoped für Multi-Tenant-Betrieb.
|
||||
*/
|
||||
export const ServiceCategories: CollectionConfig = {
|
||||
slug: 'service-categories',
|
||||
admin: {
|
||||
useAsTitle: 'name',
|
||||
group: 'Content',
|
||||
defaultColumns: ['name', 'slug', 'order', 'isActive'],
|
||||
description: 'Kategorien für Leistungen/Services',
|
||||
},
|
||||
access: {
|
||||
read: tenantScopedPublicRead,
|
||||
create: authenticatedOnly,
|
||||
update: authenticatedOnly,
|
||||
delete: authenticatedOnly,
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
name: 'name',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Name',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'z.B. "Pflege", "Beratung", "Schulung"',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'slug',
|
||||
type: 'text',
|
||||
required: true,
|
||||
label: 'Slug',
|
||||
unique: false,
|
||||
admin: {
|
||||
description: 'URL-freundlicher Name (z.B. "pflege", "beratung")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'description',
|
||||
type: 'textarea',
|
||||
label: 'Beschreibung',
|
||||
localized: true,
|
||||
admin: {
|
||||
description: 'Kurze Beschreibung der Kategorie',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'icon',
|
||||
type: 'text',
|
||||
label: 'Icon',
|
||||
admin: {
|
||||
description: 'Icon-Name (z.B. "heart", "users", "book")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'image',
|
||||
type: 'upload',
|
||||
relationTo: 'media',
|
||||
label: 'Bild',
|
||||
admin: {
|
||||
description: 'Optionales Kategorie-Bild',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'color',
|
||||
type: 'text',
|
||||
label: 'Farbe',
|
||||
admin: {
|
||||
description: 'Akzentfarbe für die Kategorie (z.B. "#3B82F6")',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'isActive',
|
||||
type: 'checkbox',
|
||||
defaultValue: true,
|
||||
label: 'Aktiv',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'order',
|
||||
type: 'number',
|
||||
defaultValue: 0,
|
||||
label: 'Sortierung',
|
||||
admin: {
|
||||
position: 'sidebar',
|
||||
description: 'Niedrigere Zahlen werden zuerst angezeigt',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
488
src/collections/Services.ts
Normal file
488
src/collections/Services.ts
Normal file
|
|
@ -0,0 +1,488 @@
|
|||
// 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',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
19114
src/migrations/20251210_073811_add_services_collections.json
Normal file
19114
src/migrations/20251210_073811_add_services_collections.json
Normal file
File diff suppressed because it is too large
Load diff
297
src/migrations/20251210_073811_add_services_collections.ts
Normal file
297
src/migrations/20251210_073811_add_services_collections.ts
Normal file
|
|
@ -0,0 +1,297 @@
|
|||
import { MigrateUpArgs, MigrateDownArgs, sql } from '@payloadcms/db-postgres'
|
||||
|
||||
export async function up({ db, payload, req }: MigrateUpArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_display_mode" AS ENUM('all', 'featured', 'category', 'selected');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_layout" AS ENUM('grid', 'list', 'tabs', 'accordion', 'featured-grid', 'slider', 'compact', 'masonry');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_columns" AS ENUM('2', '3', '4');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_tabs_style" AS ENUM('horizontal', 'vertical', 'pills');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_slides_per_view" AS ENUM('1', '2', '3', '4');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_card_style" AS ENUM('elevated', 'bordered', 'flat', 'filled');
|
||||
CREATE TYPE "public"."enum_pages_blocks_services_block_background_color" AS ENUM('white', 'light', 'dark', 'accent');
|
||||
CREATE TYPE "public"."enum_services_pricing_type" AS ENUM('on-request', 'fixed', 'from', 'range', 'hourly', 'monthly', 'free');
|
||||
CREATE TYPE "public"."enum_services_cta_style" AS ENUM('primary', 'secondary', 'outline');
|
||||
CREATE TABLE "pages_blocks_services_block" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"_path" text NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"display_mode" "enum_pages_blocks_services_block_display_mode" DEFAULT 'all',
|
||||
"category_id" integer,
|
||||
"limit" numeric DEFAULT 12,
|
||||
"layout" "enum_pages_blocks_services_block_layout" DEFAULT 'grid',
|
||||
"columns" "enum_pages_blocks_services_block_columns" DEFAULT '3',
|
||||
"featured_count" numeric DEFAULT 1,
|
||||
"tabs_style" "enum_pages_blocks_services_block_tabs_style" DEFAULT 'horizontal',
|
||||
"show_all_tab" boolean DEFAULT true,
|
||||
"expand_first" boolean DEFAULT true,
|
||||
"allow_multiple_open" boolean DEFAULT false,
|
||||
"autoplay" boolean DEFAULT true,
|
||||
"autoplay_speed" numeric DEFAULT 5000,
|
||||
"slides_per_view" "enum_pages_blocks_services_block_slides_per_view" DEFAULT '3',
|
||||
"show_image" boolean DEFAULT true,
|
||||
"show_icon" boolean DEFAULT true,
|
||||
"show_description" boolean DEFAULT true,
|
||||
"show_category" boolean DEFAULT false,
|
||||
"show_pricing" boolean DEFAULT true,
|
||||
"show_features" boolean DEFAULT false,
|
||||
"features_limit" numeric DEFAULT 3,
|
||||
"show_c_t_a" boolean DEFAULT true,
|
||||
"show_new_badge" boolean DEFAULT true,
|
||||
"group_by_category" boolean DEFAULT false,
|
||||
"link_to_detail" boolean DEFAULT true,
|
||||
"services_base_path" varchar DEFAULT '/leistungen',
|
||||
"card_style" "enum_pages_blocks_services_block_card_style" DEFAULT 'elevated',
|
||||
"background_color" "enum_pages_blocks_services_block_background_color" DEFAULT 'white',
|
||||
"show_section_c_t_a" boolean DEFAULT false,
|
||||
"section_c_t_a_link" varchar DEFAULT '/leistungen',
|
||||
"block_name" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "pages_blocks_services_block_locales" (
|
||||
"title" varchar DEFAULT 'Unsere Leistungen',
|
||||
"subtitle" varchar,
|
||||
"introduction" jsonb,
|
||||
"detail_link_text" varchar DEFAULT 'Mehr erfahren',
|
||||
"section_c_t_a_text" varchar DEFAULT 'Alle Leistungen ansehen',
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "service_categories" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer,
|
||||
"slug" varchar NOT NULL,
|
||||
"icon" varchar,
|
||||
"image_id" integer,
|
||||
"color" varchar,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"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
|
||||
);
|
||||
|
||||
CREATE TABLE "service_categories_locales" (
|
||||
"name" varchar NOT NULL,
|
||||
"description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services_gallery" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"image_id" integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services_gallery_locales" (
|
||||
"caption" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services_features" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"icon" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "services_features_locales" (
|
||||
"title" varchar NOT NULL,
|
||||
"description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services_detail_sections" (
|
||||
"_order" integer NOT NULL,
|
||||
"_parent_id" integer NOT NULL,
|
||||
"id" varchar PRIMARY KEY NOT NULL,
|
||||
"icon" varchar
|
||||
);
|
||||
|
||||
CREATE TABLE "services_detail_sections_locales" (
|
||||
"title" varchar NOT NULL,
|
||||
"content" jsonb NOT NULL,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" varchar NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"tenant_id" integer,
|
||||
"slug" varchar NOT NULL,
|
||||
"icon" varchar,
|
||||
"icon_image_id" integer,
|
||||
"image_id" integer,
|
||||
"category_id" integer,
|
||||
"pricing_type" "enum_services_pricing_type" DEFAULT 'on-request',
|
||||
"price" numeric,
|
||||
"price_max" numeric,
|
||||
"cta_link" varchar DEFAULT '/kontakt',
|
||||
"cta_style" "enum_services_cta_style" DEFAULT 'primary',
|
||||
"secondary_cta_enabled" boolean DEFAULT false,
|
||||
"secondary_cta_link" varchar,
|
||||
"testimonial_author" varchar,
|
||||
"og_image_id" integer,
|
||||
"is_active" boolean DEFAULT true,
|
||||
"is_featured" boolean DEFAULT false,
|
||||
"is_new" 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
|
||||
);
|
||||
|
||||
CREATE TABLE "services_locales" (
|
||||
"title" varchar NOT NULL,
|
||||
"subtitle" varchar,
|
||||
"short_description" varchar NOT NULL,
|
||||
"description" jsonb,
|
||||
"price_unit" varchar,
|
||||
"price_note" varchar,
|
||||
"pricing_details" jsonb,
|
||||
"cta_text" varchar DEFAULT 'Jetzt anfragen',
|
||||
"secondary_cta_text" varchar,
|
||||
"testimonial_quote" varchar,
|
||||
"meta_title" varchar,
|
||||
"meta_description" varchar,
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"_locale" "_locales" NOT NULL,
|
||||
"_parent_id" integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE "services_rels" (
|
||||
"id" serial PRIMARY KEY NOT NULL,
|
||||
"order" integer,
|
||||
"parent_id" integer NOT NULL,
|
||||
"path" varchar NOT NULL,
|
||||
"services_id" integer,
|
||||
"team_id" integer,
|
||||
"faqs_id" integer
|
||||
);
|
||||
|
||||
ALTER TABLE "pages_rels" ADD COLUMN "services_id" integer;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "service_categories_id" integer;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD COLUMN "services_id" integer;
|
||||
ALTER TABLE "pages_blocks_services_block" ADD CONSTRAINT "pages_blocks_services_block_category_id_service_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."service_categories"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "pages_blocks_services_block" ADD CONSTRAINT "pages_blocks_services_block_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "pages_blocks_services_block_locales" ADD CONSTRAINT "pages_blocks_services_block_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."pages_blocks_services_block"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "service_categories" ADD CONSTRAINT "service_categories_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "service_categories" ADD CONSTRAINT "service_categories_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "service_categories_locales" ADD CONSTRAINT "service_categories_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."service_categories"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_gallery" ADD CONSTRAINT "services_gallery_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services_gallery" ADD CONSTRAINT "services_gallery_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_gallery_locales" ADD CONSTRAINT "services_gallery_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services_gallery"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_features" ADD CONSTRAINT "services_features_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_features_locales" ADD CONSTRAINT "services_features_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services_features"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_detail_sections" ADD CONSTRAINT "services_detail_sections_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_detail_sections_locales" ADD CONSTRAINT "services_detail_sections_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services_detail_sections"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services" ADD CONSTRAINT "services_tenant_id_tenants_id_fk" FOREIGN KEY ("tenant_id") REFERENCES "public"."tenants"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services" ADD CONSTRAINT "services_icon_image_id_media_id_fk" FOREIGN KEY ("icon_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services" ADD CONSTRAINT "services_image_id_media_id_fk" FOREIGN KEY ("image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services" ADD CONSTRAINT "services_category_id_service_categories_id_fk" FOREIGN KEY ("category_id") REFERENCES "public"."service_categories"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services" ADD CONSTRAINT "services_og_image_id_media_id_fk" FOREIGN KEY ("og_image_id") REFERENCES "public"."media"("id") ON DELETE set null ON UPDATE no action;
|
||||
ALTER TABLE "services_locales" ADD CONSTRAINT "services_locales_parent_id_fk" FOREIGN KEY ("_parent_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_rels" ADD CONSTRAINT "services_rels_parent_fk" FOREIGN KEY ("parent_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_rels" ADD CONSTRAINT "services_rels_services_fk" FOREIGN KEY ("services_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_rels" ADD CONSTRAINT "services_rels_team_fk" FOREIGN KEY ("team_id") REFERENCES "public"."team"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "services_rels" ADD CONSTRAINT "services_rels_faqs_fk" FOREIGN KEY ("faqs_id") REFERENCES "public"."faqs"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "pages_blocks_services_block_order_idx" ON "pages_blocks_services_block" USING btree ("_order");
|
||||
CREATE INDEX "pages_blocks_services_block_parent_id_idx" ON "pages_blocks_services_block" USING btree ("_parent_id");
|
||||
CREATE INDEX "pages_blocks_services_block_path_idx" ON "pages_blocks_services_block" USING btree ("_path");
|
||||
CREATE INDEX "pages_blocks_services_block_category_idx" ON "pages_blocks_services_block" USING btree ("category_id");
|
||||
CREATE UNIQUE INDEX "pages_blocks_services_block_locales_locale_parent_id_unique" ON "pages_blocks_services_block_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "service_categories_tenant_idx" ON "service_categories" USING btree ("tenant_id");
|
||||
CREATE INDEX "service_categories_image_idx" ON "service_categories" USING btree ("image_id");
|
||||
CREATE INDEX "service_categories_updated_at_idx" ON "service_categories" USING btree ("updated_at");
|
||||
CREATE INDEX "service_categories_created_at_idx" ON "service_categories" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "service_categories_locales_locale_parent_id_unique" ON "service_categories_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "services_gallery_order_idx" ON "services_gallery" USING btree ("_order");
|
||||
CREATE INDEX "services_gallery_parent_id_idx" ON "services_gallery" USING btree ("_parent_id");
|
||||
CREATE INDEX "services_gallery_image_idx" ON "services_gallery" USING btree ("image_id");
|
||||
CREATE UNIQUE INDEX "services_gallery_locales_locale_parent_id_unique" ON "services_gallery_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "services_features_order_idx" ON "services_features" USING btree ("_order");
|
||||
CREATE INDEX "services_features_parent_id_idx" ON "services_features" USING btree ("_parent_id");
|
||||
CREATE UNIQUE INDEX "services_features_locales_locale_parent_id_unique" ON "services_features_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "services_detail_sections_order_idx" ON "services_detail_sections" USING btree ("_order");
|
||||
CREATE INDEX "services_detail_sections_parent_id_idx" ON "services_detail_sections" USING btree ("_parent_id");
|
||||
CREATE UNIQUE INDEX "services_detail_sections_locales_locale_parent_id_unique" ON "services_detail_sections_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "services_tenant_idx" ON "services" USING btree ("tenant_id");
|
||||
CREATE INDEX "services_icon_image_idx" ON "services" USING btree ("icon_image_id");
|
||||
CREATE INDEX "services_image_idx" ON "services" USING btree ("image_id");
|
||||
CREATE INDEX "services_category_idx" ON "services" USING btree ("category_id");
|
||||
CREATE INDEX "services_og_image_idx" ON "services" USING btree ("og_image_id");
|
||||
CREATE INDEX "services_updated_at_idx" ON "services" USING btree ("updated_at");
|
||||
CREATE INDEX "services_created_at_idx" ON "services" USING btree ("created_at");
|
||||
CREATE UNIQUE INDEX "services_locales_locale_parent_id_unique" ON "services_locales" USING btree ("_locale","_parent_id");
|
||||
CREATE INDEX "services_rels_order_idx" ON "services_rels" USING btree ("order");
|
||||
CREATE INDEX "services_rels_parent_idx" ON "services_rels" USING btree ("parent_id");
|
||||
CREATE INDEX "services_rels_path_idx" ON "services_rels" USING btree ("path");
|
||||
CREATE INDEX "services_rels_services_id_idx" ON "services_rels" USING btree ("services_id");
|
||||
CREATE INDEX "services_rels_team_id_idx" ON "services_rels" USING btree ("team_id");
|
||||
CREATE INDEX "services_rels_faqs_id_idx" ON "services_rels" USING btree ("faqs_id");
|
||||
ALTER TABLE "pages_rels" ADD CONSTRAINT "pages_rels_services_fk" FOREIGN KEY ("services_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_service_categories_fk" FOREIGN KEY ("service_categories_id") REFERENCES "public"."service_categories"("id") ON DELETE cascade ON UPDATE no action;
|
||||
ALTER TABLE "payload_locked_documents_rels" ADD CONSTRAINT "payload_locked_documents_rels_services_fk" FOREIGN KEY ("services_id") REFERENCES "public"."services"("id") ON DELETE cascade ON UPDATE no action;
|
||||
CREATE INDEX "pages_rels_services_id_idx" ON "pages_rels" USING btree ("services_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_service_categories_id_idx" ON "payload_locked_documents_rels" USING btree ("service_categories_id");
|
||||
CREATE INDEX "payload_locked_documents_rels_services_id_idx" ON "payload_locked_documents_rels" USING btree ("services_id");`)
|
||||
}
|
||||
|
||||
export async function down({ db, payload, req }: MigrateDownArgs): Promise<void> {
|
||||
await db.execute(sql`
|
||||
ALTER TABLE "pages_blocks_services_block" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "pages_blocks_services_block_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "service_categories" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "service_categories_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_gallery" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_gallery_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_features" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_features_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_detail_sections" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_detail_sections_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_locales" DISABLE ROW LEVEL SECURITY;
|
||||
ALTER TABLE "services_rels" DISABLE ROW LEVEL SECURITY;
|
||||
DROP TABLE "pages_blocks_services_block" CASCADE;
|
||||
DROP TABLE "pages_blocks_services_block_locales" CASCADE;
|
||||
DROP TABLE "service_categories" CASCADE;
|
||||
DROP TABLE "service_categories_locales" CASCADE;
|
||||
DROP TABLE "services_gallery" CASCADE;
|
||||
DROP TABLE "services_gallery_locales" CASCADE;
|
||||
DROP TABLE "services_features" CASCADE;
|
||||
DROP TABLE "services_features_locales" CASCADE;
|
||||
DROP TABLE "services_detail_sections" CASCADE;
|
||||
DROP TABLE "services_detail_sections_locales" CASCADE;
|
||||
DROP TABLE "services" CASCADE;
|
||||
DROP TABLE "services_locales" CASCADE;
|
||||
DROP TABLE "services_rels" CASCADE;
|
||||
ALTER TABLE "pages_rels" DROP CONSTRAINT "pages_rels_services_fk";
|
||||
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_service_categories_fk";
|
||||
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP CONSTRAINT "payload_locked_documents_rels_services_fk";
|
||||
|
||||
DROP INDEX "pages_rels_services_id_idx";
|
||||
DROP INDEX "payload_locked_documents_rels_service_categories_id_idx";
|
||||
DROP INDEX "payload_locked_documents_rels_services_id_idx";
|
||||
ALTER TABLE "pages_rels" DROP COLUMN "services_id";
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "service_categories_id";
|
||||
ALTER TABLE "payload_locked_documents_rels" DROP COLUMN "services_id";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_display_mode";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_layout";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_columns";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_tabs_style";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_slides_per_view";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_card_style";
|
||||
DROP TYPE "public"."enum_pages_blocks_services_block_background_color";
|
||||
DROP TYPE "public"."enum_services_pricing_type";
|
||||
DROP TYPE "public"."enum_services_cta_style";`)
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import * as migration_20251206_141403_email_logs_collection from './20251206_141
|
|||
import * as migration_20251207_205727_audit_logs_collection from './20251207_205727_audit_logs_collection';
|
||||
import * as migration_20251210_052757_add_faqs_collection from './20251210_052757_add_faqs_collection';
|
||||
import * as migration_20251210_071506_add_team_collection from './20251210_071506_add_team_collection';
|
||||
import * as migration_20251210_073811_add_services_collections from './20251210_073811_add_services_collections';
|
||||
|
||||
export const migrations = [
|
||||
{
|
||||
|
|
@ -46,6 +47,11 @@ export const migrations = [
|
|||
{
|
||||
up: migration_20251210_071506_add_team_collection.up,
|
||||
down: migration_20251210_071506_add_team_collection.down,
|
||||
name: '20251210_071506_add_team_collection'
|
||||
name: '20251210_071506_add_team_collection',
|
||||
},
|
||||
{
|
||||
up: migration_20251210_073811_add_services_collections.up,
|
||||
down: migration_20251210_073811_add_services_collections.down,
|
||||
name: '20251210_073811_add_services_collections'
|
||||
},
|
||||
];
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@ import { SocialLinks } from './collections/SocialLinks'
|
|||
import { Testimonials } from './collections/Testimonials'
|
||||
import { FAQs } from './collections/FAQs'
|
||||
import { Team } from './collections/Team'
|
||||
import { ServiceCategories } from './collections/ServiceCategories'
|
||||
import { Services } from './collections/Services'
|
||||
import { NewsletterSubscribers } from './collections/NewsletterSubscribers'
|
||||
|
||||
// Portfolio Collections
|
||||
|
|
@ -132,6 +134,8 @@ export default buildConfig({
|
|||
Testimonials,
|
||||
FAQs,
|
||||
Team,
|
||||
ServiceCategories,
|
||||
Services,
|
||||
NewsletterSubscribers,
|
||||
// Portfolio
|
||||
PortfolioCategories,
|
||||
|
|
@ -175,6 +179,8 @@ export default buildConfig({
|
|||
testimonials: {},
|
||||
faqs: {},
|
||||
team: {},
|
||||
'service-categories': {},
|
||||
services: {},
|
||||
'newsletter-subscribers': {},
|
||||
// Portfolio Collections
|
||||
'portfolio-categories': {},
|
||||
|
|
|
|||
Loading…
Reference in a new issue