# YouTube Operations Hub v2 – Vollständiges Konzept

## Übersicht der Erweiterungen

### Neue Features:
1. **Script Editor** – Custom Lexical Rich-Text-Editor mit Section-Blöcken
2. **Kalenderansichten** – Content Calendar + Posting Calendar direkt in Payload
3. **Batch-Planung** – Detaillierte Produktions-Batches mit Kapazitätsplanung
4. **Monthly Goals Dashboard** – KPI-Tracking pro Kanal und Monat
5. **Upload Workflow** – Strukturierte Checklisten mit Fortschrittsanzeige

---

## 1. Script Editor – Custom Lexical Blocks

### 1.1 Konzept

Payload 3.x verwendet Lexical als Rich-Text-Editor. Wir erstellen Custom Blocks für die Skript-Struktur:

```
┌─────────────────────────────────────────────────────────────┐
│  📝 Script Editor                                           │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🎬 HOOK [0-10 Sek]                            [Edit]│   │
│  │ "Board-Meeting in 20 Minuten. Der Morgen war       │   │
│  │  Chaos, die Kinder waren anstrengend..."           │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 🏷️ INTRO-IDENT [1-2 Sek]                     [Edit]│   │
│  │ [GRFI Serien-Badge einblenden]                      │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 📖 CONTEXT [45-60 Sek]                        [Edit]│   │
│  │ "Kennst du das? Du wachst auf mit einem Plan..."    │   │
│  │                                                      │   │
│  │ 🎥 B-Roll: Morgenroutine, Meetings                  │   │
│  │ 📝 Overlay: "System schlägt Motivation"             │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │ 📚 TEIL 1: OUTFIT-LOGIK [2-3 Min]             [Edit]│   │
│  │ "Fangen wir mit der Basis an: dem Outfit..."        │   │
│  │                                                      │   │
│  │ 🎥 B-Roll: Outfit zeigen, 3 Kombinationen           │   │
│  │ 📝 Overlay: "3 Kombinationen = 0 Entscheidungen"    │   │
│  └─────────────────────────────────────────────────────┘   │
│                                                             │
│  [+ Section hinzufügen ▾]                                   │
│    • Hook                                                   │
│    • Intro/Ident                                            │
│    • Context                                                │
│    • Content Part                                           │
│    • Summary                                                │
│    • CTA                                                    │
│    • Outro                                                  │
│    • Disclaimer                                             │
└─────────────────────────────────────────────────────────────┘
```

### 1.2 Script Section Block Schema

```typescript
// src/blocks/ScriptSection.ts

import { Block } from 'payload/types'

export const ScriptSectionBlock: Block = {
  slug: 'scriptSection',
  labels: {
    singular: 'Script Section',
    plural: 'Script Sections',
  },
  fields: [
    {
      name: 'sectionType',
      type: 'select',
      required: true,
      options: [
        { label: '🎬 Hook', value: 'hook' },
        { label: '🏷️ Intro/Ident', value: 'intro_ident' },
        { label: '📖 Context', value: 'context' },
        { label: '📚 Content Part', value: 'content_part' },
        { label: '📋 Summary', value: 'summary' },
        { label: '📢 CTA', value: 'cta' },
        { label: '🎬 Outro', value: 'outro' },
        { label: '⚠️ Disclaimer', value: 'disclaimer' },
      ],
      admin: {
        description: 'Art der Script-Section',
      },
    },
    {
      name: 'sectionTitle',
      type: 'text',
      label: 'Section-Titel',
      admin: {
        placeholder: 'z.B. "TEIL 1: OUTFIT-LOGIK"',
        condition: (data, siblingData) => 
          siblingData?.sectionType === 'content_part',
      },
    },
    {
      name: 'duration',
      type: 'text',
      label: 'Dauer',
      admin: {
        placeholder: 'z.B. "2-3 Min" oder "45-60 Sek"',
        width: '25%',
      },
    },
    {
      name: 'spokenText',
      type: 'richText',
      label: 'Gesprochener Text',
      required: true,
      admin: {
        description: 'Der Text, den Caroline spricht',
      },
    },
    {
      name: 'bRollInstructions',
      type: 'array',
      label: 'B-Roll Anweisungen',
      labels: {
        singular: 'B-Roll',
        plural: 'B-Roll Anweisungen',
      },
      fields: [
        {
          name: 'instruction',
          type: 'text',
          required: true,
          admin: {
            placeholder: 'z.B. "Outfit zeigen", "Kleiderschrank-Aufnahme"',
          },
        },
        {
          name: 'timestamp',
          type: 'text',
          admin: {
            placeholder: 'Optional: "bei 0:45"',
            width: '30%',
          },
        },
      ],
    },
    {
      name: 'textOverlays',
      type: 'array',
      label: 'Text-Overlays',
      labels: {
        singular: 'Overlay',
        plural: 'Text-Overlays',
      },
      fields: [
        {
          name: 'text',
          type: 'text',
          required: true,
          admin: {
            placeholder: 'z.B. "3 Kombinationen = 0 Entscheidungen"',
          },
        },
        {
          name: 'style',
          type: 'select',
          defaultValue: 'standard',
          options: [
            { label: 'Standard', value: 'standard' },
            { label: 'Highlight', value: 'highlight' },
            { label: 'Quote', value: 'quote' },
            { label: 'Statistik', value: 'statistic' },
            { label: 'Liste', value: 'list' },
          ],
        },
      ],
    },
    {
      name: 'visualNotes',
      type: 'textarea',
      label: 'Visuelle Notizen',
      admin: {
        placeholder: 'Zusätzliche Anweisungen für Schnitt/Grafik',
        rows: 2,
      },
    },
  ],
}
```

