mirror of
https://github.com/complexcaresolutions/cms.c2sgmbh.git
synced 2026-03-17 18:34:13 +00:00
Complete YouTube content management system: - YouTubeChannels: Channel management with branding and metrics - YouTubeContent: Video pipeline with workflow, approvals, scheduling - YtSeries: Dedicated series management per channel (NEW) - YtBatches: Production batch tracking with targets and progress - YtTasks: Task management with notifications - YtNotifications: User notification system - YtMonthlyGoals: Monthly production goals per channel - YtScriptTemplates: Reusable script templates - YtChecklistTemplates: Checklist templates for workflows Features: - Role-based access (YouTubeManager, YouTubeCreator, YouTubeViewer) - Auto-task generation on status changes - Series relationship with channel-based filtering - API endpoints for dashboard, tasks, and task completion - German/English localization support Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
219 lines
5.8 KiB
TypeScript
219 lines
5.8 KiB
TypeScript
// src/collections/YtTasks.ts
|
|
|
|
import type { CollectionConfig } from 'payload'
|
|
import { isYouTubeManager, canAccessAssignedContent } from '../lib/youtubeAccess'
|
|
import { notifyOnAssignment } from '../hooks/ytTasks/notifyOnAssignment'
|
|
|
|
/**
|
|
* YtTasks Collection
|
|
*
|
|
* Aufgabenverwaltung für die Video-Produktion.
|
|
* Tasks werden automatisch bei Statuswechsel erstellt.
|
|
* Teil des YouTube Operations Hub.
|
|
*/
|
|
export const YtTasks: CollectionConfig = {
|
|
slug: 'yt-tasks',
|
|
labels: {
|
|
singular: 'YouTube-Aufgabe',
|
|
plural: 'YouTube-Aufgaben',
|
|
},
|
|
admin: {
|
|
useAsTitle: 'title',
|
|
group: 'YouTube',
|
|
defaultColumns: ['title', 'video', 'assignedTo', 'status', 'dueDate', 'priority'],
|
|
listSearchableFields: ['title'],
|
|
description: 'Aufgaben für die Video-Produktion',
|
|
},
|
|
access: {
|
|
read: canAccessAssignedContent,
|
|
create: isYouTubeManager,
|
|
update: canAccessAssignedContent,
|
|
delete: isYouTubeManager,
|
|
},
|
|
fields: [
|
|
{
|
|
name: 'title',
|
|
type: 'text',
|
|
required: true,
|
|
localized: true,
|
|
label: 'Aufgabe',
|
|
},
|
|
{
|
|
name: 'description',
|
|
type: 'textarea',
|
|
localized: true,
|
|
label: 'Beschreibung',
|
|
},
|
|
{
|
|
name: 'video',
|
|
type: 'relationship',
|
|
relationTo: 'youtube-content',
|
|
label: 'Zugehöriges Video',
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'channel',
|
|
type: 'relationship',
|
|
relationTo: 'youtube-channels',
|
|
label: 'Kanal',
|
|
admin: {
|
|
position: 'sidebar',
|
|
description: 'Wird automatisch vom Video übernommen',
|
|
},
|
|
},
|
|
{
|
|
name: 'taskType',
|
|
type: 'select',
|
|
required: true,
|
|
label: 'Aufgabentyp',
|
|
options: [
|
|
{ label: 'Skript schreiben', value: 'script_write' },
|
|
{ label: 'Skript reviewen', value: 'script_review' },
|
|
{ label: 'Dreh vorbereiten', value: 'shoot_prep' },
|
|
{ label: 'Drehen', value: 'shoot' },
|
|
{ label: 'Schneiden', value: 'edit' },
|
|
{ label: 'Grafiken erstellen', value: 'graphics' },
|
|
{ label: 'Thumbnail erstellen', value: 'thumbnail' },
|
|
{ label: 'Review/Freigabe', value: 'review' },
|
|
{ label: 'Hochladen', value: 'upload' },
|
|
{ label: 'Performance tracken', value: 'track' },
|
|
{ label: 'Kommentare beantworten', value: 'comments' },
|
|
{ label: 'Sonstiges', value: 'other' },
|
|
],
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'status',
|
|
type: 'select',
|
|
required: true,
|
|
defaultValue: 'todo',
|
|
label: 'Status',
|
|
options: [
|
|
{ label: 'Offen', value: 'todo' },
|
|
{ label: 'In Arbeit', value: 'in_progress' },
|
|
{ label: 'Blockiert', value: 'blocked' },
|
|
{ label: 'Wartet auf Review', value: 'waiting_review' },
|
|
{ label: 'Erledigt', value: 'done' },
|
|
{ label: 'Abgebrochen', value: 'cancelled' },
|
|
],
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'priority',
|
|
type: 'select',
|
|
defaultValue: 'normal',
|
|
label: 'Priorität',
|
|
options: [
|
|
{ label: 'Dringend', value: 'urgent' },
|
|
{ label: 'Hoch', value: 'high' },
|
|
{ label: 'Normal', value: 'normal' },
|
|
{ label: 'Niedrig', value: 'low' },
|
|
],
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'assignedTo',
|
|
type: 'relationship',
|
|
relationTo: 'users',
|
|
required: true,
|
|
label: 'Zugewiesen an',
|
|
admin: {
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'dueDate',
|
|
type: 'date',
|
|
label: 'Fälligkeitsdatum',
|
|
admin: {
|
|
position: 'sidebar',
|
|
date: { pickerAppearance: 'dayAndTime' },
|
|
},
|
|
},
|
|
{
|
|
name: 'completedAt',
|
|
type: 'date',
|
|
label: 'Abgeschlossen am',
|
|
admin: {
|
|
readOnly: true,
|
|
position: 'sidebar',
|
|
},
|
|
},
|
|
{
|
|
name: 'completedBy',
|
|
type: 'relationship',
|
|
relationTo: 'users',
|
|
label: 'Abgeschlossen von',
|
|
admin: { readOnly: true },
|
|
},
|
|
{
|
|
name: 'blockedReason',
|
|
type: 'text',
|
|
localized: true,
|
|
label: 'Grund für Blockierung',
|
|
admin: {
|
|
condition: (data) => data?.status === 'blocked',
|
|
},
|
|
},
|
|
{
|
|
name: 'attachments',
|
|
type: 'array',
|
|
label: 'Anhänge',
|
|
fields: [
|
|
{ name: 'file', type: 'upload', relationTo: 'media', label: 'Datei' },
|
|
{ name: 'note', type: 'text', label: 'Notiz' },
|
|
],
|
|
},
|
|
{
|
|
name: 'comments',
|
|
type: 'array',
|
|
label: 'Kommentare',
|
|
fields: [
|
|
{ name: 'author', type: 'relationship', relationTo: 'users', label: 'Autor' },
|
|
{ name: 'content', type: 'textarea', label: 'Inhalt' },
|
|
{ name: 'createdAt', type: 'date', label: 'Erstellt am' },
|
|
],
|
|
},
|
|
],
|
|
timestamps: true,
|
|
hooks: {
|
|
afterChange: [notifyOnAssignment],
|
|
beforeChange: [
|
|
async ({ data, originalDoc, req }) => {
|
|
if (!data) return data
|
|
|
|
// Setze completedAt wenn Status auf "done" wechselt
|
|
if (data.status === 'done' && originalDoc?.status !== 'done') {
|
|
data.completedAt = new Date().toISOString()
|
|
data.completedBy = req.user?.id
|
|
}
|
|
|
|
// Setze Channel automatisch vom Video
|
|
if (data.video && !data.channel) {
|
|
try {
|
|
const video = await req.payload.findByID({
|
|
collection: 'youtube-content',
|
|
id: typeof data.video === 'object' ? data.video.id : data.video,
|
|
depth: 0,
|
|
})
|
|
if (video?.channel) {
|
|
data.channel = typeof video.channel === 'object' ? video.channel.id : video.channel
|
|
}
|
|
} catch (error) {
|
|
console.error('[YtTasks] Error fetching video for channel:', error)
|
|
}
|
|
}
|
|
|
|
return data
|
|
},
|
|
],
|
|
},
|
|
}
|