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; $xStart = $this->snapToGrid(-200); foreach ($projects as $order => $project) { $nodes[] = [ 'id' => "project-{$project->id}", 'entityId' => $project->id, 'entityType' => 'project', 'name' => $project->naam, 'lineId' => "thema-{$thema->id}", 'x' => $this->snapToGrid($xStart + ($order * self::GRID_STEP_X)), 'y' => $this->snapToGrid($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 += self::GRID_STEP_Y; } // 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 = []; $xStart = $this->snapToGrid(-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' => $this->snapToGrid($xStart + ($order * self::GRID_STEP_X)), 'y' => $this->snapToGrid(-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' => $this->snapToGrid($xStart + ($order * self::GRID_STEP_X)), 'y' => $this->snapToGrid(100), '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' => $this->snapToGrid($xStart + ($order * self::GRID_STEP_X)), 'y' => $this->snapToGrid(250), '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 = []; $xStart = $this->snapToGrid(-250); // 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' => $this->snapToGrid($xStart + ($i * self::GRID_STEP_X)), 'y' => $this->snapToGrid(-50), 'order' => $i + 1, 'status' => $fase->status->value, 'badge' => ucfirst($fase->status->value), 'children' => null, ]; } // 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' => $this->snapToGrid($xStart + ($i * self::GRID_STEP_X)), 'y' => $this->snapToGrid(100), 'order' => $i + 1, 'status' => $commitment->status->value, 'owner' => $commitment->eigenaar?->name, 'badge' => $commitment->deadline?->format('d M Y'), 'children' => null, ]; } // 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' => $this->snapToGrid($xStart + ($i * self::GRID_STEP_X)), 'y' => $this->snapToGrid(250), 'order' => $i + 1, 'status' => 'active', 'badge' => "v{$doc->versie}", 'children' => null, ]; } return [ 'lines' => $lines, 'nodes' => $nodes, 'connections' => [], 'parentEntityType' => 'project', 'parentEntityId' => $project->id, 'parentName' => $project->naam, ]; } /** * Return children for any node by entity type and ID. */ public function getNodeChildren(string $entityType, int $entityId): ?array { return match ($entityType) { 'project' => $this->buildProjectChildren(Project::findOrFail($entityId)), default => null, }; } }