### 1.3 Script Editor Field in YouTube Content

```typescript
// In YouTubeContent.ts

{
  name: 'script',
  type: 'blocks',
  label: 'Script',
  labels: {
    singular: 'Section',
    plural: 'Script Sections',
  },
  blocks: [ScriptSectionBlock],
  admin: {
    description: 'Strukturiertes Video-Script mit Sections',
  },
}
```

### 1.4 Script Templates

```typescript
// src/collections/ScriptTemplates.ts

export const ScriptTemplates: CollectionConfig = {
  slug: 'yt-script-templates',
  labels: {
    singular: 'Script Template',
    plural: 'Script Templates',
  },
  admin: {
    group: 'YouTube',
    useAsTitle: 'name',
    defaultColumns: ['name', 'series', 'format', 'updatedAt'],
  },
  fields: [
    {
      name: 'name',
      type: 'text',
      required: true,
      localized: true,
      // z.B. "GRFI Longform Template", "M2M Short Template"
    },
    {
      name: 'series',
      type: 'text',
      required: true,
      // z.B. "GRFI", "M2M", "SPARK"
    },
    {
      name: 'format',
      type: 'select',
      required: true,
      options: [
        { label: 'Short (45-60s)', value: 'short' },
        { label: 'Longform (8-16 Min)', value: 'longform' },
      ],
    },
    {
      name: 'channel',
      type: 'relationship',
      relationTo: 'youtube-channels',
      required: true,
    },
    {
      name: 'templateSections',
      type: 'blocks',
      blocks: [ScriptSectionBlock],
      // Vorgefüllte Section-Struktur
    },
    {
      name: 'notes',
      type: 'textarea',
      localized: true,
      admin: {
        description: 'Hinweise zur Verwendung dieses Templates',
      },
    },
  ],
}
```

---

## 2. Kalenderansichten in Payload Admin

### 2.1 Custom Admin Views Struktur

```
src/
├── app/(payload)/admin/
│   ├── [[...segments]]/
│   │   └── page.tsx          # Standard Payload Admin
│   └── views/
│       └── youtube/
│           ├── content-calendar/
│           │   └── page.tsx   # Content/Production Calendar
│           ├── posting-calendar/
│           │   └── page.tsx   # Publishing Calendar
│           └── batch-overview/
│               └── page.tsx   # Batch Dashboard
```

### 2.2 Content Calendar View

```typescript
// src/app/(payload)/admin/views/youtube/content-calendar/page.tsx

'use client'

import React, { useState, useEffect } from 'react'
import { useConfig } from '@payloadcms/ui'
import { format, startOfMonth, endOfMonth, eachDayOfInterval, 
         startOfWeek, endOfWeek, isSameMonth, isSameDay } from 'date-fns'
import { de } from 'date-fns/locale'

interface CalendarVideo {
  id: string
  title: string
  format: 'short' | 'longform'
  series: string
  status: string
  productionDate?: string
  scheduledPublishDate?: string
  channel: {
    name: string
    slug: string
  }
}

export default function ContentCalendarView() {
  const [currentMonth, setCurrentMonth] = useState(new Date())
  const [videos, setVideos] = useState<CalendarVideo[]>([])
  const [selectedChannel, setSelectedChannel] = useState<string>('all')
  const [viewType, setViewType] = useState<'production' | 'publishing'>('production')
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetchVideos()
  }, [currentMonth, selectedChannel])

  const fetchVideos = async () => {
    setLoading(true)
    const start = startOfMonth(currentMonth)
    const end = endOfMonth(currentMonth)
    
    const dateField = viewType === 'production' ? 'productionDate' : 'scheduledPublishDate'
    
    let query = `where[${dateField}][greater_than_equal]=${start.toISOString()}&where[${dateField}][less_than_equal]=${end.toISOString()}`
    
    if (selectedChannel !== 'all') {
      query += `&where[channel][equals]=${selectedChannel}`
    }
    
    const res = await fetch(`/api/youtube-content?${query}&depth=1&limit=100`)
    const data = await res.json()
    setVideos(data.docs)
    setLoading(false)
  }

  const renderCalendar = () => {
    const monthStart = startOfMonth(currentMonth)
    const monthEnd = endOfMonth(currentMonth)
    const calendarStart = startOfWeek(monthStart, { locale: de })
    const calendarEnd = endOfWeek(monthEnd, { locale: de })
    
    const days = eachDayOfInterval({ start: calendarStart, end: calendarEnd })
    
    return (
      <div className="calendar-grid">
        {/* Header */}
        <div className="calendar-header">
          {['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So'].map(day => (
            <div key={day} className="calendar-header-cell">{day}</div>
          ))}
        </div>
        
        {/* Days */}
        <div className="calendar-body">
          {days.map(day => {
            const dateField = viewType === 'production' ? 'productionDate' : 'scheduledPublishDate'
            const dayVideos = videos.filter(v => 
              v[dateField] && isSameDay(new Date(v[dateField]), day)
            )
            
            return (
              <div 
                key={day.toISOString()}
                className={`calendar-cell ${!isSameMonth(day, currentMonth) ? 'outside-month' : ''}`}
              >
                <div className="calendar-date">{format(day, 'd')}</div>
                <div className="calendar-videos">
                  {dayVideos.map(video => (
                    <a 
                      key={video.id}
                      href={`/admin/collections/youtube-content/${video.id}`}
                      className={`calendar-video ${video.format} ${video.status}`}
                    >
                      <span className="video-series">{video.series}</span>
                      <span className="video-title">{video.title}</span>
                    </a>
                  ))}
                </div>
              </div>
            )
          })}
        </div>
      </div>
    )
  }

  return (
    <div className="content-calendar-view">
      <header className="calendar-controls">
        <h1>📅 Content Calendar</h1>
        
        <div className="controls-row">
          <div className="month-navigation">
            <button onClick={() => setCurrentMonth(prev => 
              new Date(prev.getFullYear(), prev.getMonth() - 1)
            )}>
              ← Vorheriger
            </button>
            <h2>{format(currentMonth, 'MMMM yyyy', { locale: de })}</h2>
            <button onClick={() => setCurrentMonth(prev => 
              new Date(prev.getFullYear(), prev.getMonth() + 1)
            )}>
              Nächster →
            </button>
          </div>
          
          <div className="filters">
            <select 
              value={viewType} 
              onChange={e => setViewType(e.target.value as any)}
            >
              <option value="production">Produktion</option>
              <option value="publishing">Veröffentlichung</option>
            </select>
            
            <select 
              value={selectedChannel}
              onChange={e => setSelectedChannel(e.target.value)}
            >
              <option value="all">Alle Kanäle</option>
              <option value="blogwoman">BlogWoman</option>
              <option value="corporate-de">Corporate DE</option>
              <option value="business-en">Business EN</option>
            </select>
          </div>
        </div>
        
        {/* Legend */}
        <div className="calendar-legend">
          <span className="legend-item short">Short</span>
          <span className="legend-item longform">Longform</span>
          <span className="legend-item status-idea">Idee</span>
          <span className="legend-item status-production">In Produktion</span>
          <span className="legend-item status-ready">Fertig</span>
          <span className="legend-item status-published">Veröffentlicht</span>
        </div>
      </header>
      
      {loading ? (
        <div className="loading">Lade Kalender...</div>
      ) : (
        renderCalendar()
      )}
    </div>
  )
}
```

