diff --git a/docs/content/2.essentials/4.api-reference.md b/docs/content/2.essentials/4.api-reference.md index 318aeec4d..964b1c89a 100644 --- a/docs/content/2.essentials/4.api-reference.md +++ b/docs/content/2.essentials/4.api-reference.md @@ -14,6 +14,8 @@ navigation: | `positionIdentifier(string)` | Field name for drag-drop ordering | Yes | | `columns(array)` | Define board columns | Yes | | `recordTitleAttribute(string)` | Field name for card titles | | +| `cardLabel(string)` | Name of a single card, e.g. "Task" | | +| `pluralCardLabel(string)` | Name of a set of cards, e.g. "Tasks" | | | `cardSchema(Closure)` | Rich card content using Schema | | | `cardActions(array)` | Actions available on each card | | | `columnActions(array)` | Actions available on column headers | | @@ -48,6 +50,8 @@ public function board(Board $board): Board ```php ->recordTitleAttribute('title') // Card title field +->cardLabel('Task') // Defaults to the model label +->pluralCardLabel('Tasks') // Defaults to the plural model label ->cardSchema(fn(Schema $schema) => $schema // Rich card content ->components([ TextEntry::make('description'), diff --git a/resources/lang/de/flowforge.php b/resources/lang/de/flowforge.php index 6032e5b9c..edbc6694f 100644 --- a/resources/lang/de/flowforge.php +++ b/resources/lang/de/flowforge.php @@ -2,7 +2,7 @@ return [ 'loading_more_cards' => 'Lade mehr Karten...', - 'no_cards_in_column' => 'Keine :cardLabel in dieser Spalte', + 'no_cards_in_column' => 'Keine :CardLabel in dieser Spalte', 'cards_count' => '{0} :cards|{1} :card|[2,*] :cards', 'card_label' => 'Datensatz', 'plural_card_label' => 'Datensätze', diff --git a/src/Board.php b/src/Board.php index 564ee28a8..797b867a2 100644 --- a/src/Board.php +++ b/src/Board.php @@ -11,6 +11,7 @@ use Relaticle\Flowforge\Concerns\HasBoardColumns; use Relaticle\Flowforge\Concerns\HasBoardFilters; use Relaticle\Flowforge\Concerns\HasBoardRecords; +use Relaticle\Flowforge\Concerns\HasCardLabels; use Relaticle\Flowforge\Concerns\HasCardSchema; use Relaticle\Flowforge\Concerns\InteractsWithKanbanQuery; use Relaticle\Flowforge\Contracts\HasBoard; @@ -23,6 +24,7 @@ class Board extends ViewComponent use HasBoardColumns; use HasBoardFilters; use HasBoardRecords; + use HasCardLabels; use HasCardSchema; use InteractsWithKanbanQuery; @@ -101,8 +103,8 @@ public function getViewData(): array 'config' => [ 'recordTitleAttribute' => $this->getRecordTitleAttribute(), 'columnIdentifierAttribute' => $this->getColumnIdentifierAttribute(), - 'cardLabel' => __('flowforge::flowforge.card_label'), - 'pluralCardLabel' => __('flowforge::flowforge.plural_card_label'), + 'cardLabel' => $this->getCardLabel(), + 'pluralCardLabel' => $this->getPluralCardLabel(), 'headerToolbar' => $this->hasHeaderToolbar(), ], ]; diff --git a/src/Concerns/HasCardLabels.php b/src/Concerns/HasCardLabels.php new file mode 100644 index 000000000..a5c0b6b0a --- /dev/null +++ b/src/Concerns/HasCardLabels.php @@ -0,0 +1,114 @@ +cardLabel = $label; + + return $this; + } + + /** + * Name a set of cards, e.g. "Tasks". Defaults to the plural model label. + */ + public function pluralCardLabel(string | Closure | null $label): static + { + $this->pluralCardLabel = $label; + + return $this; + } + + public function getCardLabel(): string + { + $label = $this->evaluate($this->cardLabel); + + if (filled($label)) { + return $label; + } + + $resource = $this->getCardResource(); + + if ($resource !== null) { + return $resource::getModelLabel(); + } + + $model = $this->getCardModel(); + + if ($model !== null) { + return Str::title(Str::snake(class_basename($model), ' ')); + } + + return __('flowforge::flowforge.card_label'); + } + + public function getPluralCardLabel(): string + { + $label = $this->evaluate($this->pluralCardLabel); + + if (filled($label)) { + return $label; + } + + $resource = $this->getCardResource(); + + if ($resource !== null) { + return $resource::getPluralModelLabel(); + } + + if ($this->getCardModel() !== null) { + return Str::plural($this->getCardLabel()); + } + + return __('flowforge::flowforge.plural_card_label'); + } + + protected function getCardModel(): ?Model + { + return $this->getQuery()?->getModel(); + } + + /** + * The Filament resource describing the cards, either the one the board page + * belongs to or the one registered for the model in the current panel. + * + * @return class-string|null + */ + protected function getCardResource(): ?string + { + $livewire = $this->getLivewire(); + + if ($livewire instanceof ResourcePage) { + return $livewire::getResource(); + } + + $model = $this->getCardModel(); + + if ($model === null || ! class_exists(Filament::class)) { + return null; + } + + if (Filament::getCurrentPanel() === null) { + return null; + } + + return Filament::getModelResource($model); + } +} diff --git a/tests/Feature/CardLabelTest.php b/tests/Feature/CardLabelTest.php new file mode 100644 index 000000000..06971dd56 --- /dev/null +++ b/tests/Feature/CardLabelTest.php @@ -0,0 +1,46 @@ +instance()->getBoard(); + + expect($board->getCardLabel())->toBe(TestResource::getModelLabel()) + ->and($board->getPluralCardLabel())->toBe(TestResource::getPluralModelLabel()); + }); + + test('names the cards after the resource registered for the model', function () { + $board = Livewire::test(TestBoard::class)->instance()->getBoard(); + + expect($board->getCardLabel())->toBe('Task') + ->and($board->getPluralCardLabel())->toBe('Tasks'); + }); + + test('explicit labels win over the model', function () { + $board = Livewire::test(TestBoard::class)->instance()->getBoard() + ->cardLabel('Ticket') + ->pluralCardLabel('Tickets'); + + expect($board->getCardLabel())->toBe('Ticket') + ->and($board->getPluralCardLabel())->toBe('Tickets'); + }); + + test('falls back to the translated label without a query', function () { + $board = Livewire::test(TestBoard::class)->instance()->getBoard()->query(fn () => null); + + expect($board->getCardLabel())->toBe(__('flowforge::flowforge.card_label')) + ->and($board->getPluralCardLabel())->toBe(__('flowforge::flowforge.plural_card_label')); + }); + + test('the empty column names the cards', function () { + Livewire::test(TestBoard::class) + ->assertStatus(200) + ->assertSee('No tasks in this column'); + }); +}); diff --git a/tests/Unit/TranslationTest.php b/tests/Unit/TranslationTest.php new file mode 100644 index 000000000..643208684 --- /dev/null +++ b/tests/Unit/TranslationTest.php @@ -0,0 +1,15 @@ +setLocale('de'); + + $html = Blade::render('', [ + 'label' => __('flowforge::flowforge.plural_card_label'), + ]); + + expect($html)->toContain('Keine Datensätze in dieser Spalte'); +});