diff --git a/app/Filament/Resources/Applications/Tables/ApplicationsTable.php b/app/Filament/Resources/Applications/Tables/ApplicationsTable.php index 7299c21..74dddcb 100644 --- a/app/Filament/Resources/Applications/Tables/ApplicationsTable.php +++ b/app/Filament/Resources/Applications/Tables/ApplicationsTable.php @@ -6,6 +6,7 @@ namespace App\Filament\Resources\Applications\Tables; use App\Enums\ApplicationKind; use App\Enums\ApplicationStatus; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Application; use Filament\Actions\Action; use Filament\Actions\ActionGroup; @@ -93,6 +94,7 @@ class ApplicationsTable SelectFilter::make('call') ->label(__('panel.applications.fields.call')) ->relationship('call', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload(), diff --git a/app/Filament/Resources/Calls/CallResource.php b/app/Filament/Resources/Calls/CallResource.php index 00249ac..0abe7c1 100644 --- a/app/Filament/Resources/Calls/CallResource.php +++ b/app/Filament/Resources/Calls/CallResource.php @@ -12,6 +12,7 @@ use App\Filament\Resources\Calls\Pages\ListCalls; use App\Filament\Resources\Calls\RelationManagers\ApplicationsRelationManager; use App\Filament\Support\PreviewAction; use App\Filament\Support\TranslatableTabs; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Call; use App\Rules\UniqueTranslatedSlug; use BackedEnum; @@ -141,6 +142,7 @@ class CallResource extends Resource Select::make('project_id') ->label(__('panel.calls.fields.project')) ->relationship('project', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload() ->helperText(__('panel.calls.fields.project_hint')), diff --git a/app/Filament/Resources/Pages/Schemas/PageForm.php b/app/Filament/Resources/Pages/Schemas/PageForm.php index 8eb56d4..d185143 100644 --- a/app/Filament/Resources/Pages/Schemas/PageForm.php +++ b/app/Filament/Resources/Pages/Schemas/PageForm.php @@ -7,6 +7,7 @@ namespace App\Filament\Resources\Pages\Schemas; use App\Enums\PageStatus; use App\Filament\Resources\Pages\PageResource; use App\Filament\Support\TranslatableTabs; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Page; use App\Rules\UniqueTranslatedSlug; use App\Support\DuplicateTitles; @@ -112,6 +113,7 @@ class PageForm Select::make('category_id') ->label(__('panel.pages.fields.category')) ->relationship('category', 'name->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('name')) ->searchable() ->preload(), diff --git a/app/Filament/Resources/Pages/Tables/PagesTable.php b/app/Filament/Resources/Pages/Tables/PagesTable.php index 4d1c20a..f1ffe61 100644 --- a/app/Filament/Resources/Pages/Tables/PagesTable.php +++ b/app/Filament/Resources/Pages/Tables/PagesTable.php @@ -8,6 +8,7 @@ use App\Enums\PageStatus; use App\Filament\Support\PreviewAction; use App\Filament\Support\ReviewActions; use App\Filament\Support\ReviewStatusColumn; +use App\Filament\Support\TranslatedOptionLabel; use App\Filament\Support\TranslationStatusColumn; use App\Filament\Support\ViewsColumn; use App\Models\Page; @@ -127,6 +128,7 @@ class PagesTable SelectFilter::make('category') ->label(__('panel.pages.fields.category')) ->relationship('category', 'name->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('name')) ->searchable() ->preload(), diff --git a/app/Filament/Resources/Repositories/RepositoryResource.php b/app/Filament/Resources/Repositories/RepositoryResource.php index aabf03e..d548b78 100644 --- a/app/Filament/Resources/Repositories/RepositoryResource.php +++ b/app/Filament/Resources/Repositories/RepositoryResource.php @@ -9,6 +9,7 @@ use App\Filament\Resources\Repositories\Pages\CreateRepository; use App\Filament\Resources\Repositories\Pages\EditRepository; use App\Filament\Resources\Repositories\Pages\ListRepositories; use App\Filament\Resources\Repositories\Tables\RepositoriesTable; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Repository; use BackedEnum; use Filament\Forms\Components\Select; @@ -81,6 +82,7 @@ class RepositoryResource extends Resource Select::make('project_id') ->label(__('panel.repositories.fields.project')) ->relationship('project', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload(), diff --git a/app/Filament/Resources/Talks/TalkResource.php b/app/Filament/Resources/Talks/TalkResource.php index 1e93c12..17023b0 100644 --- a/app/Filament/Resources/Talks/TalkResource.php +++ b/app/Filament/Resources/Talks/TalkResource.php @@ -9,6 +9,7 @@ use App\Filament\Resources\Talks\Pages\CreateTalk; use App\Filament\Resources\Talks\Pages\EditTalk; use App\Filament\Resources\Talks\Pages\ListTalks; use App\Filament\Support\TranslatableTabs; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Talk; use BackedEnum; use Filament\Actions\BulkActionGroup; @@ -126,6 +127,7 @@ class TalkResource extends Resource Select::make('project_id') ->label(__('panel.talks.fields.project')) ->relationship('project', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload(), diff --git a/app/Filament/Resources/Testimonials/TestimonialResource.php b/app/Filament/Resources/Testimonials/TestimonialResource.php index 089bdf2..d9472da 100644 --- a/app/Filament/Resources/Testimonials/TestimonialResource.php +++ b/app/Filament/Resources/Testimonials/TestimonialResource.php @@ -9,6 +9,7 @@ use App\Filament\Resources\Testimonials\Pages\CreateTestimonial; use App\Filament\Resources\Testimonials\Pages\EditTestimonial; use App\Filament\Resources\Testimonials\Pages\ListTestimonials; use App\Filament\Support\TranslatableTabs; +use App\Filament\Support\TranslatedOptionLabel; use App\Models\Testimonial; use BackedEnum; use Filament\Actions\BulkActionGroup; @@ -137,12 +138,14 @@ class TestimonialResource extends Resource Select::make('project_id') ->label(__('panel.testimonials.fields.project')) ->relationship('project', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload(), Select::make('service_id') ->label(__('panel.testimonials.fields.service')) ->relationship('service', 'title->'.config('locales.default')) + ->getOptionLabelFromRecordUsing(TranslatedOptionLabel::using('title')) ->searchable() ->preload(), diff --git a/app/Filament/Support/TranslatedOptionLabel.php b/app/Filament/Support/TranslatedOptionLabel.php new file mode 100644 index 0000000..dbb1354 --- /dev/null +++ b/app/Filament/Support/TranslatedOptionLabel.php @@ -0,0 +1,29 @@ +relationship('category', 'name->es')` builds the list fine — that path + * goes through a query — but to validate a choice Filament reads the title + * back off the record as a property, `$record->{'name->es'}`, which does not + * exist. Every choice then came back without a label and was refused as "not + * in the list of allowed values", whichever one it was. Given this, Filament + * asks the record instead, in the language of whoever is using the panel. + * + * The JSON path stays in `relationship()`: it is still what the search box + * queries and the list is ordered by. + */ +final class TranslatedOptionLabel +{ + public static function using(string $attribute): Closure + { + return fn (Model $record): ?string => $record->getTranslation($attribute, app()->getLocale()); + } +} diff --git a/tests/Feature/Panel/TranslatedRelationshipSelectsTest.php b/tests/Feature/Panel/TranslatedRelationshipSelectsTest.php new file mode 100644 index 0000000..e842809 --- /dev/null +++ b/tests/Feature/Panel/TranslatedRelationshipSelectsTest.php @@ -0,0 +1,94 @@ +es` off the record + * as a property. See App\Filament\Support\TranslatedOptionLabel. + */ +class TranslatedRelationshipSelectsTest extends TestCase +{ + use FakesPireon, RefreshDatabase; + + protected function setUp(): void + { + parent::setUp(); + + $this->seed(RolePermissionSeeder::class); + Queue::fake(); + + $this->actingAs($this->userWithRoles('super_admin')); + } + + /** + * @return array + */ + public static function selects(): array + { + return [ + "a page's category" => [CreatePage::class, 'category_id', Category::class], + "a call's project" => [CreateCall::class, 'project_id', Project::class], + "a testimonial's project" => [CreateTestimonial::class, 'project_id', Project::class], + "a testimonial's service" => [CreateTestimonial::class, 'service_id', Service::class], + "a talk's project" => [CreateTalk::class, 'project_id', Project::class], + "a repository's project" => [CreateRepository::class, 'project_id', Project::class], + ]; + } + + /** + * @param class-string $page + * @param class-string $model + */ + #[Test] + #[DataProvider('selects')] + public function the_option_picked_is_accepted(string $page, string $field, string $model): void + { + $record = $model::factory()->create(); + + // Only this field is asserted: the rest of each form is left empty on + // purpose, and its own errors are not what this is about. + Livewire::test($page) + ->fillForm([$field => $record->getKey()]) + ->call('create') + ->assertHasNoFormErrors([$field]); + } + + #[Test] + public function the_option_reads_in_the_language_of_whoever_uses_the_panel(): void + { + $category = Category::factory()->create(['name' => ['es' => 'Novedades', 'en' => 'News']]); + + app()->setLocale('en'); + + $select = collect(Livewire::test(CreatePage::class)->instance()->form->getFlatComponents()) + ->first(fn ($component) => method_exists($component, 'getName') && $component->getName() === 'category_id'); + + $label = $select->getOptionLabelFromRecord($category); + + $this->assertSame('News', $label); + } +}