### 2.3 Calendar Styles

```css
/* src/app/(payload)/admin/views/youtube/calendar.css */

.content-calendar-view {
  padding: 20px;
  max-width: 1400px;
  margin: 0 auto;
}

.calendar-controls {
  margin-bottom: 24px;
}

.controls-row {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-top: 16px;
}

.month-navigation {
  display: flex;
  align-items: center;
  gap: 16px;
}

.month-navigation button {
  padding: 8px 16px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background: white;
  cursor: pointer;
}

.month-navigation h2 {
  min-width: 200px;
  text-align: center;
}

.filters {
  display: flex;
  gap: 12px;
}

.filters select {
  padding: 8px 12px;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.calendar-legend {
  display: flex;
  gap: 16px;
  margin-top: 16px;
  padding: 12px;
  background: #f5f5f5;
  border-radius: 4px;
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 6px;
  font-size: 12px;
}

.legend-item::before {
  content: '';
  width: 12px;
  height: 12px;
  border-radius: 2px;
}

.legend-item.short::before { background: #3B82F6; }
.legend-item.longform::before { background: #8B5CF6; }
.legend-item.status-idea::before { background: #9CA3AF; }
.legend-item.status-production::before { background: #F59E0B; }
.legend-item.status-ready::before { background: #10B981; }
.legend-item.status-published::before { background: #059669; }

/* Calendar Grid */
.calendar-grid {
  border: 1px solid #ddd;
  border-radius: 8px;
  overflow: hidden;
}

.calendar-header {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
  background: #f5f5f5;
  border-bottom: 1px solid #ddd;
}

.calendar-header-cell {
  padding: 12px;
  text-align: center;
  font-weight: 600;
  font-size: 12px;
  text-transform: uppercase;
}

.calendar-body {
  display: grid;
  grid-template-columns: repeat(7, 1fr);
}

.calendar-cell {
  min-height: 120px;
  border-right: 1px solid #eee;
  border-bottom: 1px solid #eee;
  padding: 8px;
}

.calendar-cell:nth-child(7n) {
  border-right: none;
}

.calendar-cell.outside-month {
  background: #fafafa;
  opacity: 0.5;
}

.calendar-date {
  font-weight: 600;
  font-size: 14px;
  margin-bottom: 8px;
}

.calendar-videos {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.calendar-video {
  display: block;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 11px;
  text-decoration: none;
  color: white;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.calendar-video.short {
  background: #3B82F6;
}

.calendar-video.longform {
  background: #8B5CF6;
}

.calendar-video .video-series {
  font-weight: 600;
  margin-right: 4px;
}

.calendar-video:hover {
  opacity: 0.9;
  transform: scale(1.02);
}

/* Status Colors als Border */
.calendar-video.idea { border-left: 3px solid #9CA3AF; }
.calendar-video.script_draft { border-left: 3px solid #F59E0B; }
.calendar-video.in_production { border-left: 3px solid #F59E0B; }
.calendar-video.ready { border-left: 3px solid #10B981; }
.calendar-video.published { border-left: 3px solid #059669; }
```

