590 lines
31 KiB
PHP
590 lines
31 KiB
PHP
<?php
|
||
|
||
namespace Database\Seeders;
|
||
|
||
use App\Models\Emotion;
|
||
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
|
||
{
|
||
$definitions = $this->definitions();
|
||
$collectionMap = [];
|
||
|
||
DB::transaction(function () use ($definitions, &$collectionMap) {
|
||
foreach ($definitions as $eventTypeSlug => $definition) {
|
||
$eventType = $this->ensureEventType($definition['event_type']);
|
||
|
||
$collection = TaskCollection::updateOrCreate(
|
||
['slug' => $definition['collection']['slug']],
|
||
[
|
||
'tenant_id' => null,
|
||
'event_type_id' => $eventType->id,
|
||
'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['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];
|
||
}
|
||
|
||
$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 couple’s 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
|
||
{
|
||
return EventType::updateOrCreate(
|
||
['slug' => $definition['slug']],
|
||
[
|
||
'name' => $definition['name'],
|
||
'icon' => $definition['icon'] ?? null,
|
||
]
|
||
);
|
||
}
|
||
|
||
protected function ensureEmotion(array $definition, ?int $eventTypeId): ?Emotion
|
||
{
|
||
if (empty($definition)) {
|
||
return null;
|
||
}
|
||
|
||
$query = Emotion::query();
|
||
$name = $definition['name'] ?? [];
|
||
|
||
if (isset($name['en'])) {
|
||
$query->orWhere('name->en', $name['en']);
|
||
}
|
||
|
||
if (isset($name['de'])) {
|
||
$query->orWhere('name->de', $name['de']);
|
||
}
|
||
|
||
$emotion = $query->first();
|
||
|
||
if (! $emotion) {
|
||
$emotion = Emotion::create([
|
||
'name' => $name,
|
||
'icon' => $definition['icon'] ?? 'lucide-smile',
|
||
'color' => $definition['color'] ?? '#6366f1',
|
||
'description' => $definition['description'] ?? null,
|
||
'sort_order' => $definition['sort_order'] ?? 0,
|
||
'is_active' => true,
|
||
]);
|
||
}
|
||
|
||
if ($eventTypeId && ! $emotion->eventTypes()->where('event_type_id', $eventTypeId)->exists()) {
|
||
$emotion->eventTypes()->attach($eventTypeId);
|
||
}
|
||
|
||
return $emotion;
|
||
}
|
||
}
|