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());