### 2.4 Admin Navigation erweitern

```typescript
// payload.config.ts - Admin Navigation

export default buildConfig({
  admin: {
    components: {
      // Custom Navigation mit YouTube Views
      Nav: '/components/CustomNav',
    },
  },
  // ...
})
```

```typescript
// src/components/CustomNav.tsx

'use client'

import { NavGroup, Nav as DefaultNav } from '@payloadcms/ui'
import Link from 'next/link'

export const CustomNav = () => {
  return (
    <nav>
      <DefaultNav />
      
      {/* YouTube Views */}
      <NavGroup label="YouTube Views">
        <Link href="/admin/views/youtube/content-calendar">
          📅 Content Calendar
        </Link>
        <Link href="/admin/views/youtube/posting-calendar">
          📤 Posting Calendar
        </Link>
        <Link href="/admin/views/youtube/batch-overview">
          📦 Batch Overview
        </Link>
        <Link href="/admin/views/youtube/monthly-goals">
          🎯 Monthly Goals
        </Link>
      </NavGroup>
    </nav>
  )
}
```

---

## 3. Detaillierte Batch-Planung

### 3.1 Batch Collection

```typescript
// src/collections/YtBatches.ts

import { CollectionConfig } from 'payload/types'
import { isYouTubeManager, isYouTubeCreatorOrAbove } from '../lib/youtubeAccess'

export const YtBatches: CollectionConfig = {
  slug: 'yt-batches',
  labels: {
    singular: 'Production Batch',
    plural: 'Production Batches',
  },
  admin: {
    group: 'YouTube',
    useAsTitle: 'name',
    defaultColumns: ['name', 'channel', 'status', 'productionStart', 'progress'],
    listSearchableFields: ['name'],
  },
  access: {
    read: isYouTubeCreatorOrAbove,
    create: isYouTubeManager,
    update: isYouTubeCreatorOrAbove,
    delete: isYouTubeManager,
  },
  fields: [
    // === BASIC INFO ===
    {
      type: 'row',
      fields: [
        {
          name: 'name',
          type: 'text',
          required: true,
          admin: {
            placeholder: 'z.B. "Januar Woche 1" oder "Batch 1"',
            width: '50%',
          },
        },
        {
          name: 'channel',
          type: 'relationship',
          relationTo: 'youtube-channels',
          required: true,
          admin: {
            width: '50%',
          },
        },
      ],
    },
    
    // === ZEITRAUM ===
    {
      name: 'productionPeriod',
      type: 'group',
      label: 'Produktionszeitraum',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'start',
              type: 'date',
              required: true,
              label: 'Start',
              admin: {
                width: '50%',
                date: {
                  pickerAppearance: 'dayOnly',
                  displayFormat: 'dd.MM.yyyy',
                },
              },
            },
            {
              name: 'end',
              type: 'date',
              required: true,
              label: 'Ende',
              admin: {
                width: '50%',
                date: {
                  pickerAppearance: 'dayOnly',
                  displayFormat: 'dd.MM.yyyy',
                },
              },
            },
          ],
        },
        {
          name: 'shootDays',
          type: 'array',
          label: 'Drehtage',
          labels: {
            singular: 'Drehtag',
            plural: 'Drehtage',
          },
          fields: [
            {
              type: 'row',
              fields: [
                {
                  name: 'date',
                  type: 'date',
                  required: true,
                  admin: {
                    width: '30%',
                    date: {
                      pickerAppearance: 'dayOnly',
                    },
                  },
                },
                {
                  name: 'location',
                  type: 'select',
                  options: [
                    { label: '🏠 Home Studio', value: 'home' },
                    { label: '🏢 Office', value: 'office' },
                    { label: '🚗 Unterwegs', value: 'mobile' },
                    { label: '📍 Extern', value: 'external' },
                  ],
                  admin: { width: '25%' },
                },
                {
                  name: 'duration',
                  type: 'select',
                  options: [
                    { label: '2 Stunden', value: '2h' },
                    { label: '4 Stunden (Halbtag)', value: '4h' },
                    { label: '8 Stunden (Ganztag)', value: '8h' },
                  ],
                  admin: { width: '25%' },
                },
                {
                  name: 'notes',
                  type: 'text',
                  admin: { 
                    width: '20%',
                    placeholder: 'Notizen',
                  },
                },
              ],
            },
          ],
        },
      ],
    },
    
    // === CONTENT TARGETS ===
    {
      name: 'targets',
      type: 'group',
      label: 'Content-Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'shortsTarget',
              type: 'number',
              label: 'Shorts (Ziel)',
              required: true,
              defaultValue: 7,
              admin: { width: '25%' },
            },
            {
              name: 'longformsTarget',
              type: 'number',
              label: 'Longforms (Ziel)',
              required: true,
              defaultValue: 3,
              admin: { width: '25%' },
            },
            {
              name: 'totalTarget',
              type: 'number',
              label: 'Gesamt (Ziel)',
              admin: {
                width: '25%',
                readOnly: true,
                description: 'Automatisch berechnet',
              },
              hooks: {
                beforeChange: [
                  ({ siblingData }) => {
                    return (siblingData.shortsTarget || 0) + (siblingData.longformsTarget || 0)
                  },
                ],
              },
            },
            {
              name: 'bufferDays',
              type: 'number',
              label: 'Puffer (Tage)',
              defaultValue: 3,
              admin: { 
                width: '25%',
                description: 'Tage zwischen Produktion und Publish',
              },
            },
          ],
        },
      ],
    },
    
    // === SERIEN-VERTEILUNG ===
    {
      name: 'seriesDistribution',
      type: 'array',
      label: 'Serien-Verteilung',
      admin: {
        description: 'Welche Serien in diesem Batch produziert werden',
      },
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'series',
              type: 'text',
              required: true,
              admin: { 
                width: '30%',
                placeholder: 'z.B. GRFI',
              },
            },
            {
              name: 'shortsCount',
              type: 'number',
              label: 'Shorts',
              defaultValue: 0,
              admin: { width: '20%' },
            },
            {
              name: 'longformsCount',
              type: 'number',
              label: 'Longforms',
              defaultValue: 0,
              admin: { width: '20%' },
            },
            {
              name: 'priority',
              type: 'select',
              options: [
                { label: '🔴 Hoch', value: 'high' },
                { label: '🟡 Normal', value: 'normal' },
                { label: '🟢 Niedrig', value: 'low' },
              ],
              defaultValue: 'normal',
              admin: { width: '30%' },
            },
          ],
        },
      ],
    },
    
    // === STATUS & PROGRESS ===
    {
      name: 'status',
      type: 'select',
      required: true,
      defaultValue: 'planning',
      options: [
        { label: '📝 Planung', value: 'planning' },
        { label: '✍️ Scripts', value: 'scripting' },
        { label: '🎬 Produktion', value: 'production' },
        { label: '✂️ Schnitt', value: 'editing' },
        { label: '✅ Review', value: 'review' },
        { label: '📤 Upload-Ready', value: 'ready' },
        { label: '🎉 Veröffentlicht', value: 'published' },
      ],
      admin: {
        position: 'sidebar',
      },
    },
    
    // Virtuelle Felder für Progress (berechnet via Hook)
    {
      name: 'progress',
      type: 'group',
      label: 'Fortschritt',
      admin: {
        position: 'sidebar',
        readOnly: true,
      },
      fields: [
        {
          name: 'shortsCompleted',
          type: 'number',
          label: 'Shorts fertig',
          admin: { readOnly: true },
        },
        {
          name: 'longformsCompleted',
          type: 'number',
          label: 'Longforms fertig',
          admin: { readOnly: true },
        },
        {
          name: 'percentage',
          type: 'number',
          label: 'Gesamt %',
          admin: { readOnly: true },
        },
      ],
    },
    
    // === TEAM & RESOURCES ===
    {
      name: 'team',
      type: 'group',
      label: 'Team',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'producer',
              type: 'relationship',
              relationTo: 'users',
              label: 'Producer',
              admin: { width: '33%' },
            },
            {
              name: 'editor',
              type: 'relationship',
              relationTo: 'users',
              label: 'Editor',
              admin: { width: '33%' },
            },
            {
              name: 'reviewer',
              type: 'relationship',
              relationTo: 'users',
              label: 'Reviewer',
              admin: { width: '33%' },
            },
          ],
        },
      ],
    },
    
    // === NOTES ===
    {
      name: 'notes',
      type: 'textarea',
      label: 'Notizen',
      admin: {
        rows: 4,
      },
    },
  ],
  
  // === HOOKS ===
  hooks: {
    afterRead: [
      // Progress aus verknüpften Videos berechnen
      async ({ doc, req }) => {
        if (!doc?.id) return doc
        
        const videos = await req.payload.find({
          collection: 'youtube-content',
          where: {
            productionBatch: { equals: doc.id },
          },
          limit: 100,
        })
        
        const shorts = videos.docs.filter(v => v.format === 'short')
        const longforms = videos.docs.filter(v => v.format === 'longform')
        
        const completedStatuses = ['approved', 'upload_scheduled', 'published', 'tracked']
        
        const shortsCompleted = shorts.filter(v => 
          completedStatuses.includes(v.status)
        ).length
        
        const longformsCompleted = longforms.filter(v => 
          completedStatuses.includes(v.status)
        ).length
        
        const totalTarget = (doc.targets?.shortsTarget || 0) + (doc.targets?.longformsTarget || 0)
        const totalCompleted = shortsCompleted + longformsCompleted
        const percentage = totalTarget > 0 ? Math.round((totalCompleted / totalTarget) * 100) : 0
        
        return {
          ...doc,
          progress: {
            shortsCompleted,
            longformsCompleted,
            percentage,
          },
        }
      },
    ],
  },
}
```

