zu fabricjs gewechselt, noch nicht funktionsfähig

This commit is contained in:
Codex Agent
2025-10-31 20:19:09 +01:00
parent 06df61f706
commit eb0c31c90b
33 changed files with 7718 additions and 2062 deletions

View File

@@ -0,0 +1,65 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('task_collections') && ! Schema::hasColumn('task_collections', 'source_collection_id')) {
Schema::table('task_collections', function (Blueprint $table) {
$table->foreignId('source_collection_id')
->nullable()
->after('event_type_id')
->constrained('task_collections')
->nullOnDelete();
});
}
if (Schema::hasTable('tasks')) {
Schema::table('tasks', function (Blueprint $table) {
if (! Schema::hasColumn('tasks', 'source_collection_id')) {
$table->foreignId('source_collection_id')
->nullable()
->after('collection_id')
->constrained('task_collections')
->nullOnDelete();
}
if (! Schema::hasColumn('tasks', 'source_task_id')) {
$table->foreignId('source_task_id')
->nullable()
->after('tenant_id')
->constrained('tasks')
->nullOnDelete();
}
});
}
}
public function down(): void
{
if (Schema::hasTable('tasks')) {
Schema::table('tasks', function (Blueprint $table) {
if (Schema::hasColumn('tasks', 'source_collection_id')) {
$table->dropForeign(['source_collection_id']);
$table->dropColumn('source_collection_id');
}
if (Schema::hasColumn('tasks', 'source_task_id')) {
$table->dropForeign(['source_task_id']);
$table->dropColumn('source_task_id');
}
});
}
if (Schema::hasTable('task_collections') && Schema::hasColumn('task_collections', 'source_collection_id')) {
Schema::table('task_collections', function (Blueprint $table) {
$table->dropForeign(['source_collection_id']);
$table->dropColumn('source_collection_id');
});
}
}
};

View File

