diff --git a/packages/tables/resources/views/index.blade.php b/packages/tables/resources/views/index.blade.php index 2fb3fb1e37..bb1b867e53 100644 --- a/packages/tables/resources/views/index.blade.php +++ b/packages/tables/resources/views/index.blade.php @@ -1301,7 +1301,7 @@ @endphp @endforeach - @if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle) && ((! $records instanceof \Illuminate\Contracts\Pagination\Paginator) || (! $records->hasMorePages()))) + @if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle) && $this->shouldRenderTrailingGroupedTableSummary($previousRecord)) @php @@ -2353,7 +2353,7 @@ @endphp @endforeach - @if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle) && ((! $records instanceof \Illuminate\Contracts\Pagination\Paginator) || (! $records->hasMorePages()))) + @if ($hasSummary && (! $isReordering) && filled($previousRecordGroupTitle) && $this->shouldRenderTrailingGroupedTableSummary($previousRecord)) @php $groupColumn = $group->getColumn(); $groupScopedAllTableSummaryQuery = $group->scopeQuery($this->getAllTableSummaryQuery(), $previousRecord); diff --git a/packages/tables/src/Concerns/CanSummarizeRecords.php b/packages/tables/src/Concerns/CanSummarizeRecords.php index a53a9a8df4..6477e01a64 100644 --- a/packages/tables/src/Concerns/CanSummarizeRecords.php +++ b/packages/tables/src/Concerns/CanSummarizeRecords.php @@ -4,13 +4,68 @@ namespace Filament\Tables\Concerns; use Closure; use Filament\Support\Services\RelationshipJoiner; +use Illuminate\Contracts\Pagination\CursorPaginator; +use Illuminate\Contracts\Pagination\Paginator; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Query\Expression; use Illuminate\Support\Str; use stdClass; trait CanSummarizeRecords { + /** + * @param Model | array | null $lastRecord + */ + public function shouldRenderTrailingGroupedTableSummary(Model | array | null $lastRecord): bool + { + if ($lastRecord === null) { + return false; + } + + $records = $this->getTableRecords(); + + $isPaginated = ($records instanceof Paginator) || ($records instanceof CursorPaginator); + + if ((! $isPaginated) || (! $records->hasMorePages())) { + return true; + } + + $group = $this->getTableGrouping(); + + if (! $group) { + return true; + } + + $query = $this->getFilteredSortedTableQuery(); + + if ($query === null) { + return true; + } + + if ($records instanceof CursorPaginator) { + $nextCursor = $records->nextCursor(); + + if (! $nextCursor) { + return true; + } + + $nextPageFirstRecord = (clone $query) + ->cursorPaginate(perPage: 1, cursor: $nextCursor) + ->items()[0] ?? null; + } else { + $nextPageFirstRecord = (clone $query) + ->skip($records->currentPage() * $records->perPage()) + ->first(); + } + + if ($nextPageFirstRecord === null) { + return true; + } + + return $group->getStringKey($nextPageFirstRecord) !== $group->getStringKey($lastRecord); + } + public function getAllTableSummaryQuery(): ?Builder { return $this->getFilteredTableQuery(); diff --git a/tests/src/Fixtures/Livewire/PostsTableWithCursorPagination.php b/tests/src/Fixtures/Livewire/PostsTableWithCursorPagination.php new file mode 100644 index 0000000000..22c0bb5320 --- /dev/null +++ b/tests/src/Fixtures/Livewire/PostsTableWithCursorPagination.php @@ -0,0 +1,44 @@ +query(Post::query()) + ->paginationMode(PaginationMode::Cursor) + ->groups(fn () => [ + Tables\Grouping\Group::make('title'), + ]) + ->columns([ + Tables\Columns\TextColumn::make('title') + ->sortable(), + Tables\Columns\TextColumn::make('rating') + ->summarize([ + Tables\Columns\Summarizers\Sum::make('sum'), + ]), + ]); + } + + public function render(): View + { + return view('livewire.table'); + } +} diff --git a/tests/src/Tables/SummaryTest.php b/tests/src/Tables/SummaryTest.php index ace4b66f86..93a1a4dbf2 100644 --- a/tests/src/Tables/SummaryTest.php +++ b/tests/src/Tables/SummaryTest.php @@ -7,10 +7,10 @@ use Filament\Schemas\Contracts\HasSchemas; use Filament\Tables; use Filament\Tables\Table; use Filament\Tests\Fixtures\Livewire\PostsTable; +use Filament\Tests\Fixtures\Livewire\PostsTableWithCursorPagination; use Filament\Tests\Fixtures\Models\Post; use Filament\Tests\Tables\TestCase; use Illuminate\Contracts\View\View; -use Illuminate\Database\Query\Builder; use Livewire\Component; use function Filament\Tests\livewire; @@ -169,6 +169,46 @@ it('renders group summaries when page and all-table summaries are disabled', fun ->assertDontSeeHtml('fi-ta-summary-header-row'); }); +it('renders the trailing group summary when the next page starts a different group', function (): void { + Post::factory()->count(10)->create(['title' => 'A']); + Post::factory()->create(['title' => 'B']); + + livewire(PostsTable::class) + ->set('tableRecordsPerPage', 10) + ->set('tableGrouping', 'title') + ->assertSee('A summary') + ->assertDontSee('B summary'); +}); + +it('does not render the trailing group summary when the next page continues the same group', function (): void { + Post::factory()->count(15)->create(['title' => 'A']); + + livewire(PostsTable::class) + ->set('tableRecordsPerPage', 10) + ->set('tableGrouping', 'title') + ->assertDontSee('A summary'); +}); + +it('renders the trailing group summary with cursor pagination when the next page starts a different group', function (): void { + Post::factory()->count(10)->create(['title' => 'A']); + Post::factory()->create(['title' => 'B']); + + livewire(PostsTableWithCursorPagination::class) + ->set('tableRecordsPerPage', 10) + ->set('tableGrouping', 'title') + ->assertSee('A summary') + ->assertDontSee('B summary'); +}); + +it('does not render the trailing group summary with cursor pagination when the next page continues the same group', function (): void { + Post::factory()->count(15)->create(['title' => 'A']); + + livewire(PostsTableWithCursorPagination::class) + ->set('tableRecordsPerPage', 10) + ->set('tableGrouping', 'title') + ->assertDontSee('A summary'); +}); + class TestTableWithGroupSummariesOnly extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable { use InteractsWithActions;