### 3.2 Batch Overview Dashboard

```typescript
// src/app/(payload)/admin/views/youtube/batch-overview/page.tsx

'use client'

import React, { useState, useEffect } from 'react'
import { format } from 'date-fns'
import { de } from 'date-fns/locale'

interface Batch {
  id: string
  name: string
  channel: { name: string; slug: string }
  status: string
  productionPeriod: {
    start: string
    end: string
  }
  targets: {
    shortsTarget: number
    longformsTarget: number
    totalTarget: number
  }
  progress: {
    shortsCompleted: number
    longformsCompleted: number
    percentage: number
  }
}

export default function BatchOverviewView() {
  const [batches, setBatches] = useState<Batch[]>([])
  const [selectedChannel, setSelectedChannel] = useState('all')
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    fetchBatches()
  }, [selectedChannel])

  const fetchBatches = async () => {
    setLoading(true)
    let query = 'depth=1&limit=50&sort=-productionPeriod.start'
    
    if (selectedChannel !== 'all') {
      query += `&where[channel][equals]=${selectedChannel}`
    }
    
    const res = await fetch(`/api/yt-batches?${query}`)
    const data = await res.json()
    setBatches(data.docs)
    setLoading(false)
  }

  const getStatusColor = (status: string) => {
    const colors: Record<string, string> = {
      planning: '#9CA3AF',
      scripting: '#60A5FA',
      production: '#FBBF24',
      editing: '#F97316',
      review: '#A78BFA',
      ready: '#34D399',
      published: '#10B981',
    }
    return colors[status] || '#9CA3AF'
  }

  const getStatusLabel = (status: string) => {
    const labels: Record<string, string> = {
      planning: '📝 Planung',
      scripting: '✍️ Scripts',
      production: '🎬 Produktion',
      editing: '✂️ Schnitt',
      review: '✅ Review',
      ready: '📤 Ready',
      published: '🎉 Published',
    }
    return labels[status] || status
  }

  return (
    <div className="batch-overview">
      <header>
        <h1>📦 Batch Overview</h1>
        
        <div className="controls">
          <select 
            value={selectedChannel}
            onChange={e => setSelectedChannel(e.target.value)}
          >
            <option value="all">Alle Kanäle</option>
            <option value="blogwoman">BlogWoman</option>
            <option value="corporate-de">Corporate DE</option>
            <option value="business-en">Business EN</option>
          </select>
          
          <a href="/admin/collections/yt-batches/create" className="btn-primary">
            + Neuer Batch
          </a>
        </div>
      </header>

      {loading ? (
        <div className="loading">Lade Batches...</div>
      ) : (
        <div className="batch-grid">
          {batches.map(batch => (
            <a 
              key={batch.id} 
              href={`/admin/collections/yt-batches/${batch.id}`}
              className="batch-card"
            >
              <div className="batch-header">
                <h3>{batch.name}</h3>
                <span 
                  className="status-badge"
                  style={{ backgroundColor: getStatusColor(batch.status) }}
                >
                  {getStatusLabel(batch.status)}
                </span>
              </div>
              
              <div className="batch-channel">
                {batch.channel?.name}
              </div>
              
              <div className="batch-dates">
                📅 {format(new Date(batch.productionPeriod.start), 'dd.MM.', { locale: de })} 
                - {format(new Date(batch.productionPeriod.end), 'dd.MM.yyyy', { locale: de })}
              </div>
              
              <div className="batch-progress">
                <div className="progress-bar">
                  <div 
                    className="progress-fill"
                    style={{ width: `${batch.progress?.percentage || 0}%` }}
                  />
                </div>
                <span className="progress-text">
                  {batch.progress?.percentage || 0}%
                </span>
              </div>
              
              <div className="batch-counts">
                <div className="count">
                  <span className="count-value">
                    {batch.progress?.shortsCompleted || 0}/{batch.targets?.shortsTarget || 0}
                  </span>
                  <span className="count-label">Shorts</span>
                </div>
                <div className="count">
                  <span className="count-value">
                    {batch.progress?.longformsCompleted || 0}/{batch.targets?.longformsTarget || 0}
                  </span>
                  <span className="count-label">Longforms</span>
                </div>
              </div>
            </a>
          ))}
        </div>
      )}
    </div>
  )
}
```

