diff --git a/app/Http/Controllers/MapController.php b/app/Http/Controllers/MapController.php index 93f1f6d..48ed338 100644 --- a/app/Http/Controllers/MapController.php +++ b/app/Http/Controllers/MapController.php @@ -43,4 +43,9 @@ class MapController extends Controller { return response()->json($this->mapDataService->getProjectMap($projectId)); } + + public function apiNodeChildren(string $type, int $id) + { + return response()->json($this->mapDataService->getNodeChildren($type, $id)); + } } diff --git a/app/Services/MapDataService.php b/app/Services/MapDataService.php index c0f18d7..691edc2 100644 --- a/app/Services/MapDataService.php +++ b/app/Services/MapDataService.php @@ -17,7 +17,7 @@ class MapDataService { $themas = Thema::with([ 'speerpunten.projects' => function ($q) { - $q->with('eigenaar') + $q->with(['eigenaar', 'fases', 'commitments.eigenaar', 'documents.auteur', 'risicos']) ->withCount(['documents', 'commitments', 'risicos', 'fases']); } ])->get(); @@ -54,7 +54,7 @@ class MapDataService 'description' => Str::limit($project->beschrijving, 100), 'owner' => $project->eigenaar?->name, 'badge' => ucfirst(str_replace('_', ' ', $project->status->value)), - 'children' => $project->documents_count + $project->commitments_count, + 'children' => $this->buildProjectChildren($project), ]; } @@ -114,6 +114,7 @@ class MapDataService 'order' => $order + 1, 'status' => $fase->status->value, 'badge' => ucfirst($fase->status->value), + 'children' => null, ]; } @@ -131,6 +132,7 @@ class MapDataService 'status' => $commitment->status->value, 'owner' => $commitment->eigenaar?->name, 'badge' => $commitment->deadline?->format('d M'), + 'children' => null, ]; } @@ -147,6 +149,7 @@ class MapDataService 'order' => $order + 1, 'status' => 'active', 'badge' => "v{$doc->versie}", + 'children' => null, ]; } @@ -162,4 +165,94 @@ class MapDataService ], ]; } + + /** + * 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, + }; + } } diff --git a/resources/js/Components/MetroMap/MetroCanvas.vue b/resources/js/Components/MetroMap/MetroCanvas.vue index 0a419be..811da92 100644 --- a/resources/js/Components/MetroMap/MetroCanvas.vue +++ b/resources/js/Components/MetroMap/MetroCanvas.vue @@ -1,54 +1,150 @@ diff --git a/routes/web.php b/routes/web.php index cde38a2..52c8eb4 100644 --- a/routes/web.php +++ b/routes/web.php @@ -20,6 +20,7 @@ Route::middleware(['auth', 'verified'])->group(function () { Route::prefix('api')->group(function () { Route::get('/map/strategy', [MapController::class, 'apiStrategy'])->name('api.map.strategy'); Route::get('/map/project/{project}', [MapController::class, 'apiProject'])->name('api.map.project'); + Route::get('/map/node/{type}/{id}/children', [MapController::class, 'apiNodeChildren'])->name('api.map.node.children'); }); // Projects