function ($q) { $q->with(['eigenaar', 'fases', 'commitments.eigenaar', 'documents.auteur', 'risicos']) ->withCount(['documents', 'commitments', 'risicos', 'fases']); } ])->get(); $lines = []; $nodes = []; $connections = []; $lineColors = ['#00d2ff', '#e94560', '#00ff88', '#7b68ee', '#ff6b6b', '#ffd93d', '#6bcb77', '#4d96ff']; $yOffset = 0; foreach ($themas as $index => $thema) { $color = $lineColors[$index % count($lineColors)]; $lines[] = [ 'id' => "thema-{$thema->id}", 'name' => $thema->naam, 'color' => $color, ]; $projects = $thema->speerpunten->flatMap->projects; $xOffset = -200; foreach ($projects as $order => $project) { $nodes[] = [ 'id' => "project-{$project->id}", 'entityId' => $project->id, 'entityType' => 'project', 'name' => $project->naam, 'lineId' => "thema-{$thema->id}", 'x' => $xOffset + ($order * 200), 'y' => $yOffset, 'order' => $order + 1, 'status' => $project->status->value, 'description' => Str::limit($project->beschrijving, 100), 'owner' => $project->eigenaar?->name, 'badge' => ucfirst(str_replace('_', ' ', $project->status->value)), 'children' => $this->buildProjectChildren($project), ]; } $yOffset += 130; } // Get dependencies as connections $dependencies = Afhankelijkheid::all(); foreach ($dependencies as $dep) { $connections[] = [ 'from' => "project-{$dep->project_id}", 'to' => "project-{$dep->afhankelijk_van_project_id}", ]; } return [ 'lines' => $lines, 'nodes' => $nodes, 'connections' => $connections, 'level' => 1, ]; } /** * Build Level 2 (Project) metro map data. * The project's lifecycle phases = a metro line, milestones = stations. */ public function getProjectMap(int $projectId): array { $project = Project::with([ 'fases', 'commitments' => fn ($q) => $q->with('eigenaar'), 'documents', 'risicos', 'besluiten', ])->findOrFail($projectId); $lines = [ ['id' => 'lifecycle', 'name' => $project->naam, 'color' => '#00d2ff'], ['id' => 'commitments', 'name' => 'Commitments', 'color' => '#e94560'], ['id' => 'documents', 'name' => 'Documenten', 'color' => '#7b68ee'], ]; $nodes = []; $xOffset = -300; // Phase nodes on lifecycle line foreach ($project->fases->sortBy('type') as $order => $fase) { $nodes[] = [ 'id' => "fase-{$fase->id}", 'entityId' => $fase->id, 'entityType' => 'fase', 'name' => ucfirst(str_replace('_', ' ', $fase->type->value)), 'lineId' => 'lifecycle', 'x' => $xOffset + ($order * 180), 'y' => -50, 'order' => $order + 1, 'status' => $fase->status->value, 'badge' => ucfirst($fase->status->value), 'children' => null, ]; } // Commitment nodes foreach ($project->commitments as $order => $commitment) { $nodes[] = [ 'id' => "commitment-{$commitment->id}", 'entityId' => $commitment->id, 'entityType' => 'commitment', 'name' => Str::limit($commitment->beschrijving, 40), 'lineId' => 'commitments', 'x' => $xOffset + ($order * 180), 'y' => 80, 'order' => $order + 1, 'status' => $commitment->status->value, 'owner' => $commitment->eigenaar?->name, 'badge' => $commitment->deadline?->format('d M'), 'children' => null, ]; } // Document nodes foreach ($project->documents as $order => $doc) { $nodes[] = [ 'id' => "document-{$doc->id}", 'entityId' => $doc->id, 'entityType' => 'document', 'name' => $doc->titel, 'lineId' => 'documents', 'x' => $xOffset + ($order * 180), 'y' => 210, 'order' => $order + 1, 'status' => 'active', 'badge' => "v{$doc->versie}", 'children' => null, ]; } return [ 'lines' => $lines, 'nodes' => $nodes, 'connections' => [], 'level' => 2, 'project' => [ 'id' => $project->id, 'naam' => $project->naam, 'status' => $project->status->value, ], ]; } /** * Build the Level 2 children data for a project node. * Used inline in getStrategyMap() and via getNodeChildren(). */ private function buildProjectChildren(Project $project): array { $project->loadMissing(['fases', 'commitments.eigenaar', 'documents.auteur', 'risicos']); $lines = [ ['id' => "lifecycle-{$project->id}", 'name' => 'Levenscyclus', 'color' => '#00d2ff'], ['id' => "commitments-{$project->id}", 'name' => 'Commitments', 'color' => '#e94560'], ['id' => "documents-{$project->id}", 'name' => 'Documenten', 'color' => '#7b68ee'], ]; $nodes = []; $xOffset = -250; $spacing = 200; // Phase nodes foreach ($project->fases->sortBy('created_at') as $i => $fase) { $nodes[] = [ 'id' => "fase-{$fase->id}", 'entityId' => $fase->id, 'entityType' => 'fase', 'name' => ucfirst(str_replace('_', ' ', $fase->type->value)), 'lineId' => "lifecycle-{$project->id}", 'x' => $xOffset + ($i * $spacing), 'y' => -60, 'order' => $i + 1, 'status' => $fase->status->value, 'badge' => ucfirst($fase->status->value), 'children' => null, // Phases could have children in future ]; } // Commitment nodes foreach ($project->commitments as $i => $commitment) { $nodes[] = [ 'id' => "commitment-{$commitment->id}", 'entityId' => $commitment->id, 'entityType' => 'commitment', 'name' => Str::limit($commitment->beschrijving, 35), 'lineId' => "commitments-{$project->id}", 'x' => $xOffset + ($i * $spacing), 'y' => 80, 'order' => $i + 1, 'status' => $commitment->status->value, 'owner' => $commitment->eigenaar?->name, 'badge' => $commitment->deadline?->format('d M Y'), 'children' => null, // Could contain acties in future ]; } // Document nodes foreach ($project->documents as $i => $doc) { $nodes[] = [ 'id' => "document-{$doc->id}", 'entityId' => $doc->id, 'entityType' => 'document', 'name' => $doc->titel, 'lineId' => "documents-{$project->id}", 'x' => $xOffset + ($i * $spacing), 'y' => 220, 'order' => $i + 1, 'status' => 'active', 'badge' => "v{$doc->versie}", 'children' => null, ]; } return [ 'lines' => $lines, 'nodes' => $nodes, 'connections' => [], ]; } /** * Return children for any node by entity type and ID. * Used by the API endpoint for on-demand dimension data. */ public function getNodeChildren(string $entityType, int $entityId): ?array { return match ($entityType) { 'project' => $this->buildProjectChildren(Project::findOrFail($entityId)), // Future: 'commitment' => $this->buildCommitmentChildren(...) default => null, }; } }