mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
[4.x] Add array record support for table grouping (#19253)
* Support array groups * Support array groups in bulk actions --------- Co-authored-by: Dan Harrin <git@danharrin.com>
This commit is contained in:
co-authored by
Dan Harrin
parent
8beb182ee9
commit
a6d0236e21
@@ -135,10 +135,44 @@ trait HasBulkActions
|
||||
*/
|
||||
public function getGroupedSelectableTableRecordKeys(?string $group): array
|
||||
{
|
||||
$query = $this->getFilteredTableQuery();
|
||||
|
||||
$tableGrouping = $this->getTableGrouping();
|
||||
|
||||
if (! $this->getTable()->hasQuery()) {
|
||||
$groupColumn = $tableGrouping->getColumn();
|
||||
|
||||
$records = $this->getTableRecords()
|
||||
->filter(static function (array $record) use ($groupColumn, $group): bool {
|
||||
$key = $record[$groupColumn] ?? null;
|
||||
$stringKey = filled($key) ? strval($key) : null;
|
||||
|
||||
return $stringKey === $group;
|
||||
});
|
||||
|
||||
if (! $this->getTable()->checksIfRecordIsSelectable()) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $records
|
||||
->map(fn (array $record): string => $this->getTableRecordKey($record))
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
return $records->reduce(
|
||||
function (array $carry, array $record): array {
|
||||
if (! $this->getTable()->isRecordSelectable($record)) {
|
||||
return $carry;
|
||||
}
|
||||
|
||||
$carry[] = $this->getTableRecordKey($record);
|
||||
|
||||
return $carry;
|
||||
},
|
||||
initial: [],
|
||||
);
|
||||
}
|
||||
|
||||
$query = $this->getFilteredTableQuery();
|
||||
|
||||
$tableGrouping->scopeQueryByKey($query, $group);
|
||||
|
||||
if (! $this->getTable()->checksIfRecordIsSelectable()) {
|
||||
|
||||
@@ -192,7 +192,10 @@ class Group extends Component
|
||||
->ucfirst();
|
||||
}
|
||||
|
||||
public function getDescription(Model $record, string | Htmlable | null $title): string | Htmlable | null
|
||||
/**
|
||||
* @param Model | array<string, mixed> $record
|
||||
*/
|
||||
public function getDescription(Model | array $record, string | Htmlable | null $title): string | Htmlable | null
|
||||
{
|
||||
if (! $this->getDescriptionFromRecordUsing) {
|
||||
return null;
|
||||
@@ -204,14 +207,17 @@ class Group extends Component
|
||||
'record' => $record,
|
||||
'title' => $title,
|
||||
],
|
||||
typedInjections: [
|
||||
typedInjections: ($record instanceof Model) ? [
|
||||
Model::class => $record,
|
||||
$record::class => $record,
|
||||
],
|
||||
] : [],
|
||||
);
|
||||
}
|
||||
|
||||
public function getStringKey(Model $record): ?string
|
||||
/**
|
||||
* @param Model | array<string, mixed> $record
|
||||
*/
|
||||
public function getStringKey(Model | array $record): ?string
|
||||
{
|
||||
$key = $this->getKey($record);
|
||||
|
||||
@@ -230,7 +236,10 @@ class Group extends Component
|
||||
return filled($key) ? strval($key) : null;
|
||||
}
|
||||
|
||||
public function getKey(Model $record): mixed
|
||||
/**
|
||||
* @param Model | array<string, mixed> $record
|
||||
*/
|
||||
public function getKey(Model | array $record): mixed
|
||||
{
|
||||
$column = $this->getColumn();
|
||||
|
||||
@@ -241,17 +250,20 @@ class Group extends Component
|
||||
'column' => $column,
|
||||
'record' => $record,
|
||||
],
|
||||
typedInjections: [
|
||||
typedInjections: ($record instanceof Model) ? [
|
||||
Model::class => $record,
|
||||
$record::class => $record,
|
||||
],
|
||||
] : [],
|
||||
);
|
||||
}
|
||||
|
||||
return Arr::get($record, $this->getColumn());
|
||||
}
|
||||
|
||||
public function getTitle(Model $record): string | Htmlable | null
|
||||
/**
|
||||
* @param Model | array<string, mixed> $record
|
||||
*/
|
||||
public function getTitle(Model | array $record): string | Htmlable | null
|
||||
{
|
||||
$column = $this->getColumn();
|
||||
|
||||
@@ -262,10 +274,10 @@ class Group extends Component
|
||||
'column' => $column,
|
||||
'record' => $record,
|
||||
],
|
||||
typedInjections: [
|
||||
typedInjections: ($record instanceof Model) ? [
|
||||
Model::class => $record,
|
||||
$record::class => $record,
|
||||
],
|
||||
] : [],
|
||||
);
|
||||
} else {
|
||||
$title = Arr::get($record, $column);
|
||||
@@ -332,7 +344,10 @@ class Group extends Component
|
||||
return $query->orderBy($this->getRelationshipAttribute(), $direction);
|
||||
}
|
||||
|
||||
public function scopeQuery(EloquentBuilder $query, Model $record): EloquentBuilder
|
||||
/**
|
||||
* @param Model | array<string, mixed> $record
|
||||
*/
|
||||
public function scopeQuery(EloquentBuilder $query, Model | array $record): EloquentBuilder
|
||||
{
|
||||
if ($this->scopeQueryUsing) {
|
||||
return $this->evaluate(
|
||||
@@ -342,10 +357,10 @@ class Group extends Component
|
||||
'query' => $query,
|
||||
'record' => $record,
|
||||
],
|
||||
typedInjections: [
|
||||
typedInjections: ($record instanceof Model) ? [
|
||||
Model::class => $record,
|
||||
$record::class => $record,
|
||||
],
|
||||
] : [],
|
||||
) ?? $query;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tests\Fixtures\Livewire;
|
||||
|
||||
use Filament\Actions\BulkAction;
|
||||
use Filament\Actions\Concerns\InteractsWithActions;
|
||||
use Filament\Actions\Contracts\HasActions;
|
||||
use Filament\Schemas\Concerns\InteractsWithSchemas;
|
||||
use Filament\Schemas\Contracts\HasSchemas;
|
||||
use Filament\Tables;
|
||||
use Filament\Tables\Columns\TextColumn;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Livewire\Component;
|
||||
|
||||
class GroupedCustomDataTable extends Component implements HasActions, HasSchemas, Tables\Contracts\HasTable
|
||||
{
|
||||
use InteractsWithActions;
|
||||
use InteractsWithSchemas;
|
||||
use Tables\Concerns\InteractsWithTable;
|
||||
|
||||
public function table(Table $table): Table
|
||||
{
|
||||
return $table
|
||||
->records(fn (): array => [
|
||||
1 => [
|
||||
'title' => 'First item',
|
||||
'status' => 'active',
|
||||
],
|
||||
2 => [
|
||||
'title' => 'Second item',
|
||||
'status' => 'inactive',
|
||||
],
|
||||
3 => [
|
||||
'title' => 'Third item',
|
||||
'status' => 'active',
|
||||
],
|
||||
4 => [
|
||||
'title' => 'Fourth item',
|
||||
'status' => 'inactive',
|
||||
],
|
||||
5 => [
|
||||
'title' => 'Fifth item',
|
||||
'status' => 'active',
|
||||
],
|
||||
])
|
||||
->columns([
|
||||
TextColumn::make('title'),
|
||||
TextColumn::make('status'),
|
||||
])
|
||||
->groups([
|
||||
Tables\Grouping\Group::make('status'),
|
||||
])
|
||||
->toolbarActions([
|
||||
BulkAction::make('delete')
|
||||
->action(fn () => null),
|
||||
]);
|
||||
}
|
||||
|
||||
public function render(): View
|
||||
{
|
||||
return view('livewire.table');
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Filament\Tables;
|
||||
use Filament\Tests\Fixtures\Livewire\GroupedCustomDataTable;
|
||||
use Filament\Tests\Fixtures\Livewire\PostsTable;
|
||||
use Filament\Tests\Fixtures\Livewire\UsersTable;
|
||||
use Filament\Tests\Fixtures\Models\Company;
|
||||
@@ -502,3 +503,96 @@ it('can group records with nullable `BelongsTo` -> `HasOneThrough` relationship'
|
||||
->set('tableGrouping', 'author.setting.theme')
|
||||
->assertCanSeeTableRecords($allPosts);
|
||||
});
|
||||
|
||||
it('can handle array records in `getKey()`', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getKey($arrayRecord))->toBe('active');
|
||||
});
|
||||
|
||||
it('can handle array records in `getStringKey()`', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getStringKey($arrayRecord))->toBe('active');
|
||||
});
|
||||
|
||||
it('can handle array records in `getTitle()`', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getTitle($arrayRecord))->toBe('active');
|
||||
});
|
||||
|
||||
it('can handle array records in `getDescription()`', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')
|
||||
->getDescriptionFromRecordUsing(fn (array $record): string => 'User: ' . $record['name'])
|
||||
->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getDescription($arrayRecord, 'Active'))->toBe('User: John');
|
||||
});
|
||||
|
||||
it('can use custom `getKeyFromRecordUsing()` with array records', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')
|
||||
->getKeyFromRecordUsing(fn (array $record): string => strtoupper($record['status']))
|
||||
->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getKey($arrayRecord))->toBe('ACTIVE')
|
||||
->and($group->getStringKey($arrayRecord))->toBe('ACTIVE');
|
||||
});
|
||||
|
||||
it('can use custom `getTitleFromRecordUsing()` with array records', function (): void {
|
||||
$livewire = livewire(PostsTable::class)->instance();
|
||||
$group = \Filament\Tables\Grouping\Group::make('status')
|
||||
->getTitleFromRecordUsing(fn (array $record): string => 'Status: ' . ucfirst($record['status']))
|
||||
->table($livewire->getTable());
|
||||
|
||||
$arrayRecord = ['__key' => '1', 'name' => 'John', 'status' => 'active'];
|
||||
|
||||
expect($group->getTitle($arrayRecord))->toBe('Status: Active');
|
||||
});
|
||||
|
||||
it('can get grouped selectable record keys for array tables', function (): void {
|
||||
livewire(GroupedCustomDataTable::class)
|
||||
->set('tableGrouping', 'status')
|
||||
->tap(function (Testable $testable): void {
|
||||
/** @var GroupedCustomDataTable $livewire */
|
||||
$livewire = $testable->instance();
|
||||
|
||||
$activeKeys = $livewire->getGroupedSelectableTableRecordKeys('active');
|
||||
$inactiveKeys = $livewire->getGroupedSelectableTableRecordKeys('inactive');
|
||||
|
||||
expect($activeKeys)
|
||||
->toHaveCount(3)
|
||||
->each->toBeString()
|
||||
->and($inactiveKeys)
|
||||
->toHaveCount(2)
|
||||
->each->toBeString();
|
||||
});
|
||||
});
|
||||
|
||||
it('returns an empty array for a non-existent group in array tables', function (): void {
|
||||
livewire(GroupedCustomDataTable::class)
|
||||
->set('tableGrouping', 'status')
|
||||
->tap(function (Testable $testable): void {
|
||||
/** @var GroupedCustomDataTable $livewire */
|
||||
$livewire = $testable->instance();
|
||||
|
||||
$keys = $livewire->getGroupedSelectableTableRecordKeys('nonexistent');
|
||||
|
||||
expect($keys)->toBeEmpty();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user