cms.c2sgmbh/src/collections/Timelines.ts
Martin Porwoll 5df2139bbd feat: add Workflows Collection for complex process management
Hybrid solution for process visualization:

## Timeline Collection (Simple Processes)
Enhanced with process-specific fields for simple linear flows:
- Step number, duration, responsible person
- Action required indicator (customer/internal/both/automatic)
- Deliverables/documents per step

Ideal for: Onboarding, Bewerbungsprozess, simple customer journeys

## Workflows Collection (Complex Processes)
New dedicated collection for multi-phase workflows with:

**Phases:**
- Named sections with icons, colors, estimated duration
- Responsible person/role assignment
- Phase-level deliverables

**Steps:**
- Multiple types: task, decision, milestone, approval, wait, automatic
- Priority levels: critical, high, normal, low, optional
- Dependencies between steps (blocking, parallel)
- Conditions/branches for decision steps
- Checklists with required/optional items
- Resources (documents, templates, links, tools)
- Outputs per step

**Properties:**
- Workflow types: project, business, approval, onboarding, support, development, marketing
- Complexity levels, iterative flag, parallel phases flag
- Display options: vertical, horizontal, flowchart, kanban, gantt layouts

**API Features:**
- Public endpoint at /api/workflows with tenant isolation
- Filter by type, complexity
- Statistics: phase count, step count, checklist count, step type breakdown

Database: 20 new tables (18 for workflows, 2 for timeline process fields)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-13 10:47:56 +00:00

581 lines
16 KiB
TypeScript