---

## 4. Monthly Goals Collection

```typescript
// src/collections/YtMonthlyGoals.ts

import { CollectionConfig } from 'payload/types'

export const YtMonthlyGoals: CollectionConfig = {
  slug: 'yt-monthly-goals',
  labels: {
    singular: 'Monthly Goal',
    plural: 'Monthly Goals',
  },
  admin: {
    group: 'YouTube',
    useAsTitle: 'displayTitle',
    defaultColumns: ['channel', 'month', 'status', 'updatedAt'],
  },
  fields: [
    {
      name: 'channel',
      type: 'relationship',
      relationTo: 'youtube-channels',
      required: true,
    },
    {
      name: 'month',
      type: 'date',
      required: true,
      admin: {
        date: {
          pickerAppearance: 'monthOnly',
          displayFormat: 'MMMM yyyy',
        },
      },
    },
    {
      name: 'displayTitle',
      type: 'text',
      admin: {
        hidden: true,
      },
      hooks: {
        beforeChange: [
          async ({ siblingData, req }) => {
            if (siblingData.channel && siblingData.month) {
              const channel = await req.payload.findByID({
                collection: 'youtube-channels',
                id: siblingData.channel,
              })
              const monthStr = new Date(siblingData.month).toLocaleDateString('de-DE', {
                month: 'long',
                year: 'numeric',
              })
              return `${channel?.name} - ${monthStr}`
            }
            return 'Neues Monatsziel'
          },
        ],
      },
    },
    
    // === CONTENT GOALS ===
    {
      name: 'contentGoals',
      type: 'group',
      label: 'Content-Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'longformsTarget',
              type: 'number',
              label: 'Longforms',
              defaultValue: 12,
              admin: { width: '25%' },
            },
            {
              name: 'longformsCurrent',
              type: 'number',
              label: 'Aktuell',
              defaultValue: 0,
              admin: { width: '25%' },
            },
            {
              name: 'shortsTarget',
              type: 'number',
              label: 'Shorts',
              defaultValue: 28,
              admin: { width: '25%' },
            },
            {
              name: 'shortsCurrent',
              type: 'number',
              label: 'Aktuell',
              defaultValue: 0,
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },
    
    // === AUDIENCE GOALS ===
    {
      name: 'audienceGoals',
      type: 'group',
      label: 'Audience-Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'subscribersTarget',
              type: 'number',
              label: 'Neue Abos (Ziel)',
              admin: { width: '25%' },
            },
            {
              name: 'subscribersCurrent',
              type: 'number',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
            {
              name: 'viewsTarget',
              type: 'number',
              label: 'Views (Ziel)',
              admin: { width: '25%' },
            },
            {
              name: 'viewsCurrent',
              type: 'number',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },
    
    // === ENGAGEMENT GOALS ===
    {
      name: 'engagementGoals',
      type: 'group',
      label: 'Engagement-Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'avgCtrTarget',
              type: 'text',
              label: 'Ø CTR (Ziel)',
              admin: { 
                width: '25%',
                placeholder: 'z.B. ">4%"',
              },
            },
            {
              name: 'avgCtrCurrent',
              type: 'text',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
            {
              name: 'avgRetentionTarget',
              type: 'text',
              label: 'Ø Retention (Ziel)',
              admin: { 
                width: '25%',
                placeholder: 'z.B. ">50%"',
              },
            },
            {
              name: 'avgRetentionCurrent',
              type: 'text',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },
    
    // === BUSINESS GOALS ===
    {
      name: 'businessGoals',
      type: 'group',
      label: 'Business-Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'newsletterSignupsTarget',
              type: 'number',
              label: 'Newsletter-Anmeldungen',
              admin: { width: '25%' },
            },
            {
              name: 'newsletterSignupsCurrent',
              type: 'number',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
            {
              name: 'affiliateClicksTarget',
              type: 'number',
              label: 'Affiliate-Klicks',
              admin: { width: '25%' },
            },
            {
              name: 'affiliateClicksCurrent',
              type: 'number',
              label: 'Aktuell',
              admin: { width: '25%' },
            },
          ],
        },
      ],
    },
    
    // === CUSTOM GOALS ===
    {
      name: 'customGoals',
      type: 'array',
      label: 'Weitere Ziele',
      fields: [
        {
          type: 'row',
          fields: [
            {
              name: 'metric',
              type: 'text',
              required: true,
              admin: { 
                width: '40%',
                placeholder: 'z.B. "SPARK Videos"',
              },
            },
            {
              name: 'target',
              type: 'text',
              required: true,
              admin: { 
                width: '20%',
                placeholder: 'Ziel',
              },
            },
            {
              name: 'current',
              type: 'text',
              admin: { 
                width: '20%',
                placeholder: 'Aktuell',
              },
            },
            {
              name: 'status',
              type: 'select',
              options: [
                { label: '🟢 On Track', value: 'on_track' },
                { label: '🟡 At Risk', value: 'at_risk' },
                { label: '✅ Achieved', value: 'achieved' },
                { label: '❌ Missed', value: 'missed' },
              ],
              admin: { width: '20%' },
            },
          ],
        },
      ],
    },
    
    {
      name: 'notes',
      type: 'textarea',
      label: 'Notizen / Learnings',
    },
  ],
}
```

