diff --git a/app/Entity/Book.php b/app/Entity/Book.php index 30b6781f8..90e800443 100644 --- a/app/Entity/Book.php +++ b/app/Entity/Book.php @@ -13,6 +13,8 @@ namespace App\Entity; +use App\Enum\BookCategory; +use App\Form\BookType; use App\Grid\BookGrid; use App\Repository\BookRepository; use App\Responder\ExportGridToCsvResponder; @@ -31,6 +33,7 @@ #[ORM\Entity(repositoryClass: BookRepository::class)] #[AsResource( section: 'admin', + formType: BookType::class, templatesDir: '@SyliusAdminUi/crud', routePrefix: '/admin', operations: [ @@ -79,6 +82,9 @@ class Book implements ResourceInterface #[NotBlank] private ?string $authorName = null; + #[ORM\Column(type: 'enum', length: 255, nullable: true)] + private ?BookCategory $category = null; + #[ORM\Column(type: 'datetime_immutable')] private \DateTimeImmutable $createdAt; @@ -121,4 +127,14 @@ public function setCreatedAt(\DateTimeImmutable $createdAt): void { $this->createdAt = $createdAt; } + + public function getCategory(): ?BookCategory + { + return $this->category; + } + + public function setCategory(?BookCategory $category): void + { + $this->category = $category; + } } diff --git a/app/Enum/BookCategory.php b/app/Enum/BookCategory.php new file mode 100644 index 000000000..4609039d6 --- /dev/null +++ b/app/Enum/BookCategory.php @@ -0,0 +1,42 @@ +with(['authorName' => $authorName]); } + public function withCategory(BookCategory $category): self + { + return $this->with(['category' => $category]); + } + protected function defaults(): array|callable { return [ 'title' => ucfirst(self::faker()->words(3, true)), 'authorName' => self::faker()->firstName() . ' ' . self::faker()->lastName(), + 'category' => self::faker()->randomElement(BookCategory::cases()), ]; } } diff --git a/app/Form/BookType.php b/app/Form/BookType.php new file mode 100644 index 000000000..39682e6eb --- /dev/null +++ b/app/Form/BookType.php @@ -0,0 +1,50 @@ +add('title') + ->add('authorName') + ->add('category', EnumType::class, [ + 'class' => BookCategory::class, + 'choice_value' => fn (?BookCategory $enum) => $enum?->value, + 'choice_label' => fn (BookCategory $choice) => ucfirst($choice->value), + 'required' => false, + ]) + ; + } + + public function configureOptions(OptionsResolver $resolver): void + { + $resolver->setDefaults([ + 'data_class' => Book::class, + ]); + } + + public function getBlockPrefix(): string + { + return 'sylius_resource'; + } +} diff --git a/app/Grid/BookGrid.php b/app/Grid/BookGrid.php index 1efada35e..2a7ce2880 100644 --- a/app/Grid/BookGrid.php +++ b/app/Grid/BookGrid.php @@ -14,6 +14,7 @@ namespace App\Grid; use App\Entity\Book; +use App\Enum\BookCategory; use Sylius\Bundle\GridBundle\Builder\Action\Action; use Sylius\Bundle\GridBundle\Builder\Action\CreateAction; use Sylius\Bundle\GridBundle\Builder\Action\DeleteAction; @@ -22,36 +23,42 @@ use Sylius\Bundle\GridBundle\Builder\ActionGroup\BulkActionGroup; use Sylius\Bundle\GridBundle\Builder\ActionGroup\ItemActionGroup; use Sylius\Bundle\GridBundle\Builder\ActionGroup\MainActionGroup; +use Sylius\Bundle\GridBundle\Builder\Field\EnumField; use Sylius\Bundle\GridBundle\Builder\Field\StringField; +use Sylius\Bundle\GridBundle\Builder\Filter\EnumFilter; use Sylius\Bundle\GridBundle\Builder\Filter\StringFilter; use Sylius\Bundle\GridBundle\Builder\GridBuilderInterface; use Sylius\Bundle\GridBundle\Grid\AbstractGrid; -use Sylius\Bundle\GridBundle\Grid\ResourceAwareGridInterface; +use Sylius\Component\Grid\Attribute\AsGrid; -final class BookGrid extends AbstractGrid implements ResourceAwareGridInterface +#[AsGrid( + resourceClass: Book::class, + name: 'app_book', +)] +final class BookGrid extends AbstractGrid { - public static function getName(): string - { - return 'app_book'; - } - - public function buildGrid(GridBuilderInterface $gridBuilder): void + public function __invoke(GridBuilderInterface $gridBuilder): void { $gridBuilder ->orderBy('title') - ->addFilter( + ->withFilters( StringFilter::create('search', ['title', 'authorName']) ->setLabel('sylius.ui.search'), + EnumFilter::create(name: 'category', enumClass: BookCategory::class, field: 'category') + ->addFormOption('choice_value', fn (?BookCategory $enum) => $enum?->value) + ->addFormOption('choice_label', fn (BookCategory $choice) => ucfirst($choice->value)) + ->setLabel('app.ui.category'), ) - ->addField( + ->withFields( StringField::create('title') ->setLabel('app.ui.title') ->setSortable(true), - ) - ->addField( StringField::create('authorName') ->setLabel('app.ui.author_name') ->setSortable(true), + EnumField::create('category') + ->setLabel('app.ui.category') + ->setSortable(true), ) ->addActionGroup( MainActionGroup::create( @@ -78,9 +85,4 @@ public function buildGrid(GridBuilderInterface $gridBuilder): void ) ; } - - public function getResourceClass(): string - { - return Book::class; - } } diff --git a/config/packages/sylius_resource.yaml b/config/packages/sylius_resource.yaml index 9f33b60b4..ec063ba83 100644 --- a/config/packages/sylius_resource.yaml +++ b/config/packages/sylius_resource.yaml @@ -10,6 +10,7 @@ sylius_resource: # Configure your resources resources: - #app.book: - #classes: - #model: App\Entity\Book + app.book: + classes: + model: App\Entity\Book + form: App\Form\BookType diff --git a/config/reference.php b/config/reference.php index b0452a249..8b31be3a7 100644 --- a/config/reference.php +++ b/config/reference.php @@ -188,7 +188,7 @@ * only_exceptions?: bool|Param, // Default: false * only_main_requests?: bool|Param, // Default: false * dsn?: scalar|Param|null, // Default: "file:%kernel.cache_dir%/profiler" - * collect_serializer_data?: true|Param, // Default: true + * collect_serializer_data?: bool|Param, // Enables the serializer data collector and profiler panel. // Default: false * }, * workflows?: bool|array{ * enabled?: bool|Param, // Default: false @@ -255,6 +255,8 @@ * gc_maxlifetime?: scalar|Param|null, * save_path?: scalar|Param|null, // Defaults to "%kernel.cache_dir%/sessions" if the "handler_id" option is not null. * metadata_update_threshold?: int|Param, // Seconds to wait between 2 session metadata updates. // Default: 0 + * sid_length?: int|Param, // Deprecated: Setting the "framework.session.sid_length.sid_length" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. + * sid_bits_per_character?: int|Param, // Deprecated: Setting the "framework.session.sid_bits_per_character.sid_bits_per_character" configuration option is deprecated. It will be removed in version 8.0. No alternative is provided as PHP 8.4 has deprecated the related option. * }, * request?: bool|array{ // Request configuration * enabled?: bool|Param, // Default: false @@ -328,10 +330,11 @@ * }, * validation?: bool|array{ // Validation configuration * enabled?: bool|Param, // Default: true + * cache?: scalar|Param|null, // Deprecated: Setting the "framework.validation.cache.cache" configuration option is deprecated. It will be removed in version 8.0. * enable_attributes?: bool|Param, // Default: true * static_method?: list, * translation_domain?: scalar|Param|null, // Default: "validators" - * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|Param, // Default: "html5" + * email_validation_mode?: "html5"|"html5-allow-no-tld"|"strict"|"loose"|Param, // Default: "html5" * mapping?: array{ * paths?: list, * }, @@ -344,6 +347,9 @@ * services?: list, * }>, * }, + * annotations?: bool|array{ + * enabled?: bool|Param, // Default: false + * }, * serializer?: bool|array{ // Serializer configuration * enabled?: bool|Param, // Default: false * enable_attributes?: bool|Param, // Default: true @@ -375,7 +381,7 @@ * }, * property_info?: bool|array{ // Property info configuration * enabled?: bool|Param, // Default: true - * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. // Default: true + * with_constructor_extractor?: bool|Param, // Registers the constructor extractor. * }, * cache?: array{ // Cache configuration * prefix_seed?: scalar|Param|null, // Used to namespace cache keys when using several apps with the same shared backend. // Default: "_%kernel.project_dir%.%kernel.container_class%" @@ -1162,6 +1168,7 @@ * @psalm-type SecurityConfig = array{ * access_denied_url?: scalar|Param|null, // Default: null * session_fixation_strategy?: "none"|"migrate"|"invalidate"|Param, // Default: "migrate" + * hide_user_not_found?: bool|Param, // Deprecated: The "hide_user_not_found" option is deprecated and will be removed in 8.0. Use the "expose_security_errors" option instead. * expose_security_errors?: \Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::None|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::AccountStatus|\Symfony\Component\Security\Http\Authentication\ExposeSecurityLevel::All|Param, // Default: "none" * erase_credentials?: bool|Param, // Default: true * access_decision_manager?: array{ diff --git a/src/BootstrapAdminUi/config/app/grid/templates.php b/src/BootstrapAdminUi/config/app/grid/templates.php index 5e9a15b11..a10ef3591 100644 --- a/src/BootstrapAdminUi/config/app/grid/templates.php +++ b/src/BootstrapAdminUi/config/app/grid/templates.php @@ -32,6 +32,7 @@ 'exists' => '@SyliusBootstrapAdminUi/shared/grid/filter/exists.html.twig', 'select' => '@SyliusBootstrapAdminUi/shared/grid/filter/select.html.twig', 'string' => '@SyliusBootstrapAdminUi/shared/grid/filter/string.html.twig', + 'enum' => '@SyliusBootstrapAdminUi/shared/grid/filter/enum.html.twig', ], ], ]); diff --git a/src/BootstrapAdminUi/templates/shared/grid/filter/enum.html.twig b/src/BootstrapAdminUi/templates/shared/grid/filter/enum.html.twig new file mode 100644 index 000000000..d308c68b0 --- /dev/null +++ b/src/BootstrapAdminUi/templates/shared/grid/filter/enum.html.twig @@ -0,0 +1,3 @@ +{% form_theme form '@SyliusBootstrapAdminUi/shared/form_theme.html.twig' %} + +{{ form_row(form, { 'label': filter.label}) }} diff --git a/templates/book/show/content/page_body.html.twig b/templates/book/show/content/page_body.html.twig index ce6eb7c4a..26076fdb9 100644 --- a/templates/book/show/content/page_body.html.twig +++ b/templates/book/show/content/page_body.html.twig @@ -4,8 +4,8 @@
- {{ 'app.ui.author'|trans }}: - {{ book.authorName }} +

