From f279be896efc90949ae9d350f49dc766c914a6fc Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Thu, 6 Mar 2025 16:58:25 +0100 Subject: [PATCH 1/5] storybook: add Book and Review entities --- api/migrations/Version20250306152729.php | 37 +++++++++++++++ api/src/Entity/Book.php | 58 ++++++++++++++++++++++++ api/src/Entity/Review.php | 49 ++++++++++++++++++++ 3 files changed, 144 insertions(+) create mode 100644 api/migrations/Version20250306152729.php create mode 100644 api/src/Entity/Book.php create mode 100644 api/src/Entity/Review.php diff --git a/api/migrations/Version20250306152729.php b/api/migrations/Version20250306152729.php new file mode 100644 index 00000000..fd915fce --- /dev/null +++ b/api/migrations/Version20250306152729.php @@ -0,0 +1,37 @@ +addSql('CREATE TABLE book (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, isbn VARCHAR(255) DEFAULT NULL, title VARCHAR(255) NOT NULL, description TEXT NOT NULL, author VARCHAR(255) NOT NULL, publication_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE TABLE review (id INT GENERATED BY DEFAULT AS IDENTITY NOT NULL, rating SMALLINT NOT NULL, body TEXT NOT NULL, author VARCHAR(255) NOT NULL, publication_date TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, book_id INT DEFAULT NULL, PRIMARY KEY(id))'); + $this->addSql('CREATE INDEX IDX_794381C616A2B381 ON review (book_id)'); + $this->addSql('ALTER TABLE review ADD CONSTRAINT FK_794381C616A2B381 FOREIGN KEY (book_id) REFERENCES book (id) NOT DEFERRABLE INITIALLY IMMEDIATE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('CREATE SCHEMA public'); + $this->addSql('ALTER TABLE review DROP CONSTRAINT FK_794381C616A2B381'); + $this->addSql('DROP TABLE book'); + $this->addSql('DROP TABLE review'); + } +} diff --git a/api/src/Entity/Book.php b/api/src/Entity/Book.php new file mode 100644 index 00000000..22191ed6 --- /dev/null +++ b/api/src/Entity/Book.php @@ -0,0 +1,58 @@ +reviews = new ArrayCollection(); + } + + public function getId(): ?int + { + return $this->id; + } +} diff --git a/api/src/Entity/Review.php b/api/src/Entity/Review.php new file mode 100644 index 00000000..3f716dc1 --- /dev/null +++ b/api/src/Entity/Review.php @@ -0,0 +1,49 @@ +id; + } +} From de512e90c4816981bd27c301aa4e06b3ef611168 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Thu, 6 Mar 2025 17:12:17 +0100 Subject: [PATCH 2/5] storybook: improve entities attributes --- api/src/Entity/Book.php | 21 ++++++++++++++++++++- api/src/Entity/Review.php | 22 +++++++++++++++++++++- 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/api/src/Entity/Book.php b/api/src/Entity/Book.php index 22191ed6..e51d68a8 100644 --- a/api/src/Entity/Book.php +++ b/api/src/Entity/Book.php @@ -3,13 +3,31 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\ApiProperty; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; use Doctrine\Common\Collections\ArrayCollection; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; -#[ApiResource] +#[ApiResource(mercure: true)] #[ORM\Entity] +#[ApiFilter(OrderFilter::class, properties: [ + 'id' => 'ASC', + 'isbn' => 'ASC', + 'title' => 'ASC', + 'author' => 'ASC', + 'publicationDate' => 'DESC' +])] +#[ApiFilter(SearchFilter::class, properties: [ + 'id' => 'exact', + 'title' => 'ipartial', + 'author' => 'ipartial' +])] +#[ApiFilter(DateFilter::class, properties: ['publicationDate'])] class Book { /** The ID of this book */ @@ -25,6 +43,7 @@ class Book /** The title of this book */ #[ORM\Column] #[Assert\NotBlank] + #[ApiProperty(iris: ['http://schema.org/name'])] public string $title = ''; /** The description of this book */ diff --git a/api/src/Entity/Review.php b/api/src/Entity/Review.php index 3f716dc1..dc7de3f2 100644 --- a/api/src/Entity/Review.php +++ b/api/src/Entity/Review.php @@ -3,12 +3,31 @@ namespace App\Entity; use ApiPlatform\Metadata\ApiResource; +use ApiPlatform\Metadata\ApiProperty; use Doctrine\ORM\Mapping as ORM; use Symfony\Component\Validator\Constraints as Assert; +use ApiPlatform\Metadata\ApiFilter; +use ApiPlatform\Doctrine\Orm\Filter\DateFilter; +use ApiPlatform\Doctrine\Orm\Filter\NumericFilter; +use ApiPlatform\Doctrine\Orm\Filter\SearchFilter; +use ApiPlatform\Doctrine\Orm\Filter\OrderFilter; -#[ApiResource] +#[ApiResource(mercure: true)] #[ORM\Entity] +#[ApiFilter(OrderFilter::class, properties: [ + 'id' => 'ASC', + 'rating' => 'ASC', + 'author' => 'ASC', + 'publicationDate' => 'DESC' +])] +#[ApiFilter(SearchFilter::class, properties: [ + 'id' => 'exact', + 'body' => 'ipartial', + 'author' => 'ipartial' +])] +#[ApiFilter(NumericFilter::class, properties: ['rating'])] +#[ApiFilter(DateFilter::class, properties: ['publicationDate'])] class Review { /** The ID of this review */ @@ -35,6 +54,7 @@ class Review /** The publication date of this review */ #[ORM\Column] #[Assert\NotNull] + #[ApiProperty(iris: ['http://schema.org/name'])] public ?\DateTimeImmutable $publicationDate = null; /** The book this review is about */ From 5182f885986a065bd1008f39e39e540760900b01 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Thu, 6 Mar 2025 17:28:14 +0100 Subject: [PATCH 3/5] storybook: add story using Field and Input Guessers --- src/stories/Custom.stories.tsx | 53 --------- src/stories/custom/UsingGuessers.stories.tsx | 115 +++++++++++++++++++ 2 files changed, 115 insertions(+), 53 deletions(-) delete mode 100644 src/stories/Custom.stories.tsx create mode 100644 src/stories/custom/UsingGuessers.stories.tsx diff --git a/src/stories/Custom.stories.tsx b/src/stories/Custom.stories.tsx deleted file mode 100644 index a68b6281..00000000 --- a/src/stories/Custom.stories.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import React from 'react'; -import { HydraAdmin } from '../hydra'; -import ResourceGuesser from '../core/ResourceGuesser'; -import ListGuesser from '../list/ListGuesser'; -import ShowGuesser from '../show/ShowGuesser'; -import FieldGuesser from '../field/FieldGuesser'; -import EditGuesser from '../edit/EditGuesser'; -import InputGuesser from '../input/InputGuesser'; -import CreateGuesser from '../create/CreateGuesser'; - -export default { - title: 'Admin/Custom', - parameters: { - layout: 'fullscreen', - }, -}; - -const GreetingList = () => ( - - - -); - -const GreetingShow = () => ( - - - -); - -const GreetingEdit = () => ( - - - -); - -const GreetingCreate = () => ( - - - -); - -export const Custom = () => ( - - - -); diff --git a/src/stories/custom/UsingGuessers.stories.tsx b/src/stories/custom/UsingGuessers.stories.tsx new file mode 100644 index 00000000..15f8e57b --- /dev/null +++ b/src/stories/custom/UsingGuessers.stories.tsx @@ -0,0 +1,115 @@ +import React from 'react'; +import { HydraAdmin } from '../../hydra'; +import ResourceGuesser from '../../core/ResourceGuesser'; +import ListGuesser from '../../list/ListGuesser'; +import ShowGuesser from '../../show/ShowGuesser'; +import FieldGuesser from '../../field/FieldGuesser'; +import EditGuesser from '../../edit/EditGuesser'; +import InputGuesser from '../../input/InputGuesser'; +import CreateGuesser from '../../create/CreateGuesser'; + +export default { + title: 'Admin/Custom/UsingGuessers', + parameters: { + layout: 'fullscreen', + }, +}; + +const BookCreate = () => ( + + + + + + + +); + +const BookEdit = () => ( + + + + + + + +); + +const BookShow = () => ( + + + + + + + + +); + +const BookList = () => ( + + + + + + + +); + +const ReviewCreate = () => ( + + + + + + + +); + +const ReviewEdit = () => ( + + + + + + + +); + +const ReviewShow = () => ( + + + + + + + +); + +const ReviewList = () => ( + + + + + + +); + +export const UsingGuessers = () => ( + + + + +); From 9e367789c2933d91edcfc08ad5d2ae7acec91ddd Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Thu, 6 Mar 2025 18:20:40 +0100 Subject: [PATCH 4/5] storybook: add Advanced Customization story --- .../custom/AdvancedCustomization.stories.tsx | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 src/stories/custom/AdvancedCustomization.stories.tsx diff --git a/src/stories/custom/AdvancedCustomization.stories.tsx b/src/stories/custom/AdvancedCustomization.stories.tsx new file mode 100644 index 00000000..c3c90b84 --- /dev/null +++ b/src/stories/custom/AdvancedCustomization.stories.tsx @@ -0,0 +1,247 @@ +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; +import ReviewsIcon from '@mui/icons-material/Reviews'; +import { Rating, Stack } from '@mui/material'; +import React from 'react'; +import type { InputProps } from 'react-admin'; +import { + AutocompleteInput, + Create, + Datagrid, + DateField, + Edit, + Labeled, + Layout, + List, + NumberField, + ReferenceArrayField, + ReferenceField, + ReferenceInput, + Show, + SimpleForm, + SimpleList, + SimpleShowLayout, + TabbedShowLayout, + TextField, + TextInput, + WithRecord, + WrapperField, + defaultDarkTheme, + defaultLightTheme, + required, + useInput, +} from 'react-admin'; +import ResourceGuesser from '../../core/ResourceGuesser'; +import FieldGuesser from '../../field/FieldGuesser'; +import { HydraAdmin } from '../../hydra'; +import InputGuesser from '../../input/InputGuesser'; + +export default { + title: 'Admin/Custom/AdvancedCustomization', + parameters: { + layout: 'fullscreen', + }, +}; + +const BookCreate = () => ( + + + + + + + + + + + +); + +const BookEdit = () => ( + + + + + + + + + + + +); + +const BookShow = () => ( + + + + + + + + + + + + + + + review.author + .split(' ') + .map((name: string) => name[0]) + .join('') + } + // eslint-disable-next-line react/no-unstable-nested-components + tertiaryText={(review) => ( + + )} + /> + + + + +); + +const BookList = () => ( + + + + + + + + + +); + +const RatingInput = (props: InputProps) => { + const { field } = useInput(props); + return ( + { + field.onChange(value); + }} + /> + ); +}; + +const filterToBookQuery = (searchText: string) => ({ + title: `%${searchText}%`, +}); + +const ReviewCreate = () => ( + + + + + + + + + + + + +); + +const ReviewEdit = () => ( + + + + + + + + + + + + + + + +); + +const ReviewShow = () => ( + + + + + + + ( + + )} + /> + + + + +); + +const ReviewList = () => ( + + + + + + ( + + )} + /> + + + + +); + +export const AdvancedCustomization = () => ( + + + + +); From 24143de865cf28f0c366d93744605b36ec82a9c3 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste Kaiser Date: Mon, 10 Mar 2025 16:03:14 +0100 Subject: [PATCH 5/5] storybook: add custom menu icons to the guessers story --- src/stories/custom/UsingGuessers.stories.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/stories/custom/UsingGuessers.stories.tsx b/src/stories/custom/UsingGuessers.stories.tsx index 15f8e57b..79a59bee 100644 --- a/src/stories/custom/UsingGuessers.stories.tsx +++ b/src/stories/custom/UsingGuessers.stories.tsx @@ -1,3 +1,5 @@ +import AutoStoriesIcon from '@mui/icons-material/AutoStories'; +import ReviewsIcon from '@mui/icons-material/Reviews'; import React from 'react'; import { HydraAdmin } from '../../hydra'; import ResourceGuesser from '../../core/ResourceGuesser'; @@ -103,6 +105,7 @@ export const UsingGuessers = () => ( show={BookShow} edit={BookEdit} create={BookCreate} + icon={AutoStoriesIcon} /> ( show={ReviewShow} edit={ReviewEdit} create={ReviewCreate} + icon={ReviewsIcon} /> );