---

## 5. Erweiterte YouTube Content Collection

```typescript
// Erweiterungen für YouTubeContent.ts

// Neue Felder hinzufügen:

// === PRODUKTION ===
{
  name: 'productionBatch',
  type: 'relationship',
  relationTo: 'yt-batches',
  label: 'Produktions-Batch',
  admin: {
    position: 'sidebar',
  },
},
{
  name: 'productionWeek',
  type: 'number',
  label: 'Produktionswoche',
  min: 1,
  max: 52,
  admin: {
    position: 'sidebar',
  },
},
{
  name: 'calendarWeek',
  type: 'number',
  label: 'Kalenderwoche',
  min: 1,
  max: 52,
  admin: {
    position: 'sidebar',
  },
},
{
  name: 'productionDate',
  type: 'date',
  label: 'Produktionsdatum',
  admin: {
    date: {
      pickerAppearance: 'dayOnly',
      displayFormat: 'dd.MM.yyyy',
    },
  },
},
{
  name: 'targetDuration',
  type: 'text',
  label: 'Ziel-Dauer',
  admin: {
    placeholder: 'z.B. "8-12 Min" oder "45-58s"',
  },
},
{
  name: 'bRollNotes',
  type: 'textarea',
  label: 'B-Roll / Setting Notizen',
  admin: {
    rows: 2,
  },
},

// === POSTING ===
{
  name: 'publishTime',
  type: 'text',
  label: 'Posting-Uhrzeit',
  admin: {
    placeholder: 'z.B. "07:00" oder "17:00"',
  },
},
{
  name: 'thumbnailText',
  type: 'text',
  label: 'Thumbnail-Text',
  admin: {
    placeholder: 'z.B. "BOARD READY | 7 MIN"',
  },
},
{
  name: 'ctaType',
  type: 'select',
  label: 'CTA-Typ',
  options: [
    { label: 'Link in Bio', value: 'link_in_bio' },
    { label: 'Newsletter', value: 'newsletter' },
    { label: 'Longform verlinken', value: 'longform_link' },
    { label: 'Benutzerdefiniert', value: 'custom' },
  ],
},
{
  name: 'ctaDetail',
  type: 'text',
  label: 'CTA-Detail',
  admin: {
    placeholder: 'z.B. "GRFI-Checkliste" oder "CPW-Rechner"',
    condition: (data) => data?.ctaType,
  },
},

// === SCRIPT (Block-basiert) ===
{
  name: 'script',
  type: 'blocks',
  label: 'Script',
  labels: {
    singular: 'Section',
    plural: 'Script Sections',
  },
  blocks: [ScriptSectionBlock],
},

// === UPLOAD CHECKLIST ===
{
  name: 'uploadChecklist',
  type: 'array',
  label: 'Upload-Checkliste',
  admin: {
    condition: (data) => ['approved', 'upload_scheduled', 'published'].includes(data?.status),
  },
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'step',
          type: 'text',
          required: true,
          admin: { width: '40%' },
        },
        {
          name: 'completed',
          type: 'checkbox',
          label: '✓',
          admin: { width: '10%' },
        },
        {
          name: 'completedAt',
          type: 'date',
          admin: { 
            width: '25%',
            readOnly: true,
          },
        },
        {
          name: 'completedBy',
          type: 'relationship',
          relationTo: 'users',
          admin: { 
            width: '25%',
            readOnly: true,
          },
        },
      ],
    },
  ],
},

// === DISCLAIMERS ===
{
  name: 'disclaimers',
  type: 'array',
  label: 'Disclaimers',
  fields: [
    {
      type: 'row',
      fields: [
        {
          name: 'type',
          type: 'select',
          required: true,
          options: [
            { label: '⚠️ Medizinisch', value: 'medical' },
            { label: '⚖️ Rechtlich', value: 'legal' },
            { label: '🔗 Affiliate', value: 'affiliate' },
            { label: '🤝 Sponsored', value: 'sponsored' },
          ],
          admin: { width: '25%' },
        },
        {
          name: 'text',
          type: 'text',
          admin: { width: '50%' },
        },
        {
          name: 'placement',
          type: 'select',
          options: [
            { label: 'Gesprochen', value: 'spoken' },
            { label: 'Text-Overlay', value: 'overlay' },
            { label: 'Beschreibung', value: 'description' },
            { label: 'Überall', value: 'all' },
          ],
          admin: { width: '25%' },
        },
      ],
    },
  ],
},
```