import type { CollectionConfig } from 'payload'
/**
* Timelines Collection
*
* Dedizierte Collection für komplexe Timeline-Ereignisse
* - Unternehmensgeschichte
* - Projektmeilensteine
* - Produkt-Releases
* - Historische Ereignisse
*
* Multi-Tenant-fähig mit flexiblen Kategorisierungen
*/
export const Timelines: CollectionConfig = {
slug: 'timelines',
labels: {
singular: 'Timeline',
plural: 'Timelines',
},
admin: {
useAsTitle: 'name',
group: 'Inhalte',
defaultColumns: ['name', 'type', 'status', 'updatedAt'],
description: 'Chronologische Darstellungen für Unternehmensgeschichte, Meilensteine, etc.',
},
access: {
read: () => true, // Öffentlich lesbar
},
fields: [
// Timeline-Metadaten
{
name: 'name',
type: 'text',
required: true,
label: 'Timeline-Name',
localized: true,
admin: {
description: 'Interner Name zur Identifikation (z.B. "Unternehmensgeschichte")',
},
},
{
name: 'slug',
type: 'text',
required: true,
unique: true,
label: 'Slug',
admin: {
description: 'URL-freundlicher Identifier (z.B. "company-history")',
},
},
{
name: 'description',
type: 'textarea',
label: 'Beschreibung',
localized: true,
admin: {
description: 'Optionale Beschreibung der Timeline',
},
},
{
name: 'type',
type: 'select',
required: true,
defaultValue: 'history',
label: 'Timeline-Typ',
options: [
{ label: 'Unternehmensgeschichte', value: 'history' },
{ label: 'Projektmeilensteine', value: 'milestones' },
{ label: 'Produkt-Releases', value: 'releases' },
{ label: 'Karriere/Lebenslauf', value: 'career' },
{ label: 'Ereignisse', value: 'events' },
{ label: 'Prozess/Ablauf', value: 'process' },
],
},
{
name: 'status',
type: 'select',
required: true,
defaultValue: 'draft',
label: 'Status',
options: [
{ label: 'Entwurf', value: 'draft' },
{ label: 'Veröffentlicht', value: 'published' },
{ label: 'Archiviert', value: 'archived' },
],
admin: {
position: 'sidebar',
},
},
// Display-Optionen
{
name: 'displayOptions',
type: 'group',
label: 'Darstellungsoptionen',
fields: [
{
name: 'layout',
type: 'select',
defaultValue: 'vertical',
label: 'Layout',
options: [
{ label: 'Vertikal (Standard)', value: 'vertical' },
{ label: 'Alternierend (links/rechts)', value: 'alternating' },
{ label: 'Horizontal (Zeitleiste)', value: 'horizontal' },
{ label: 'Kompakt (Liste)', value: 'compact' },
],
},
{
name: 'sortOrder',
type: 'select',
defaultValue: 'desc',
label: 'Sortierung',
options: [
{ label: 'Neueste zuerst', value: 'desc' },
{ label: 'Älteste zuerst', value: 'asc' },
],
},
{
name: 'showConnector',
type: 'checkbox',
defaultValue: true,
label: 'Verbindungslinie anzeigen',
},
{
name: 'showImages',
type: 'checkbox',
defaultValue: true,
label: 'Bilder anzeigen',
},
{
name: 'groupByYear',
type: 'checkbox',
defaultValue: false,
label: 'Nach Jahr gruppieren',
},
{
name: 'markerStyle',
type: 'select',
defaultValue: 'dot',
label: 'Marker-Stil',
options: [
{ label: 'Punkt', value: 'dot' },
{ label: 'Nummer', value: 'number' },
{ label: 'Icon', value: 'icon' },
{ label: 'Datum', value: 'date' },
],
},
{
name: 'colorScheme',
type: 'select',
defaultValue: 'primary',
label: 'Farbschema',
options: [
{ label: 'Primär (Brand)', value: 'primary' },
{ label: 'Sekundär', value: 'secondary' },
{ label: 'Neutral (Grau)', value: 'neutral' },
{ label: 'Bunt (je nach Kategorie)', value: 'colorful' },
],
},
],
},
// Timeline-Einträge
{
name: 'events',
type: 'array',
required: true,
minRows: 1,
label: 'Ereignisse',
admin: {
description: 'Die einzelnen Einträge der Timeline',
initCollapsed: false,
},
fields: [
// Datum-Optionen
{
type: 'row',
fields: [
{
name: 'dateType',
type: 'select',
defaultValue: 'year',
label: 'Datumstyp',
admin: {
width: '30%',
},
options: [
{ label: 'Nur Jahr', value: 'year' },
{ label: 'Monat & Jahr', value: 'monthYear' },
{ label: 'Vollständiges Datum', value: 'fullDate' },
{ label: 'Zeitraum', value: 'range' },
{ label: 'Freitext', value: 'custom' },
],
},
{
name: 'year',
type: 'number',
label: 'Jahr',
admin: {
width: '20%',
condition: (_, siblingData) =>
['year', 'monthYear', 'fullDate'].includes(siblingData?.dateType),
},
min: 1900,
max: 2100,
},
{
name: 'month',
type: 'select',
label: 'Monat',
admin: {
width: '25%',
condition: (_, siblingData) =>
['monthYear', 'fullDate'].includes(siblingData?.dateType),
},
options: [
{ label: 'Januar', value: '1' },
{ label: 'Februar', value: '2' },
{ label: 'März', value: '3' },
{ label: 'April', value: '4' },
{ label: 'Mai', value: '5' },
{ label: 'Juni', value: '6' },
{ label: 'Juli', value: '7' },
{ label: 'August', value: '8' },
{ label: 'September', value: '9' },
{ label: 'Oktober', value: '10' },
{ label: 'November', value: '11' },
{ label: 'Dezember', value: '12' },
],
},
{
name: 'day',
type: 'number',
label: 'Tag',
admin: {
width: '15%',
condition: (_, siblingData) => siblingData?.dateType === 'fullDate',
},
min: 1,
max: 31,
},
],
},
{
type: 'row',
fields: [
{
name: 'endYear',
type: 'number',
label: 'Ende Jahr',
admin: {
width: '25%',
condition: (_, siblingData) => siblingData?.dateType === 'range',
},
min: 1900,
max: 2100,
},
{
name: 'endMonth',
type: 'select',
label: 'Ende Monat',
admin: {
width: '25%',
condition: (_, siblingData) => siblingData?.dateType === 'range',
},
options: [
{ label: 'Januar', value: '1' },
{ label: 'Februar', value: '2' },
{ label: 'März', value: '3' },
{ label: 'April', value: '4' },
{ label: 'Mai', value: '5' },
{ label: 'Juni', value: '6' },
{ label: 'Juli', value: '7' },
{ label: 'August', value: '8' },
{ label: 'September', value: '9' },
{ label: 'Oktober', value: '10' },
{ label: 'November', value: '11' },
{ label: 'Dezember', value: '12' },
],
},
{
name: 'ongoing',
type: 'checkbox',
label: 'Laufend (bis heute)',
admin: {
width: '25%',
condition: (_, siblingData) => siblingData?.dateType === 'range',
},
},
],
},
{
name: 'customDate',
type: 'text',
label: 'Datum (Freitext)',
admin: {
description: 'z.B. "Frühjahr 2024", "Q1 2023", "1990er Jahre"',
condition: (_, siblingData) => siblingData?.dateType === 'custom',
},
},
// Inhalt
{
name: 'title',
type: 'text',
required: true,
label: 'Titel',
localized: true,
},
{
name: 'subtitle',
type: 'text',
label: 'Untertitel',
localized: true,
admin: {
description: 'Optional: z.B. Rolle, Position, Ort',
},
},
{
name: 'description',
type: 'richText',
label: 'Beschreibung',
localized: true,
},
{
name: 'shortDescription',
type: 'textarea',
label: 'Kurzbeschreibung',
localized: true,
admin: {
description: 'Für kompakte Ansichten (max. 200 Zeichen empfohlen)',
},
},
// Medien
{
name: 'image',
type: 'upload',
relationTo: 'media',
label: 'Bild',
},
{
name: 'gallery',
type: 'array',
label: 'Galerie',
admin: {
description: 'Zusätzliche Bilder für dieses Ereignis',
},
fields: [
{
name: 'image',
type: 'upload',
relationTo: 'media',
required: true,
},
{
name: 'caption',
type: 'text',
label: 'Bildunterschrift',
localized: true,
},
],
},
// Kategorisierung
{
name: 'category',
type: 'select',
label: 'Kategorie',
admin: {
description: 'Zur Filterung und farblichen Unterscheidung',
},
options: [
{ label: 'Meilenstein', value: 'milestone' },
{ label: 'Gründung/Start', value: 'founding' },
{ label: 'Produkt', value: 'product' },
{ label: 'Team/Personal', value: 'team' },
{ label: 'Auszeichnung', value: 'award' },
{ label: 'Partnerschaft', value: 'partnership' },
{ label: 'Expansion', value: 'expansion' },
{ label: 'Technologie', value: 'technology' },
{ label: 'Sonstiges', value: 'other' },
],
},
{
name: 'importance',
type: 'select',
defaultValue: 'normal',
label: 'Wichtigkeit',
options: [
{ label: 'Highlight', value: 'highlight' },
{ label: 'Normal', value: 'normal' },
{ label: 'Minor', value: 'minor' },
],
admin: {
description: 'Highlights werden visuell hervorgehoben',
},
},
// Prozess-spezifische Felder (für einfache Prozesse wie Onboarding, Bewerbung)
{
name: 'stepNumber',
type: 'number',
label: 'Schritt-Nummer',
admin: {
description: 'Explizite Nummerierung (optional, sonst wird Reihenfolge verwendet)',
condition: (data) => data?.type === 'process',
},
},
{
name: 'duration',
type: 'text',
label: 'Dauer',
localized: true,
admin: {
description: 'z.B. "2-3 Tage", "1 Woche", "30 Minuten"',
condition: (data) => data?.type === 'process',
},
},
{
name: 'responsible',
type: 'text',
label: 'Verantwortlich',
localized: true,
admin: {
description: 'Person oder Rolle (z.B. "HR-Team", "Projektleiter")',
condition: (data) => data?.type === 'process',
},
},
{
name: 'actionRequired',
type: 'select',
label: 'Aktion erforderlich von',
admin: {
description: 'Wer muss in diesem Schritt aktiv werden?',
condition: (data) => data?.type === 'process',
},
options: [
{ label: 'Kunde/Bewerber', value: 'customer' },
{ label: 'Internes Team', value: 'internal' },
{ label: 'Beide Seiten', value: 'both' },
{ label: 'Automatisch', value: 'automatic' },
],
},
{
name: 'deliverables',
type: 'array',
label: 'Ergebnisse/Dokumente',
admin: {
description: 'Was wird in diesem Schritt erstellt oder benötigt?',
condition: (data) => data?.type === 'process',
},
fields: [
{
name: 'name',
type: 'text',
required: true,
label: 'Name',
localized: true,
},
{
name: 'type',
type: 'select',
defaultValue: 'output',
label: 'Typ',
options: [
{ label: 'Ergebnis (Output)', value: 'output' },
{ label: 'Benötigt (Input)', value: 'input' },
{ label: 'Dokument', value: 'document' },
],
},
],
},
// Icon & Styling
{
name: 'icon',
type: 'text',
label: 'Icon',
admin: {
description: 'Emoji oder Lucide Icon-Name (z.B. "rocket", "award", "users")',
},
},
{
name: 'color',
type: 'text',
label: 'Farbe (optional)',
admin: {
description: 'Überschreibt die Kategorie-Farbe (z.B. "#FF5733" oder "blue")',
},
},
// Links
{
name: 'links',
type: 'array',
label: 'Links',
fields: [
{
name: 'label',
type: 'text',
required: true,
label: 'Link-Text',
localized: true,
},
{
name: 'url',
type: 'text',
required: true,
label: 'URL',
},
{
name: 'type',
type: 'select',
defaultValue: 'internal',
label: 'Link-Typ',
options: [
{ label: 'Intern', value: 'internal' },
{ label: 'Extern', value: 'external' },
{ label: 'Download', value: 'download' },
],
},
],
},
// Zusätzliche Daten
{
name: 'metadata',
type: 'json',
label: 'Zusätzliche Daten (JSON)',
admin: {
description: 'Für spezielle Anwendungsfälle (z.B. Statistiken, Kennzahlen)',
},
},
],
},
// SEO
{
name: 'seo',
type: 'group',
label: 'SEO',
admin: {
position: 'sidebar',
},
fields: [
{
name: 'metaTitle',
type: 'text',
label: 'Meta-Titel',
localized: true,
},
{
name: 'metaDescription',
type: 'textarea',
label: 'Meta-Beschreibung',
localized: true,
},
],
},
],
hooks: {
beforeChange: [
({ data }) => {
// Auto-generate slug from name if not provided
if (data && !data.slug && data.name) {
data.slug = data.name
.toLowerCase()
.replace(/[äöüß]/g, (match: string) => {
const map: Record<string, string> = { ä: 'ae', ö: 'oe', ü: 'ue', ß: 'ss' }
return map[match] || match
})
.replace(/[^a-z0-9]+/g, '-')
.replace(/^-|-$/g, '')
}
return data
},
],
},
}