diff --git a/packages/panels/resources/views/components/resources/relation-managers.blade.php b/packages/panels/resources/views/components/resources/relation-managers.blade.php index 25f4bc9f6a7..985b9c77819 100644 --- a/packages/panels/resources/views/components/resources/relation-managers.blade.php +++ b/packages/panels/resources/views/components/resources/relation-managers.blade.php @@ -70,7 +70,6 @@ @if (filled($activeManager) && isset($managers[$activeManager]))
1) - id="relationManager{{ ucfirst($activeManager) }}" role="tabpanel" tabindex="0" @endif diff --git a/packages/panels/src/Resources/Pages/Concerns/CanAuthorizeResourceAccess.php b/packages/panels/src/Resources/Pages/Concerns/CanAuthorizeResourceAccess.php index e5c1a4defad..e7cba91cf67 100644 --- a/packages/panels/src/Resources/Pages/Concerns/CanAuthorizeResourceAccess.php +++ b/packages/panels/src/Resources/Pages/Concerns/CanAuthorizeResourceAccess.php @@ -12,5 +12,9 @@ public function mountCanAuthorizeResourceAccess(): void public static function authorizeResourceAccess(): void { abort_unless(static::getResource()::canAccess(), 403); + + if ($parentResource = static::getParentResource()) { + abort_unless($parentResource::canAccess(), 403); + } } } diff --git a/packages/panels/src/Resources/Pages/Concerns/InteractsWithParentRecord.php b/packages/panels/src/Resources/Pages/Concerns/InteractsWithParentRecord.php new file mode 100644 index 00000000000..727976c90c1 --- /dev/null +++ b/packages/panels/src/Resources/Pages/Concerns/InteractsWithParentRecord.php @@ -0,0 +1,68 @@ +parentRecord = $this->resolveParentRecord( + request()->route()->parameter( + $parentResourceRegistration->getParentRouteParameterName(), + ), + ); + + $this->authorizeParentRecordAccess(); + } + + protected function authorizeParentRecordAccess(): void + { + abort_unless(static::getParentResource()::canView($this->getParentRecord()), 403); + } + + protected function resolveParentRecord(int | string $key): Model + { + $record = static::getParentResource()::resolveRecordRouteBinding($key); + + if ($record === null) { + throw (new ModelNotFoundException())->setModel($this->getModel(), [$key]); + } + + return $record; + } + + public function getParentRecord(): ?Model + { + return $this->parentRecord; + } + + public function getParentRecordTitle(): string | Htmlable | null + { + $resource = static::getParentResource(); + + if (! $resource::hasRecordTitle()) { + return $resource::getTitleCaseModelLabel(); + } + + return $resource::getRecordTitle($this->getParentRecord()); + } + + public static function getParentResource(): ?string + { + return static::getResource()::getParentResourceRegistration()?->getParentResource(); + } +} diff --git a/packages/panels/src/Resources/Pages/Concerns/InteractsWithRecord.php b/packages/panels/src/Resources/Pages/Concerns/InteractsWithRecord.php index f3aa0bbc70c..3791d73fc1e 100644 --- a/packages/panels/src/Resources/Pages/Concerns/InteractsWithRecord.php +++ b/packages/panels/src/Resources/Pages/Concerns/InteractsWithRecord.php @@ -50,22 +50,19 @@ public function getRecordTitle(): string | Htmlable */ public function getBreadcrumbs(): array { - $resource = static::getResource(); - - $breadcrumbs = [ - $resource::getUrl() => $resource::getBreadcrumb(), - ]; + $breadcrumbs = parent::getBreadcrumbs(); + $resource = static::getResource(); $record = $this->getRecord(); if ($record->exists && $resource::hasRecordTitle()) { if ($resource::hasPage('view') && $resource::canView($record)) { $breadcrumbs[ - $resource::getUrl('view', ['record' => $record]) + $this->getResourceUrl('view') ] = $this->getRecordTitle(); } elseif ($resource::hasPage('edit') && $resource::canEdit($record)) { $breadcrumbs[ - $resource::getUrl('edit', ['record' => $record]) + $this->getResourceUrl('edit') ] = $this->getRecordTitle(); } else { $breadcrumbs[] = $this->getRecordTitle(); @@ -74,10 +71,6 @@ public function getBreadcrumbs(): array $breadcrumbs[] = $this->getBreadcrumb(); - if (filled($cluster = static::getCluster())) { - return $cluster::unshiftClusterBreadcrumbs($breadcrumbs); - } - return $breadcrumbs; } diff --git a/packages/panels/src/Resources/Pages/CreateRecord.php b/packages/panels/src/Resources/Pages/CreateRecord.php index d4b4710a04b..a3e07d7155d 100644 --- a/packages/panels/src/Resources/Pages/CreateRecord.php +++ b/packages/panels/src/Resources/Pages/CreateRecord.php @@ -173,6 +173,10 @@ protected function handleRecordCreation(array $data): Model return $this->associateRecordWithTenant($record, $tenant); } + if ($parentRecord = $this->getParentRecord()) { + return $this->associateRecordWithParent($record, $parentRecord); + } + $record->save(); return $record; @@ -191,6 +195,11 @@ protected function associateRecordWithTenant(Model $record, Model $tenant): Mode return $relationship->save($record); } + protected function associateRecordWithParent(Model $record, Model $parent): Model + { + return static::getResource()::getParentResourceRegistration()->getRelationship($parent)->save($record); + } + /** * @param array $data * @return array @@ -238,7 +247,7 @@ protected function getCancelFormAction(): Action { return Action::make('cancel') ->label(__('filament-panels::resources/pages/create-record.form.actions.cancel.label')) - ->alpineClickHandler('document.referrer ? window.history.back() : (window.location.href = ' . Js::from($this->previousUrl ?? static::getResource()::getUrl()) . ')') + ->alpineClickHandler('document.referrer ? window.history.back() : (window.location.href = ' . Js::from($this->previousUrl ?? $this->getResourceUrl()) . ')') ->color('gray'); } @@ -280,14 +289,14 @@ protected function getRedirectUrl(): string $resource = static::getResource(); if ($resource::hasPage('view') && $resource::canView($this->getRecord())) { - return $resource::getUrl('view', ['record' => $this->getRecord(), ...$this->getRedirectUrlParameters()]); + return $this->getResourceUrl('view', $this->getRedirectUrlParameters()); } if ($resource::hasPage('edit') && $resource::canEdit($this->getRecord())) { - return $resource::getUrl('edit', ['record' => $this->getRecord(), ...$this->getRedirectUrlParameters()]); + return $this->getResourceUrl('edit', $this->getRedirectUrlParameters()); } - return $resource::getUrl('index'); + return $this->getResourceUrl(); } /** diff --git a/packages/panels/src/Resources/Pages/EditRecord.php b/packages/panels/src/Resources/Pages/EditRecord.php index 3aba1f0cf0e..436280be5ae 100644 --- a/packages/panels/src/Resources/Pages/EditRecord.php +++ b/packages/panels/src/Resources/Pages/EditRecord.php @@ -284,7 +284,7 @@ protected function configureViewAction(ViewAction $action): void ->form(fn (Schema $form): Schema => static::getResource()::form($form)); if ($resource::hasPage('view')) { - $action->url(fn (): string => static::getResource()::getUrl('view', ['record' => $this->getRecord()])); + $action->url(fn (): string => $this->getResourceUrl('view')); } } @@ -294,7 +294,7 @@ protected function configureForceDeleteAction(ForceDeleteAction $action): void $action ->authorize($resource::canForceDelete($this->getRecord())) - ->successRedirectUrl($resource::getUrl('index')); + ->successRedirectUrl($this->getResourceUrl()); } protected function configureReplicateAction(ReplicateAction $action): void @@ -315,7 +315,7 @@ protected function configureDeleteAction(DeleteAction $action): void $action ->authorize($resource::canDelete($this->getRecord())) - ->successRedirectUrl($resource::getUrl('index')); + ->successRedirectUrl($this->getResourceUrl()); } public function getTitle(): string | Htmlable @@ -357,7 +357,7 @@ protected function getCancelFormAction(): Action { return Action::make('cancel') ->label(__('filament-panels::resources/pages/edit-record.form.actions.cancel.label')) - ->alpineClickHandler('document.referrer ? window.history.back() : (window.location.href = ' . Js::from($this->previousUrl ?? static::getResource()::getUrl()) . ')') + ->alpineClickHandler('document.referrer ? window.history.back() : (window.location.href = ' . Js::from($this->previousUrl ?? $this->getResourceUrl()) . ')') ->color('gray'); } diff --git a/packages/panels/src/Resources/Pages/ListRecords.php b/packages/panels/src/Resources/Pages/ListRecords.php index 446d98ddf8d..27ae7248d42 100644 --- a/packages/panels/src/Resources/Pages/ListRecords.php +++ b/packages/panels/src/Resources/Pages/ListRecords.php @@ -129,7 +129,7 @@ protected function configureCreateAction(CreateAction $action): void } if ($resource::hasPage('create')) { - $action->url(fn (): string => $resource::getUrl('create')); + $action->url(fn (): string => $this->getResourceUrl('create')); } } @@ -164,7 +164,7 @@ protected function configureEditAction(EditAction $action): void ->icon(FilamentIcon::resolve('actions::edit-action') ?? 'heroicon-m-pencil-square'); if ($resource::hasPage('edit')) { - $action->url(fn (Model $record): string => $resource::getUrl('edit', ['record' => $record])); + $action->url(fn (Model $record): string => $this->getResourceUrl('edit', ['record' => $record])); } } @@ -199,7 +199,7 @@ protected function configureViewAction(ViewAction $action): void ->schema(fn (Schema $schema): Schema => $this->infolist($this->form($schema->columns(2)))); if ($resource::hasPage('view')) { - $action->url(fn (Model $record): string => $resource::getUrl('view', ['record' => $record])); + $action->url(fn (Model $record): string => $this->getResourceUrl('view', ['record' => $record])); } } @@ -321,7 +321,7 @@ protected function makeTable(): Table continue; } - return $resource::getUrl($action, ['record' => $record]); + return $this->getResourceUrl($action, ['record' => $record]); } return null; diff --git a/packages/panels/src/Resources/Pages/ManageRecords.php b/packages/panels/src/Resources/Pages/ManageRecords.php index c1c196ca322..e88b4812500 100644 --- a/packages/panels/src/Resources/Pages/ManageRecords.php +++ b/packages/panels/src/Resources/Pages/ManageRecords.php @@ -4,12 +4,8 @@ class ManageRecords extends ListRecords { - public function getBreadcrumbs(): array + public function hasResourceBreadcrumbs(): bool { - if (filled($cluster = static::getCluster())) { - return $cluster::unshiftClusterBreadcrumbs([]); - } - - return []; + return false; } } diff --git a/packages/panels/src/Resources/Pages/Page.php b/packages/panels/src/Resources/Pages/Page.php index 592bb413f3f..0d3ae7b9206 100644 --- a/packages/panels/src/Resources/Pages/Page.php +++ b/packages/panels/src/Resources/Pages/Page.php @@ -9,6 +9,7 @@ use Filament\Pages\SubNavigationPosition; use Filament\Panel; use Filament\Resources\Pages\Concerns\CanAuthorizeResourceAccess; +use Filament\Resources\Pages\Concerns\InteractsWithParentRecord; use Illuminate\Database\Eloquent\Model; use Illuminate\Routing\Route; use Illuminate\Support\Facades\Route as RouteFacade; @@ -16,6 +17,7 @@ abstract class Page extends BasePage { use CanAuthorizeResourceAccess; + use InteractsWithParentRecord; protected static ?string $breadcrumb = null; @@ -23,6 +25,22 @@ abstract class Page extends BasePage protected static bool $isDiscovered = false; + /** + * @param array $parameters + */ + public function getResourceUrl(?string $name = null, array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string + { + if (method_exists($this, 'getRecord')) { + $parameters['record'] ??= $this->getRecord(); + } + + if ($parentResourceRegistration = static::getResource()::getParentResourceRegistration()) { + $parameters[$parentResourceRegistration->getParentRouteParameterName()] ??= $this->getParentRecord(); + } + + return static::getResource()::getUrl($name, $parameters, $isAbsolute, $panel, $tenant); + } + public static function getRouteName(?string $panel = null): string { $routeBaseName = static::getResource()::getRouteBaseName(panel: $panel); @@ -112,17 +130,64 @@ public function getBreadcrumb(): ?string return static::$breadcrumb ?? static::getTitle(); } + public function hasResourceBreadcrumbs(): bool + { + return true; + } + /** * @return array */ public function getBreadcrumbs(): array { - $resource = static::getResource(); - - $breadcrumbs = [ - $resource::getUrl() => $resource::getBreadcrumb(), - ...(filled($breadcrumb = $this->getBreadcrumb()) ? [$breadcrumb] : []), - ]; + $breadcrumbs = []; + + if ($this->hasResourceBreadcrumbs()) { + $resource = static::getResource(); + + $breadcrumbs[$this->getResourceUrl()] = $resource::getBreadcrumb(); + + $parentResourceRegistration = $resource::getParentResourceRegistration(); + $parentResource = $parentResourceRegistration?->getParentResource(); + $parentRecord = $this->getParentRecord(); + + while ($parentResourceRegistration && $parentRecord) { + $parentRecordTitle = $parentResource::hasRecordTitle() ? + $parentResource::getRecordTitle($parentRecord) : + $parentResource::getTitleCaseModelLabel(); + + if ($parentResource::hasPage('view') && $parentResource::canView($parentRecord)) { + $breadcrumbs = [ + $parentResource::getUrl('view', ['record' => $parentRecord]) => $parentRecordTitle, + ...$breadcrumbs, + ]; + } elseif ($parentResource::hasPage('edit') && $parentResource::canEdit($parentRecord)) { + $breadcrumbs = [ + $parentResource::getUrl('edit', ['record' => $parentRecord]) => $parentRecordTitle, + ...$breadcrumbs, + ]; + } else { + $breadcrumbs = [ + $parentRecordTitle, + ...$breadcrumbs, + ]; + } + + $breadcrumbs = [ + $parentResource::getUrl(null, [ + 'record' => $parentRecord, + ]) => $parentResource::getBreadcrumb(), + ...$breadcrumbs, + ]; + + $parentResourceRegistration = $parentResource::getParentResourceRegistration(); + + if ($parentResourceRegistration) { + $parentResource = $parentResourceRegistration->getParentResource(); + $parentRecord = $parentRecord->{$parentResourceRegistration->getInverseRelationshipName()}; + } + } + } if (filled($cluster = static::getCluster())) { return $cluster::unshiftClusterBreadcrumbs($breadcrumbs); diff --git a/packages/panels/src/Resources/Pages/ViewRecord.php b/packages/panels/src/Resources/Pages/ViewRecord.php index d26fe0e9897..492ac06c1b3 100644 --- a/packages/panels/src/Resources/Pages/ViewRecord.php +++ b/packages/panels/src/Resources/Pages/ViewRecord.php @@ -142,7 +142,7 @@ protected function configureEditAction(EditAction $action): void ->form(fn (Schema $form): Schema => static::getResource()::form($form)); if ($resource::hasPage('edit')) { - $action->url(fn (): string => static::getResource()::getUrl('edit', ['record' => $this->getRecord()])); + $action->url(fn (): string => $this->getResourceUrl('edit')); } } @@ -152,7 +152,7 @@ protected function configureForceDeleteAction(ForceDeleteAction $action): void $action ->authorize($resource::canForceDelete($this->getRecord())) - ->successRedirectUrl($resource::getUrl('index')); + ->successRedirectUrl($this->getResourceUrl()); } protected function configureReplicateAction(ReplicateAction $action): void @@ -173,7 +173,7 @@ protected function configureDeleteAction(DeleteAction $action): void $action ->authorize($resource::canDelete($this->getRecord())) - ->successRedirectUrl($resource::getUrl('index')); + ->successRedirectUrl($this->getResourceUrl()); } public function getTitle(): string | Htmlable diff --git a/packages/panels/src/Resources/ParentResourceRegistration.php b/packages/panels/src/Resources/ParentResourceRegistration.php new file mode 100644 index 00000000000..af170bd2d57 --- /dev/null +++ b/packages/panels/src/Resources/ParentResourceRegistration.php @@ -0,0 +1,67 @@ +childResource ??= debug_backtrace(limit: 3)[2]['class']; + $this->relationshipName ??= (string) str($this->childResource::getModel()) + ->classBasename() + ->camel() + ->plural(); + $this->inverseRelationshipName ??= (string) str($this->parentResource::getModel()) + ->classBasename() + ->camel(); + } + + public function getParentResource(): string + { + return $this->parentResource; + } + + public function getChildResource(): string + { + return $this->childResource; + } + + public function getRelationship(Model $parentRecord): HasOneOrMany | BelongsToMany + { + return $parentRecord->{$this->getRelationshipName()}(); + } + + public function getRelationshipName(): string + { + return $this->relationshipName; + } + + public function getInverseRelationshipName(): string + { + return $this->inverseRelationshipName; + } + + public function getParentRouteParameterName(): string + { + return Str::kebab($this->inverseRelationshipName); + } + + public function getSlug(): string + { + return Str::slug($this->relationshipName); + } + + public function getRouteName(): string + { + return Str::kebab($this->relationshipName); + } +} diff --git a/packages/panels/src/Resources/RelationManagers/RelationManager.php b/packages/panels/src/Resources/RelationManagers/RelationManager.php index b98d0913ee6..33976c7f0fa 100644 --- a/packages/panels/src/Resources/RelationManagers/RelationManager.php +++ b/packages/panels/src/Resources/RelationManagers/RelationManager.php @@ -217,7 +217,17 @@ protected function configureCreateAction(Actions\CreateAction $action): void { $action ->authorize(static fn (RelationManager $livewire): bool => (! $livewire->isReadOnly()) && $livewire->canCreate()) - ->form(fn (Schema $form): Schema => $this->form($form->columns(2))); + ->form(function (Schema $form): Schema { + $this->configureForm($form); + + return $form; + }); + + $relatedResource = static::getRelatedResource(); + + if ($relatedResource && $relatedResource::hasPage('create')) { + $action->url(fn (): string => $relatedResource::getUrl('create', [$relatedResource::getParentResourceRegistration()->getParentRouteParameterName() => $this->getOwnerRecord()])); + } } protected function configureDeleteAction(Actions\DeleteAction $action): void @@ -242,7 +252,17 @@ protected function configureEditAction(Actions\EditAction $action): void { $action ->authorize(static fn (RelationManager $livewire, Model $record): bool => (! $livewire->isReadOnly()) && $livewire->canEdit($record)) - ->form(fn (Schema $form): Schema => $this->form($form->columns(2))); + ->form(function (Schema $form): Schema { + $this->configureForm($form); + + return $form; + }); + + $relatedResource = static::getRelatedResource(); + + if ($relatedResource && $relatedResource::hasPage('edit')) { + $action->url(fn (Model $record): string => $relatedResource::getUrl('edit', ['record' => $record])); + } } protected function configureForceDeleteAction(Actions\ForceDeleteAction $action): void @@ -267,8 +287,22 @@ protected function configureViewAction(Actions\ViewAction $action): void { $action ->authorize(static fn (RelationManager $livewire, Model $record): bool => $livewire->canView($record)) - ->infolist(fn (Schema $infolist): Schema => $this->infolist($infolist->columns(2))) - ->form(fn (Schema $form): Schema => $this->form($form->columns(2))); + ->infolist(function (Schema $infolist): Schema { + $this->configureInfolist($infolist); + + return $infolist; + }) + ->form(function (Schema $form): Schema { + $this->configureForm($form); + + return $form; + }); + + $relatedResource = static::getRelatedResource(); + + if ($relatedResource && $relatedResource::hasPage('view')) { + $action->url(fn (Model $record): string => $relatedResource::getUrl('view', ['record' => $record])); + } } protected function configureTableBulkAction(BulkAction $action): void diff --git a/packages/panels/src/Resources/Resource.php b/packages/panels/src/Resources/Resource.php index 1c9efa182cb..c059f27884c 100644 --- a/packages/panels/src/Resources/Resource.php +++ b/packages/panels/src/Resources/Resource.php @@ -2,6 +2,7 @@ namespace Filament\Resources; +use Closure; use Exception; use Filament\Actions\Action; use Filament\Clusters\Cluster; @@ -147,6 +148,10 @@ public static function registerNavigationItems(): void return; } + if (static::getParentResource()) { + return; + } + if (! static::canAccess()) { return; } @@ -579,6 +584,10 @@ public static function getWidgets(): array public static function getRouteBaseName(?string $panel = null): string { + if ($parentResource = static::getParentResourceRegistration()) { + return $parentResource->getParentResource()::getRouteBaseName($panel) . '.' . $parentResource->getRouteName(); + } + $panel = $panel ? Filament::getPanel($panel) : Filament::getCurrentPanel(); $routeBaseName = (string) str(static::getSlug()) @@ -599,6 +608,20 @@ public static function getRecordRouteKeyName(): ?string public static function registerRoutes(Panel $panel): void { + if ($parentResource = static::getParentResourceRegistration()) { + $parentResource->getParentResource()::registerNestedRoutes($panel, function () use ($panel, $parentResource) { + Route::name($parentResource->getRouteName() . '.') + ->prefix('{' . $parentResource->getParentRouteParameterName() . '}/' . $parentResource->getSlug()) + ->group(function () use ($panel) { + foreach (static::getPages() as $name => $page) { + $page->registerRoute($panel)?->name($name); + } + }); + }); + + return; + } + if (filled($cluster = static::getCluster())) { Route::name($cluster::prependClusterRouteBaseName('resources.')) ->prefix($cluster::prependClusterSlug('')) @@ -623,6 +646,35 @@ public static function routes(Panel $panel): void }); } + public static function registerNestedRoutes(Panel $panel, Closure $routes): void + { + if ($parentResource = static::getParentResourceRegistration()) { + $parentResource->getParentResource()::registerNestedRoutes($panel, function () use ($parentResource, $routes) { + Route::name($parentResource->getRouteName() . '.') + ->prefix('{' . $parentResource->getParentRouteParameterName() . '}/' . $parentResource->getSlug()) + ->group($routes); + }); + + return; + } + + $nestedRoutes = fn () => Route::name(static::getRelativeRouteName() . '.') + ->prefix(static::getRoutePrefix()) + ->middleware(static::getRouteMiddleware($panel)) + ->withoutMiddleware(static::getWithoutRouteMiddleware($panel)) + ->group($routes); + + if (filled($cluster = static::getCluster())) { + Route::name($cluster::prependClusterRouteBaseName('resources.')) + ->prefix($cluster::prependClusterSlug('')) + ->group($nestedRoutes); + + return; + } + + Route::name('resources.')->group($nestedRoutes); + } + public static function getRelativeRouteName(): string { return (string) str(static::getSlug())->replace('/', '.'); @@ -691,8 +743,23 @@ public static function getSlug(): string /** * @param array $parameters */ - public static function getUrl(string $name = 'index', array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string + public static function getUrl(?string $name = null, array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string { + $record = $parameters['record'] ?? null; + $parentResource = static::getParentResource(); + + while (filled($parentResource)) { + $record = $record?->{$parentResource->getInverseRelationshipName()}; + $parameters[$parentResource->getParentRouteParameterName()] ??= $record; + $parameters['record'] ??= $record; + + $parentResource = $parentResource->getParentResource()::getParentResource(); + } + + if (blank($name)) { + return static::getIndexUrl($parameters, $isAbsolute, $panel, $tenant); + } + if (blank($panel) || Filament::getPanel($panel)->hasTenancy()) { $parameters['tenant'] ??= ($tenant ?? Filament::getTenant()); } @@ -702,6 +769,55 @@ public static function getUrl(string $name = 'index', array $parameters = [], bo return route("{$routeBaseName}.{$name}", $parameters, $isAbsolute); } + /** + * @param array $parameters + */ + public static function getIndexUrl(array $parameters = [], bool $isAbsolute = true, ?string $panel = null, ?Model $tenant = null): string + { + $parentResourceRegistration = static::getParentResource(); + + if ($parentResourceRegistration) { + $parentResource = $parentResourceRegistration->getParentResource(); + $parentRouteParameterName = $parentResourceRegistration->getParentRouteParameterName(); + + $record = $parameters[$parentRouteParameterName] ?? null; + unset($parameters[$parentRouteParameterName]); + + if ($parentResource::hasPage($relationshipPageName = $parentResourceRegistration->getRouteName())) { + return $parentResource::getUrl($relationshipPageName, [ + ...$parameters, + 'record' => $record, + ], $isAbsolute, $panel, $tenant); + } + + if ($parentResource::hasPage('view')) { + return $parentResource::getUrl('view', [ + 'activeRelationManager' => $parentResourceRegistration->getRelationshipName(), + ...$parameters, + 'record' => $record, + ], $isAbsolute, $panel, $tenant); + } + + if ($parentResource::hasPage('edit')) { + return $parentResource::getUrl('edit', [ + 'activeRelationManager' => $parentResourceRegistration->getRelationshipName(), + ...$parameters, + 'record' => $record, + ], $isAbsolute, $panel, $tenant); + } + + if ($parentResource::hasPage('index')) { + return $parentResource::getUrl('index', $parameters, $isAbsolute, $panel, $tenant); + } + } + + if (! static::hasPage('index')) { + throw new Exception('The resource [' . static::class . '] does not have an [index] page or define [getIndexUrl()] for alternative routing.'); + } + + return static::getUrl('index', $parameters, $isAbsolute, $panel, $tenant); + } + public static function hasPage(string $page): bool { return array_key_exists($page, static::getPages());