---

## 6. Zusammenfassung der neuen Collections

| Collection | Slug | Zweck |
|------------|------|-------|
| YouTube Channels | `youtube-channels` | Kanäle (existiert) |
| YouTube Content | `youtube-content` | Videos + Script (erweitert) |
| YT Tasks | `yt-tasks` | Aufgaben (existiert) |
| YT Notifications | `yt-notifications` | Benachrichtigungen (existiert) |
| **YT Batches** | `yt-batches` | **NEU: Produktions-Batches** |
| **YT Monthly Goals** | `yt-monthly-goals` | **NEU: Monatsziele** |
| **YT Script Templates** | `yt-script-templates` | **NEU: Skript-Vorlagen** |
| **YT Checklist Templates** | `yt-checklist-templates` | **NEU: Upload-Checklisten** |

---

## 7. Custom Admin Views

| View | Pfad | Zweck |
|------|------|-------|
| Content Calendar | `/admin/views/youtube/content-calendar` | Produktions- & Publishing-Kalender |
| Batch Overview | `/admin/views/youtube/batch-overview` | Dashboard aller Batches |
| Monthly Goals | `/admin/views/youtube/monthly-goals` | KPI-Dashboard |

---

## 8. Migrations

```typescript
// src/migrations/20260112_200000_youtube_ops_v2.ts

import { MigrateUpArgs, MigrateDownArgs } from '@payloadcms/db-postgres'

export async function up({ payload }: MigrateUpArgs): Promise<void> {
  // 1. Neue Enums
  await payload.db.drizzle.execute(`
    CREATE TYPE enum_yt_batches_status AS ENUM (
      'planning', 'scripting', 'production', 'editing', 'review', 'ready', 'published'
    );
    
    CREATE TYPE enum_script_section_type AS ENUM (
      'hook', 'intro_ident', 'context', 'content_part', 'summary', 'cta', 'outro', 'disclaimer'
    );
    
    CREATE TYPE enum_cta_type AS ENUM (
      'link_in_bio', 'newsletter', 'longform_link', 'custom'
    );
    
    CREATE TYPE enum_disclaimer_type AS ENUM (
      'medical', 'legal', 'affiliate', 'sponsored'
    );
    
    CREATE TYPE enum_disclaimer_placement AS ENUM (
      'spoken', 'overlay', 'description', 'all'
    );
  `);

  // 2. Neue Tabellen
  // ... (vollständige Migration)
}

export async function down({ payload }: MigrateDownArgs): Promise<void> {
  // Rollback
}
```

---

## Nächste Schritte

1. **Claude Code Integration Prompt erstellen** mit allen Collections, Blocks und Views
2. **Seed-Daten** mit BlogWoman Januar Content als Beispiel
3. **CSS Styles** für Calendar und Dashboard Views
4. **Lexical Custom Block** für Script Section Editor

Soll ich jetzt den vollständigen **Claude Code Integration Prompt** erstellen?