{{ 'app.ui.author'|trans }}: {{ book.authorName }}

+

{{ 'app.ui.category'|trans }}:{{ book.category.value }}

diff --git a/tests/Functional/BookTest.php b/tests/Functional/BookTest.php index b247ccb80..b39745749 100644 --- a/tests/Functional/BookTest.php +++ b/tests/Functional/BookTest.php @@ -5,6 +5,7 @@ namespace MainTests\Sylius\Functional; use App\Entity\Book; +use App\Enum\BookCategory; use App\Factory\BookFactory; use App\Factory\UserFactory; use Symfony\Bundle\FrameworkBundle\KernelBrowser; @@ -56,12 +57,14 @@ public function testBrowsingBooks(): void BookFactory::new() ->withTitle('The Shining') ->withAuthorName('Stephen King') + ->withCategory(BookCategory::HORROR) ->create() ; BookFactory::new() ->withTitle('Carrie') ->withAuthorName('Stephen King') + ->withCategory(BookCategory::HORROR) ->create() ; @@ -79,17 +82,20 @@ public function testBrowsingBooks(): void // Validate Table header $this->assertSelectorTextContains('.sylius-table-column-title', 'Title'); $this->assertSelectorTextContains('.sylius-table-column-authorName', 'Author name'); + $this->assertSelectorTextContains('.sylius-table-column-category', 'Category'); $this->assertSelectorTextContains('.sylius-table-column-actions', 'Actions'); // Validate Table data $this->assertSelectorTextContains('tr.item:first-child', 'Carrie'); $this->assertSelectorTextContains('tr.item:first-child', 'Stephen King'); + $this->assertSelectorTextContains('tr.item:first-child', 'horror'); $this->assertSelectorExists('tr.item:first-child [data-bs-title=Show]'); $this->assertSelectorExists('tr.item:first-child [data-bs-title=Edit]'); $this->assertSelectorExists('tr.item:first-child [data-bs-title=Delete]'); $this->assertSelectorTextContains('tr.item:last-child', 'The Shining'); $this->assertSelectorTextContains('tr.item:last-child', 'Stephen King'); + $this->assertSelectorTextContains('tr.item:last-child', 'horror'); $this->assertSelectorExists('tr.item:last-child [data-bs-title=Show]'); $this->assertSelectorExists('tr.item:last-child [data-bs-title=Edit]'); $this->assertSelectorExists('tr.item:last-child [data-bs-title=Delete]'); diff --git a/translations/messages.de.yaml b/translations/messages.de.yaml index 5e8a37503..8b27f63ff 100644 --- a/translations/messages.de.yaml +++ b/translations/messages.de.yaml @@ -8,6 +8,7 @@ app: book: Buch books: Bücher browsing_speakers: Referenten durchsuchen + category: Kategorie company_name: Firmenname conference: Konferenz conferences: Konferenzen diff --git a/translations/messages.de_AT.yaml b/translations/messages.de_AT.yaml index 5e8a37503..8b27f63ff 100644 --- a/translations/messages.de_AT.yaml +++ b/translations/messages.de_AT.yaml @@ -8,6 +8,7 @@ app: book: Buch books: Bücher browsing_speakers: Referenten durchsuchen + category: Kategorie company_name: Firmenname conference: Konferenz conferences: Konferenzen diff --git a/translations/messages.de_CH.yaml b/translations/messages.de_CH.yaml index 5e8a37503..8b27f63ff 100644 --- a/translations/messages.de_CH.yaml +++ b/translations/messages.de_CH.yaml @@ -8,6 +8,7 @@ app: book: Buch books: Bücher browsing_speakers: Referenten durchsuchen + category: Kategorie company_name: Firmenname conference: Konferenz conferences: Konferenzen diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 4ec6efa9f..618d27492 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -8,6 +8,7 @@ app: book: Book books: Books browsing_speakers: Browsing speakers + category: Category company_name: Company name conference: Conference conferences: Conferences diff --git a/translations/messages.fr.yaml b/translations/messages.fr.yaml index 7075c7c7e..45fb2e5d7 100644 --- a/translations/messages.fr.yaml +++ b/translations/messages.fr.yaml @@ -8,6 +8,7 @@ app: book: Livre books: Livres browsing_speakers: Parcourir les Conférencier-e-s + category: Catégorie company_name: Société conference: Conférence conferences: Conférences