@@ -24,7 +24,7 @@ class InviteLayoutSeeder extends Seeder
'text' => $layout['text'] ?? null,
'badge' => $layout['badge'] ?? null,
'qr' => $layout['qr'] ?? ['size_px' => 500],
'svg' => $layout['svg'] ?? ['width' => 1080, 'height' => 1520],
'svg' => $layout['svg'] ?? ['width' => 1240, 'height' => 1754],
];
$options = [
@@ -35,7 +35,7 @@ class InviteLayoutSeeder extends Seeder
'cta_caption' => $layout['cta_caption'] ?? 'Scan mich & starte direkt',
'link_label' => $layout['link_label'] ?? null,
'logo_url' => $layout['logo_url'] ?? null,
'formats' => $layout['formats'] ?? ['pdf', 'svg'],
'formats' => $layout['formats'] ?? ['pdf', 'png'],
];
InviteLayout::updateOrCreate(

View File

@@ -7,250 +7,546 @@ use App\Models\EventType;
use App\Models\Task;
use App\Models\TaskCollection;
use Illuminate\Database\Seeder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
class TaskCollectionsSeeder extends Seeder
{
private const MIN_TASKS_PER_EVENT_TYPE = 50;
public function run(): void
{
$collections = [
[
'slug' => 'wedding-classics',
'event_type' => [
'slug' => 'wedding',
'name' => [
'de' => 'Hochzeit',
'en' => 'Wedding',
],
'icon' => 'lucide-heart',
],
'name' => [
'de' => 'Hochzeitsklassiker',
'en' => 'Wedding Classics',
],
'description' => [
'de' => 'Kuratierte Aufgaben rund um Trauung, Emotionen und besondere Momente.',
'en' => 'Curated prompts for vows, emotions, and memorable wedding highlights.',
],
'is_default' => true,
'position' => 10,
'tasks' => [
[
'slug' => 'wedding-first-look',
'title' => [
'de' => 'Erster Blick des Brautpaares festhalten',
'en' => 'Capture the couples first look',
],
'description' => [
'de' => 'Halte den Moment fest, in dem sich Braut und Bräutigam zum ersten Mal sehen.',
'en' => 'Capture the moment when the bride and groom see each other for the first time.',
],
'example' => [
'de' => 'Fotografiere die Reaktionen aus verschiedenen Blickwinkeln.',
'en' => 'Photograph their reactions from different angles.',
],
'emotion' => [
'name' => [
'de' => 'Romantik',
'en' => 'Romance',
],
'icon' => 'lucide-heart',
'color' => '#ec4899',
'sort_order' => 10,
],
'difficulty' => 'easy',
'sort_order' => 10,
],
[
'slug' => 'wedding-family-hug',
'title' => [
'de' => 'Familienumarmung organisieren',
'en' => 'Organise a family group hug',
],
'description' => [
'de' => 'Bitte die wichtigsten Menschen, das Paar gleichzeitig zu umarmen.',
'en' => 'Ask the closest friends and family to hug the couple at the same time.',
],
'example' => [
'de' => 'Kombiniere die Umarmung mit einem Toast.',
'en' => 'Combine the hug with a heartfelt toast.',
],
'emotion' => [
'name' => [
'de' => 'Freude',
'en' => 'Joy',
],
'icon' => 'lucide-smile',
'color' => '#f59e0b',
'sort_order' => 20,
],
'difficulty' => 'medium',
'sort_order' => 20,
],
[
'slug' => 'wedding-midnight-sparkler',
'title' => [
'de' => 'Mitternachtsfunkeln mit Wunderkerzen',
'en' => 'Midnight sparkler moment',
],
'description' => [
'de' => 'Verteile Wunderkerzen und schafft ein leuchtendes Spalier für das Paar.',
'en' => 'Hand out sparklers and form a glowing aisle for the couple.',
],
'example' => [
'de' => 'Koordiniere die Musik und kündige den Countdown an.',
'en' => 'Coordinate music and announce a countdown.',
],
'emotion' => [
'name' => [
'de' => 'Ekstase',
'en' => 'Euphoria',
],
'icon' => 'lucide-stars',
'color' => '#6366f1',
'sort_order' => 30,
],
'difficulty' => 'medium',
'sort_order' => 30,
],
],
],
[
'slug' => 'birthday-celebration',
'event_type' => [
'slug' => 'birthday',
'name' => [
'de' => 'Geburtstag',
'en' => 'Birthday',
],
'icon' => 'lucide-cake',
],
'name' => [
'de' => 'Geburtstags-Highlights',
'en' => 'Birthday Highlights',
],
'description' => [
'de' => 'Aufgaben für Überraschungen, Gratulationen und gemeinsames Feiern.',
'en' => 'Prompts covering surprises, wishes, and shared celebrations.',
],
'is_default' => false,
'position' => 20,
'tasks' => [
[
'slug' => 'birthday-surprise-wall',
'title' => [
'de' => 'Überraschungswand mit Polaroids gestalten',
'en' => 'Create a surprise wall filled with instant photos',
],
'description' => [
'de' => 'Sammle Schnappschüsse der Gäste und befestige sie als Fotowand.',
'en' => 'Collect snapshots from guests and mount them on a photo wall.',
],
'example' => [
'de' => 'Schreibe zu jedem Bild einen kurzen Gruß.',
'en' => 'Add a short message to each picture.',
],
'emotion' => [
'name' => [
'de' => 'Nostalgie',
'en' => 'Nostalgia',
],
'icon' => 'lucide-images',
'color' => '#f97316',
'sort_order' => 40,
],
'difficulty' => 'easy',
'sort_order' => 10,
],
[
'slug' => 'birthday-toast-circle',
'title' => [
'de' => 'Gratulationskreis mit kurzen Toasts',
'en' => 'Circle of toasts',
],
'description' => [
'de' => 'Bildet einen Kreis und bittet jede Person um einen 10-Sekunden-Toast.',
'en' => 'Form a circle and ask everyone for a 10-second toast.',
],
'example' => [
'de' => 'Nimm die Reaktionen als Video auf.',
'en' => 'Record the reactions on video.',
],
'emotion' => [
'name' => [
'de' => 'Dankbarkeit',
'en' => 'Gratitude',
],
'icon' => 'lucide-hands',
'color' => '#22c55e',
'sort_order' => 50,
],
'difficulty' => 'easy',
'sort_order' => 20,
],
],
],
];
$definitions = $this->definitions();
$collectionMap = [];
DB::transaction(function () use ($collections) {
foreach ($collections as $definition) {
DB::transaction(function () use ($definitions, &$collectionMap) {
foreach ($definitions as $eventTypeSlug => $definition) {
$eventType = $this->ensureEventType($definition['event_type']);
$collection = TaskCollection::updateOrCreate(
['slug' => $definition['slug']],
['slug' => $definition['collection']['slug']],
[
'tenant_id' => null,
'event_type_id' => $eventType->id,
'name_translations' => $definition['name'],
'description_translations' => $definition['description'],
'is_default' => $definition['is_default'] ?? false,
'position' => $definition['position'] ?? 0,
'name_translations' => $definition['collection']['name'],
'description_translations' => $definition['collection']['description'],
'is_default' => $definition['collection']['is_default'] ?? false,
'position' => $definition['collection']['position'] ?? 0,
'source_collection_id' => null,
]
);
$syncPayload = [];
foreach ($definition['tasks'] as $taskDefinition) {
$emotion = $this->ensureEmotion($taskDefinition['emotion'] ?? [], $eventType->id);
$task = Task::updateOrCreate(
['slug' => $taskDefinition['slug']],
[
'tenant_id' => null,
'event_type_id' => $eventType->id,
'collection_id' => $collection->id,
'emotion_id' => $emotion?->id,
'title' => $taskDefinition['title'],
'description' => $taskDefinition['description'] ?? null,
'example_text' => $taskDefinition['example'] ?? null,
'difficulty' => $taskDefinition['difficulty'] ?? 'easy',
'priority' => 'medium',
'sort_order' => $taskDefinition['sort_order'] ?? 0,
'is_active' => true,
'is_completed' => false,
]
);
$syncPayload[$task->id] = ['sort_order' => $taskDefinition['sort_order'] ?? 0];
foreach ($definition['base_tasks'] as $index => $taskDefinition) {
$sortOrder = $taskDefinition['sort_order'] ?? (($index + 1) * 10);
$task = $this->upsertTask($collection, $eventType, $taskDefinition, $sortOrder);
$syncPayload[$task->id] = ['sort_order' => $sortOrder];
}
if (! empty($syncPayload)) {
$collection->tasks()->sync($syncPayload);
}
$this->ensureMinimumTasks(
$collection,
$eventType,
$syncPayload,
count($syncPayload),
self::MIN_TASKS_PER_EVENT_TYPE,
$eventTypeSlug
);
$collection->tasks()->sync($syncPayload);
$collectionMap[$eventType->id] = $collection;
}
});
$this->assignOrphanTasks($collectionMap);
}
private function definitions(): array
{
return [
'wedding' => [
'event_type' => [
'slug' => 'wedding',
'name' => ['de' => 'Hochzeit', 'en' => 'Wedding'],
'icon' => 'lucide-heart',
],
'collection' => [
'slug' => 'wedding-classics',
'name' => [
'de' => 'Hochzeitsklassiker',
'en' => 'Wedding Classics',
],
'description' => [
'de' => 'Kuratierte Aufgaben rund um Trauung, Emotionen und besondere Momente.',
'en' => 'Curated prompts for vows, emotions, and memorable wedding highlights.',
],
'is_default' => true,
'position' => 10,
],
'base_tasks' => [
$this->taskDefinition(
'wedding-first-look',
['de' => 'Erster Blick des Brautpaares festhalten', 'en' => 'Capture the couples first look'],
['de' => 'Halte den Moment fest, in dem sich Braut und Bräutigam zum ersten Mal sehen.', 'en' => 'Capture the instant when the couple sees each other for the first time.'],
['de' => 'Fotografiere die Reaktionen aus verschiedenen Blickwinkeln.', 'en' => 'Shoot reactions from multiple angles.'],
['name' => ['de' => 'Romantik', 'en' => 'Romance'], 'icon' => 'lucide-heart', 'color' => '#ec4899', 'sort_order' => 10],
'easy',
10
),
$this->taskDefinition(
'wedding-family-hug',
['de' => 'Familienumarmung organisieren', 'en' => 'Organise a family group hug'],
['de' => 'Bitte die wichtigsten Menschen, das Paar gleichzeitig zu umarmen.', 'en' => 'Ask the closest friends and family to hug the couple all at once.'],
['de' => 'Kombiniere die Umarmung mit einem Toast.', 'en' => 'Combine the hug with a heartfelt toast.'],
['name' => ['de' => 'Freude', 'en' => 'Joy'], 'icon' => 'lucide-smile', 'color' => '#f59e0b', 'sort_order' => 20],
'medium',
20
),
$this->taskDefinition(
'wedding-midnight-sparkler',
['de' => 'Mitternachtsfunkeln mit Wunderkerzen', 'en' => 'Midnight sparkler moment'],
['de' => 'Verteile Wunderkerzen und schafft ein leuchtendes Spalier für das Paar.', 'en' => 'Hand out sparklers and form a glowing aisle for the couple.'],
['de' => 'Koordiniere die Musik und kündige den Countdown an.', 'en' => 'Coordinate music and announce a countdown.'],
['name' => ['de' => 'Ekstase', 'en' => 'Euphoria'], 'icon' => 'lucide-stars', 'color' => '#6366f1', 'sort_order' => 30],
'medium',
30
),
$this->taskDefinition(
'wedding-vow-whisper',
['de' => 'Flüstert die liebsten Gelübde erneut', 'en' => 'Whisper your vows again'],
['de' => 'Lasst das Paar sich gegenseitig einen Satz aus den Gelübden zuflüstern.', 'en' => 'Let the couple whisper a favourite line from their vows to each other.'],
['de' => 'Nahaufnahme der Gesichter, während sie lächeln.', 'en' => 'Capture a close-up of the smiles while they whisper.'],
['name' => ['de' => 'Verbundenheit', 'en' => 'Connection'], 'icon' => 'lucide-infinity', 'color' => '#a855f7', 'sort_order' => 35],
'easy',
40
),
$this->taskDefinition(
'wedding-generations-portrait',
['de' => 'Generationenportrait', 'en' => 'Generations portrait'],
['de' => 'Bringe drei Generationen gleichzeitig aufs Foto vom jüngsten zum ältesten Familienmitglied.', 'en' => 'Bring three generations into a single photograph youngest to oldest.'],
['de' => 'Stellt sie gestaffelt auf einer Treppe auf.', 'en' => 'Arrange them on steps for a layered look.'],
['name' => ['de' => 'Familie', 'en' => 'Family'], 'icon' => 'lucide-users', 'color' => '#0ea5e9', 'sort_order' => 45],
'medium',
50
),
],
],
'birthday' => [
'event_type' => [
'slug' => 'birthday',
'name' => ['de' => 'Geburtstag', 'en' => 'Birthday'],
'icon' => 'lucide-cake',
],
'collection' => [
'slug' => 'birthday-celebration',
'name' => [
'de' => 'Geburtstags-Highlights',
'en' => 'Birthday Highlights',
],
'description' => [
'de' => 'Aufgaben für Überraschungen, Gratulationen und gemeinsames Feiern.',
'en' => 'Prompts covering surprises, wishes, and shared celebrations.',
],
'is_default' => false,
'position' => 20,
],
'base_tasks' => [
$this->taskDefinition(
'birthday-surprise-wall',
['de' => 'Überraschungswand mit Polaroids gestalten', 'en' => 'Create a surprise wall filled with instant photos'],
['de' => 'Sammle Schnappschüsse der Gäste und befestige sie als Fotowand.', 'en' => 'Collect snapshots from guests and display them as a photo wall.'],
['de' => 'Bitte jeden Gast um einen kurzen Gruß zum Bild.', 'en' => 'Invite each guest to add a short note to the photo.'],
['name' => ['de' => 'Nostalgie', 'en' => 'Nostalgia'], 'icon' => 'lucide-images', 'color' => '#f97316', 'sort_order' => 40],
'easy',
10
),
$this->taskDefinition(
'birthday-toast-circle',
['de' => 'Gratulationskreis mit kurzen Toasts', 'en' => 'Circle of toasts'],
['de' => 'Bildet einen Kreis und bittet jede Person um einen 10-Sekunden-Toast.', 'en' => 'Gather in a circle and ask each guest for a 10-second toast.'],
['de' => 'Nimm die Lautstärke nach jedem Toast auf wer jubelt am lautesten?', 'en' => 'Capture the volume of each cheer—who is the loudest?'],
['name' => ['de' => 'Dankbarkeit', 'en' => 'Gratitude'], 'icon' => 'lucide-hands', 'color' => '#22c55e', 'sort_order' => 50],
'easy',
20
),
$this->taskDefinition(
'birthday-memory-lane',
['de' => 'Memory-Lane Foto-Story', 'en' => 'Memory lane photo story'],
['de' => 'Suche drei Gegenstände, die zum Geburtstagsgast passen, und erzähle eine Mini-Story mit Fotos.', 'en' => 'Find three objects that represent the guest of honour and photograph them in a mini story.'],
['de' => 'Nutze unterschiedliche Bildausschnitte für die Story.', 'en' => 'Use different framings to tell the story.'],
['name' => ['de' => 'Erinnerung', 'en' => 'Reminiscence'], 'icon' => 'lucide-book-open', 'color' => '#6366f1', 'sort_order' => 55],
'medium',
30
),
],
],
'christmas' => [
'event_type' => [
'slug' => 'christmas',
'name' => ['de' => 'Weihnachten', 'en' => 'Christmas'],
'icon' => 'lucide-sparkles',
],
'collection' => [
'slug' => 'christmas-magic',
'name' => [
'de' => 'Festliche Highlights',
'en' => 'Festive Highlights',
],
'description' => [
'de' => 'Wärmende Aufgaben für Glühwein-Momente und gemeinsames Staunen.',
'en' => 'Cozy prompts for mulled wine moments and shared wonder.',
],
'is_default' => false,
'position' => 30,
],
'base_tasks' => [
$this->taskDefinition(
'christmas-light-hunt',
['de' => 'Finde das schönste Lichterdekor', 'en' => 'Spot the brightest decoration'],
['de' => 'Halte das stimmungsvollste Licht im Raum fest.', 'en' => 'Capture the coziest lights around you.'],
['de' => 'Zeige die Lichtquelle plus Reaktion eines Gastes.', 'en' => 'Show the light source together with a guest reacting to it.'],
['name' => ['de' => 'Besinnlichkeit', 'en' => 'Serenity'], 'icon' => 'lucide-candle', 'color' => '#f97316', 'sort_order' => 20],
'easy',
10
),
$this->taskDefinition(
'christmas-cookie-cheers',
['de' => 'Plätzchen-Toast', 'en' => 'Cookie cheers'],
['de' => 'Stoßt mit Lieblingsplätzchen an und haltet das gemeinsame Lachen fest.', 'en' => 'Clink your favourite cookies together and capture the laughter.'],
['de' => 'Fotografiere in einer Reihe Hände vorne, Gesichter dahinter.', 'en' => 'Frame hands with cookies in front, smiling faces behind.'],
['name' => ['de' => 'Wärme', 'en' => 'Warmth'], 'icon' => 'lucide-mug-hot', 'color' => '#ef4444', 'sort_order' => 30],
'easy',
20
),
$this->taskDefinition(
'christmas-snow-story',
['de' => 'Schneegestöber im Innenraum', 'en' => 'Indoor snow story'],
['de' => 'Nutze Deko oder Papier, um Schneeflocken zu simulieren, und halte den Zauber fest.', 'en' => 'Use props or paper to fake snow and capture the magic.'],
['de' => 'Lass die Schneeflocken im Vordergrund unscharf erscheinen.', 'en' => 'Keep the fake snow in the foreground out of focus for a dreamy look.'],
['name' => ['de' => 'Staunen', 'en' => 'Wonder'], 'icon' => 'lucide-sparkles', 'color' => '#22d3ee', 'sort_order' => 35],
'medium',
30
),
],
],
'corporate' => [
'event_type' => [
'slug' => 'corporate',
'name' => ['de' => 'Firma', 'en' => 'Corporate'],
'icon' => 'lucide-briefcase',
],
'collection' => [
'slug' => 'corporate-connect',
'name' => [
'de' => 'Team-Verbindungen',
'en' => 'Team Connections',
],
'description' => [
'de' => 'Interaktive Aufgaben für produktive Offsites und Firmenfeiern.',
'en' => 'Interactive prompts for productive offsites and company celebrations.',
],
'is_default' => false,
'position' => 40,
],
'base_tasks' => [
$this->taskDefinition(
'corporate-high-five-chain',
['de' => 'High-Five-Kette starten', 'en' => 'Start a high-five chain'],
['de' => 'Platziert euch im Kreis und gebt reihum High-Fives haltet den Moment fest.', 'en' => 'Form a circle and pass on high-fives capture the energy.'],
['de' => 'Nutze Serienaufnahmen, um alle Bewegungen einzufangen.', 'en' => 'Use burst mode to catch every high-five.'],
['name' => ['de' => 'Teamgeist', 'en' => 'Team Spirit'], 'icon' => 'lucide-users', 'color' => '#0ea5e9', 'sort_order' => 10],
'easy',
10
),
$this->taskDefinition(
'corporate-goal-toast',
['de' => 'Ziele feiern', 'en' => 'Celebrate milestones'],
['de' => 'Ruft gemeinsam ein erreichte Ziel in die Kamera und haltet die Euphorie fest.', 'en' => 'Shout a recent team achievement into the camera together.'],
['de' => 'Lasst Konfetti oder Luftballons in den Rahmen fallen.', 'en' => 'Drop confetti or balloons into the frame.'],
['name' => ['de' => 'Stolz', 'en' => 'Pride'], 'icon' => 'lucide-trophy', 'color' => '#22c55e', 'sort_order' => 20],
'medium',
20
),
$this->taskDefinition(
'corporate-coffee-pose',
['de' => 'Kaffee-Powerpose', 'en' => 'Coffee power pose'],
['de' => 'Lass das Team mit Kaffeebechern posieren jede Person zeigt eine andere Emotion.', 'en' => 'Have the team pose with coffee mugs, each showing a different emotion.'],
['de' => 'Nutze eine niedrige Perspektive, um Power auszudrücken.', 'en' => 'Shoot from a low angle to emphasise power.'],
['name' => ['de' => 'Motivation', 'en' => 'Motivation'], 'icon' => 'lucide-rocket', 'color' => '#8b5cf6', 'sort_order' => 30],
'easy',
30
),
],
],
];
}
private function autoTaskSeeds(): array
{
return [
'wedding' => [
$this->autoSeed('dancefloor',
['de' => 'Dancefloor-Glück #{n}', 'en' => 'Dancefloor joy #{n}'],
['de' => 'Fange eine wilde Tanzszene ein, die die Energie der Gäste zeigt.', 'en' => 'Capture a wild dance scene packed with guest energy.'],
['de' => 'Nutze eine lange Belichtungszeit für Lichtstreifen.', 'en' => 'Use a longer exposure to create light trails.'],
['name' => ['de' => 'Ekstase', 'en' => 'Euphoria'], 'icon' => 'lucide-stars', 'color' => '#a855f7'],
'medium'
),
$this->autoSeed('sparkler',
['de' => 'Wunderkerzen-Story #{n}', 'en' => 'Sparkler story #{n}'],
['de' => 'Erzähle die Geschichte eines funkelnden Moments mit mindestens drei Bildern.', 'en' => 'Tell the story of a sparkling moment in three frames.'],
['de' => 'Variiere zwischen Detailaufnahme, Halbtotaler und Reaktion.', 'en' => 'Mix a detail shot, a medium frame, and a reaction close-up.'],
['name' => ['de' => 'Staunen', 'en' => 'Awe'], 'icon' => 'lucide-wand', 'color' => '#f97316'],
'medium'
),
$this->autoSeed('first-dance-detail',
['de' => 'Detail beim Hochzeitstanz #{n}', 'en' => 'First-dance detail #{n}'],
['de' => 'Suche ein kleines Detail während des Hochzeitstanzes Hände, Schuhe, Accessoires.', 'en' => 'Spot a tiny detail during the first dance hands, shoes or accessories.'],
['de' => 'Nutze starkes Bokeh für den Hintergrund.', 'en' => 'Use heavy bokeh to blur the background.'],
['name' => ['de' => 'Zärtlichkeit', 'en' => 'Tenderness'], 'icon' => 'lucide-feather', 'color' => '#fb7185'],
'easy'
),
$this->autoSeed('guest-story',
['de' => 'Gästegeschichte #{n}', 'en' => 'Guest story #{n}'],
['de' => 'Dokumentiere eine Gruppe von Gästen, wie sie miteinander interagiert.', 'en' => 'Document a group of guests interacting naturally.'],
['de' => 'Fotografiere die Gruppe aus zwei Perspektiven.', 'en' => 'Capture the group from two perspectives.'],
['name' => ['de' => 'Freundschaft', 'en' => 'Friendship'], 'icon' => 'lucide-users', 'color' => '#38bdf8'],
'easy'
),
],
'birthday' => [
$this->autoSeed('candle-wish',
['de' => 'Kerzenwunsch #{n}', 'en' => 'Candle wish #{n}'],
['de' => 'Halte den Moment fest, in dem der Wunsch ausgesprochen wird.', 'en' => 'Capture the second the wish is made.'],
['de' => 'Lass das Motiv in den Kerzenlichtschein eintauchen.', 'en' => 'Bathe the scene in candlelight glow.'],
['name' => ['de' => 'Freude', 'en' => 'Joy'], 'icon' => 'lucide-sparkles', 'color' => '#facc15'],
'easy'
),
$this->autoSeed('gift-reaction',
['de' => 'Geschenk-Reaktion #{n}', 'en' => 'Gift reaction #{n}'],
['de' => 'Fange die spontanste Reaktion auf ein Geschenk ein.', 'en' => 'Capture the most spontaneous gift reaction.'],
['de' => 'Versuche eine Serienaufnahme für echte Emotionen.', 'en' => 'Use burst mode for authentic emotion.'],
['name' => ['de' => 'Überraschung', 'en' => 'Surprise'], 'icon' => 'lucide-gift', 'color' => '#f472b6'],
'easy'
),
$this->autoSeed('birthday-anthem',
['de' => 'Geburtstagshymne #{n}', 'en' => 'Birthday anthem #{n}'],
['de' => 'Dokumentiere das Mitsingen eines Liedes inklusive Stimmung im Raum.', 'en' => 'Document everyone singing a song and the overall mood.'],
['de' => 'Fokussiere auf die Person, die am lautesten singt.', 'en' => 'Focus on whoever sings the loudest.'],
['name' => ['de' => 'Lebensfreude', 'en' => 'Delight'], 'icon' => 'lucide-music', 'color' => '#60a5fa'],
'medium'
),
],
'christmas' => [
$this->autoSeed('ornament-closeup',
['de' => 'Ornament-Kunstwerk #{n}', 'en' => 'Ornament art #{n}'],
['de' => 'Inszeniere ein besonderes Ornament in Nahaufnahme.', 'en' => 'Stage a special ornament in a moody close-up.'],
['de' => 'Lass warme Lichter im Hintergrund bokeh-artig wirken.', 'en' => 'Let warm lights blur softly in the background.'],
['name' => ['de' => 'Staunen', 'en' => 'Wonder'], 'icon' => 'lucide-sparkles', 'color' => '#38bdf8'],
'easy'
),
$this->autoSeed('carol-chorus',
['de' => 'Singender Chor #{n}', 'en' => 'Carolling chorus #{n}'],
['de' => 'Zeige eine Gruppe beim Singen eines Weihnachtsliedes.', 'en' => 'Highlight a group singing a seasonal song.'],
['de' => 'Nutze eine Panorama-Aufnahme für alle Beteiligten.', 'en' => 'Use a panorama framing to include everyone.'],
['name' => ['de' => 'Gemeinschaft', 'en' => 'Togetherness'], 'icon' => 'lucide-users', 'color' => '#22c55e'],
'medium'
),
$this->autoSeed('wishlist-doodle',
['de' => 'Wunschlisten-Doodle #{n}', 'en' => 'Wishlist doodle #{n}'],
['de' => 'Fotografiere eine improvisierte Wunschliste inklusive Zeichnungen.', 'en' => 'Photograph an improvised wishlist complete with doodles.'],
['de' => 'Lege die Liste auf Holz- oder Stoffoberflächen für Wärme.', 'en' => 'Place the list on wood or fabric for extra warmth.'],
['name' => ['de' => 'Hoffnung', 'en' => 'Hope'], 'icon' => 'lucide-heart', 'color' => '#f97316'],
'easy'
),
],
'corporate' => [
$this->autoSeed('team-brainstorm',
['de' => 'Brainstorm-Blitzlicht #{n}', 'en' => 'Brainstorm spotlight #{n}'],
['de' => 'Halte eine kreative Idee auf einem Whiteboard plus das Team dahinter fest.', 'en' => 'Capture a clever whiteboard idea with the team behind it.'],
['de' => 'Arbeite mit Spiegelungen im Glas oder Bildschirm.', 'en' => 'Play with reflections on glass or screens.'],
['name' => ['de' => 'Innovation', 'en' => 'Innovation'], 'icon' => 'lucide-lightbulb', 'color' => '#facc15'],
'medium'
),
$this->autoSeed('watercooler-moment',
['de' => 'Watercooler-Moment #{n}', 'en' => 'Watercooler moment #{n}'],
['de' => 'Zeige Kollegen beim lockeren Austausch abseits des Meetings.', 'en' => 'Show teammates chatting casually away from the meeting table.'],
['de' => 'Fokussiere auf Gestik und Körpersprache.', 'en' => 'Focus on gestures and body language.'],
['name' => ['de' => 'Leichtigkeit', 'en' => 'Ease'], 'icon' => 'lucide-coffee', 'color' => '#60a5fa'],
'easy'
),
$this->autoSeed('celebration-confetti',
['de' => 'Konfetti-Erfolg #{n}', 'en' => 'Confetti success #{n}'],
['de' => 'Inszeniere einen kleinen Konfettiwurf als Symbol für Team-Erfolg.', 'en' => 'Stage a mini confetti throw to celebrate team success.'],
['de' => 'Arbeite mit schneller Verschlusszeit für eingefrorene Partikel.', 'en' => 'Use a fast shutter to freeze the confetti.'],
['name' => ['de' => 'Erfolg', 'en' => 'Success'], 'icon' => 'lucide-flag', 'color' => '#a855f7'],
'medium'
),
],
'default' => [
$this->autoSeed('story-fragments',
['de' => 'Story-Fragmente #{n}', 'en' => 'Story fragments #{n}'],
['de' => 'Finde drei kleine Details, die zusammen eine Geschichte ergeben.', 'en' => 'Find three small details that build one story.'],
['de' => 'Kombiniere Nahaufnahmen mit einem Übersichtsfoto.', 'en' => 'Combine close-ups with one establishing shot.'],
['name' => ['de' => 'Neugier', 'en' => 'Curiosity'], 'icon' => 'lucide-eye', 'color' => '#38bdf8'],
'easy'
),
],
];
}
private function taskDefinition(
string $slug,
array $title,
array $description,
array $example,
?array $emotion,
string $difficulty,
int $sortOrder
): array {
return [
'slug' => $slug,
'title' => $title,
'description' => $description,
'example' => $example,
'emotion' => $emotion,
'difficulty' => $difficulty,
'sort_order' => $sortOrder,
];
}
private function autoSeed(
string $slug,
array $title,
array $description,
array $example,
?array $emotion,
string $difficulty
): array {
return [
'slug' => $slug,
'title' => $title,
'description' => $description,
'example' => $example,
'emotion' => $emotion,
'difficulty' => $difficulty,
];
}
private function upsertTask(TaskCollection $collection, EventType $eventType, array $definition, int $sortOrder): Task
{
$emotion = $this->ensureEmotion($definition['emotion'] ?? [], $eventType->id);
$task = Task::updateOrCreate(
['slug' => $definition['slug']],
[
'tenant_id' => null,
'event_type_id' => $eventType->id,
'collection_id' => $collection->id,
'emotion_id' => $emotion?->id,
'title' => $definition['title'],
'description' => $definition['description'] ?? null,
'example_text' => $definition['example'] ?? null,
'difficulty' => $definition['difficulty'] ?? 'easy',
'priority' => $definition['priority'] ?? 'medium',
'sort_order' => $definition['sort_order'] ?? $sortOrder,
'is_active' => true,
'is_completed' => false,
]
);
if ($task->collection_id !== $collection->id) {
$task->collection_id = $collection->id;
$task->save();
}
return $task;
}
private function ensureMinimumTasks(
TaskCollection $collection,
EventType $eventType,
array &$syncPayload,
int $existingCount,
int $minimum,
string $eventTypeSlug
): void {
$needed = $minimum - $existingCount;
if ($needed <= 0) {
return;
}
$seeds = $this->autoTaskSeeds()[$eventTypeSlug] ?? $this->autoTaskSeeds()['default'];
if (empty($seeds)) {
$seeds = $this->autoTaskSeeds()['default'];
}
$baseCount = $existingCount;
for ($i = 0; $i < $needed; $i++) {
$seed = $seeds[$i % count($seeds)];
$sequence = $baseCount + $i + 1;
$slug = Str::slug(sprintf('%s-%s-%s', $eventType->slug ?? $eventTypeSlug, $seed['slug'], $sequence));
$taskDefinition = [
'slug' => $slug,
'title' => [
'de' => str_replace('{n}', (string) $sequence, $seed['title']['de']),
'en' => str_replace('{n}', (string) $sequence, $seed['title']['en']),
],
'description' => [
'de' => str_replace('{n}', (string) $sequence, $seed['description']['de']),
'en' => str_replace('{n}', (string) $sequence, $seed['description']['en']),
],
'example' => [
'de' => str_replace('{n}', (string) $sequence, $seed['example']['de']),
'en' => str_replace('{n}', (string) $sequence, $seed['example']['en']),
],
'emotion' => $seed['emotion'] ?? null,
'difficulty' => $seed['difficulty'] ?? 'easy',
'priority' => $seed['priority'] ?? 'medium',
'sort_order' => 500 + ($sequence * 5),
];
$task = $this->upsertTask($collection, $eventType, $taskDefinition, $taskDefinition['sort_order']);
$syncPayload[$task->id] = ['sort_order' => $taskDefinition['sort_order']];
}
}
private function assignOrphanTasks(array $collectionMap): void
{
if (empty($collectionMap)) {
return;
}
Task::whereNull('collection_id')
->orderBy('id')
->chunkById(100, function ($tasks) use ($collectionMap) {
foreach ($tasks as $task) {
$collection = $collectionMap[$task->event_type_id] ?? Arr::first($collectionMap);
if (! $collection) {
continue;
}
$task->collection_id = $collection->id;
$task->save();
$collection->tasks()->syncWithoutDetaching([
$task->id => ['sort_order' => $task->sort_order ?? 0],
]);
}
});
}
protected function ensureEventType(array $definition): EventType
{
$payload = [
'name' => $definition['name'],
'icon' => $definition['icon'] ?? null,
];
return EventType::updateOrCreate(
['slug' => $definition['slug']],
$payload
[
'name' => $definition['name'],
'icon' => $definition['icon'] ?? null,
]
);
}
@@ -261,7 +557,6 @@ class TaskCollectionsSeeder extends Seeder
}
$query = Emotion::query();
$name = $definition['name'] ?? [];
if (isset($name['en'])) {