diff --git a/packages/tables/docs/07-grouping.md b/packages/tables/docs/07-grouping.md index 03da8c8a8f..d6b3b69cb8 100644 --- a/packages/tables/docs/07-grouping.md +++ b/packages/tables/docs/07-grouping.md @@ -381,3 +381,21 @@ public function table(Table $table): Table ->groupingDirectionSettingHidden(); } ``` + +## Persisting the grouping in the user's session + +To persist the grouping in the user's session, use the `persistGroupInSession()` method: + +```php +use Filament\Tables\Table; + +public function table(Table $table): Table +{ + return $table + ->groups([ + 'status', + 'category', + ]) + ->persistGroupInSession(); +} +``` diff --git a/packages/tables/src/Concerns/CanGroupRecords.php b/packages/tables/src/Concerns/CanGroupRecords.php index cab39a44cb..2e5640bd6e 100644 --- a/packages/tables/src/Concerns/CanGroupRecords.php +++ b/packages/tables/src/Concerns/CanGroupRecords.php @@ -29,9 +29,24 @@ trait CanGroupRecords return $this->getTable()->getDefaultGroup(); } + public function updatedTableGrouping(): void + { + if ($this->getTable()->persistsGroupInSession()) { + session()->put( + $this->getTableGroupingSessionKey(), + $this->tableGrouping, + ); + } + + $this->resetPage(); + } + + /** + * @deprecated Use `updatedTableGrouping()` instead. + */ public function updatedTableGroupColumn(): void { - $this->resetPage(); + $this->updatedTableGrouping(); } public function getTableGroupingDirection(): ?string @@ -65,4 +80,11 @@ trait CanGroupRecords return $query; } + + public function getTableGroupingSessionKey(): string + { + $table = md5($this::class); + + return "tables.{$table}_grouping"; + } } diff --git a/packages/tables/src/Concerns/InteractsWithTable.php b/packages/tables/src/Concerns/InteractsWithTable.php index 95669e2ba7..658255543d 100644 --- a/packages/tables/src/Concerns/InteractsWithTable.php +++ b/packages/tables/src/Concerns/InteractsWithTable.php @@ -91,8 +91,24 @@ trait InteractsWithTable ); } - if ($this->getTable()->isDefaultGroupSelectable()) { - $this->tableGrouping = "{$this->getTable()->getDefaultGroup()->getId()}:asc"; + $shouldPersistGroupInSession = $this->getTable()->persistsGroupInSession(); + $groupingSessionKey = $this->getTableGroupingSessionKey(); + $hasPersistedGroupInSession = $shouldPersistGroupInSession && session()->exists($groupingSessionKey); + + if (blank($this->tableGrouping)) { + if ($hasPersistedGroupInSession) { + $sessionGrouping = session()->get($groupingSessionKey); + $this->tableGrouping = is_string($sessionGrouping) ? $sessionGrouping : null; + } elseif ($this->getTable()->isDefaultGroupSelectable()) { + $this->tableGrouping = "{$this->getTable()->getDefaultGroup()->getId()}:asc"; + } + } + + if ($shouldPersistGroupInSession) { + session()->put( + $groupingSessionKey, + $this->tableGrouping, + ); } $shouldPersistSearchInSession = $this->getTable()->persistsSearchInSession(); diff --git a/packages/tables/src/Table/Concerns/CanGroupRecords.php b/packages/tables/src/Table/Concerns/CanGroupRecords.php index ad141db00e..48f4622346 100644 --- a/packages/tables/src/Table/Concerns/CanGroupRecords.php +++ b/packages/tables/src/Table/Concerns/CanGroupRecords.php @@ -25,6 +25,8 @@ trait CanGroupRecords protected bool | Closure $isGroupsOnly = false; + protected bool | Closure | null $persistsGroupInSession = false; + protected bool | Closure $areGroupingSettingsInDropdownOnDesktop = false; protected bool | Closure $areGroupingSettingsHidden = false; @@ -104,6 +106,13 @@ trait CanGroupRecords return $this; } + public function persistGroupInSession(bool | Closure $condition = true): static + { + $this->persistsGroupInSession = $condition; + + return $this; + } + public function getGroupRecordsTriggerAction(): Action { $action = Action::make('groupRecords') @@ -213,4 +222,9 @@ trait CanGroupRecords { return (bool) $this->evaluate($this->isGroupsOnly); } + + public function persistsGroupInSession(): bool + { + return (bool) $this->evaluate($this->persistsGroupInSession); + } } diff --git a/tests/src/Fixtures/Livewire/PostsTableWithGroupPersistedInSession.php b/tests/src/Fixtures/Livewire/PostsTableWithGroupPersistedInSession.php new file mode 100644 index 0000000000..6f1e4b5c78 --- /dev/null +++ b/tests/src/Fixtures/Livewire/PostsTableWithGroupPersistedInSession.php @@ -0,0 +1,45 @@ +query(Post::query()) + ->groups(static fn () => [ + Tables\Grouping\Group::make('title'), + Tables\Grouping\Group::make('author.name'), + ]) + ->defaultGroup('title') + ->persistGroupInSession() + ->columns([ + Tables\Columns\TextColumn::make('title')->sortable(), + Tables\Columns\TextColumn::make('author.name')->sortable(), + ]); + } + + public function render(): View + { + return view('livewire.table'); + } +} diff --git a/tests/src/Tables/GroupingTest.php b/tests/src/Tables/GroupingTest.php index b5bc4edc30..98a287c27c 100644 --- a/tests/src/Tables/GroupingTest.php +++ b/tests/src/Tables/GroupingTest.php @@ -3,6 +3,7 @@ use Filament\Tables\Grouping\Group; use Filament\Tests\Fixtures\Livewire\GroupedCustomDataTable; use Filament\Tests\Fixtures\Livewire\PostsTable; +use Filament\Tests\Fixtures\Livewire\PostsTableWithGroupPersistedInSession; use Filament\Tests\Fixtures\Livewire\PostsTableWithoutSummarizers; use Filament\Tests\Fixtures\Livewire\TicketMessagesTable; use Filament\Tests\Fixtures\Livewire\UsersTable; @@ -18,6 +19,7 @@ use Filament\Tests\Fixtures\Models\TicketMessage; use Filament\Tests\Fixtures\Models\User; use Filament\Tests\Tables\TestCase; use Livewire\Features\SupportTesting\Testable; +use Livewire\Livewire; use function Filament\Tests\livewire; @@ -961,3 +963,87 @@ it('can group records by `HasOneThrough` -> `BelongsTo` relationship that uses ` ->set('tableGrouping', 'setting.languageWithTrashed.name') ->assertCanSeeTableRecords($sortedUsers, inOrder: true); }); + +it('resets pagination when `$tableGrouping` is updated', function (): void { + Post::factory()->count(20)->create(); + + livewire(PostsTable::class) + ->call('setPage', 2) + ->assertSet('paginators.page', 2) + ->set('tableGrouping', 'title') + ->assertSet('paginators.page', 1); +}); + +describe('session persistence', function (): void { + it('defaults `persistsGroupInSession()` to `false`', function (): void { + livewire(PostsTable::class) + ->tap(function (Testable $testable): void { + /** @var PostsTable $livewire */ + $livewire = $testable->instance(); + + expect($livewire->getTable()->persistsGroupInSession())->toBeFalse(); + }); + }); + + it('can enable grouping persistence with `persistGroupInSession()`', function (): void { + livewire(PostsTableWithGroupPersistedInSession::class) + ->tap(function (Testable $testable): void { + /** @var PostsTableWithGroupPersistedInSession $livewire */ + $livewire = $testable->instance(); + + expect($livewire->getTable()->persistsGroupInSession())->toBeTrue(); + }); + }); + + it('scopes `getTableGroupingSessionKey()` to the component class', function (): void { + /** @var PostsTableWithGroupPersistedInSession $livewire */ + $livewire = livewire(PostsTableWithGroupPersistedInSession::class)->instance(); + + expect($livewire->getTableGroupingSessionKey()) + ->toBe('tables.' . md5($livewire::class) . '_grouping'); + }); + + it('can persist the grouping in the user\'s session', function (): void { + Post::factory()->count(10)->create(); + + livewire(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', 'title:asc') + ->set('tableGrouping', 'author.name:desc') + ->assertSet('tableGrouping', 'author.name:desc'); + + livewire(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', 'author.name:desc'); + }); + + it('can clear the persisted grouping in the user\'s session', function (): void { + Post::factory()->count(10)->create(); + + livewire(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', 'title:asc') + ->set('tableGrouping', null) + ->assertSet('tableGrouping', null); + + livewire(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', null); + }); + + it('does not override explicit `$tableGrouping` with the persisted grouping', function (): void { + livewire(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', 'title:asc'); + + Livewire::withQueryParams(['grouping' => 'author.name:desc']) + ->test(PostsTableWithGroupPersistedInSession::class) + ->assertSet('tableGrouping', 'author.name:desc'); + }); + + it('does not persist the grouping in the user\'s session by default', function (): void { + Post::factory()->count(10)->create(); + + livewire(PostsTable::class) + ->set('tableGrouping', 'title') + ->assertSet('tableGrouping', 'title'); + + livewire(PostsTable::class) + ->assertSet('tableGrouping', null); + }); +});