From 193b29e9f3284b130728df1e077d7ddbeddaea8a Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 6 Dec 2024 12:01:09 +0100 Subject: [PATCH 01/20] Handle get form --- demo/app/Sharp/Posts/PostList.php | 1 + .../QuickCreate/QuickCreationCommand.php | 39 +++++++++ src/EntityList/SharpEntityList.php | 19 ++++ ...tityListQuickCreationCommandController.php | 64 ++++++++++++++ src/Utils/Fields/HandleFields.php | 4 +- src/routes/api.php | 7 ++ ...ListQuickCreationCommandControllerTest.php | 86 +++++++++++++++++++ 7 files changed, 218 insertions(+), 2 deletions(-) create mode 100644 src/EntityList/Commands/QuickCreate/QuickCreationCommand.php create mode 100644 src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php create mode 100644 tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php diff --git a/demo/app/Sharp/Posts/PostList.php b/demo/app/Sharp/Posts/PostList.php index 77d002c4f..ac848dc09 100644 --- a/demo/app/Sharp/Posts/PostList.php +++ b/demo/app/Sharp/Posts/PostList.php @@ -61,6 +61,7 @@ public function buildListConfig(): void { $this ->configureCreateButtonLabel('New post...') + ->configureQuickCreation(fields: ['title', 'published_at', 'cover']) ->configureEntityState('state', PostStateHandler::class) ->configureDefaultSort('published_at', 'desc') ->configureDelete(confirmationText: 'Are you sure you want to delete this post (this will permanently delete its data)?') diff --git a/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php new file mode 100644 index 000000000..44d5f0583 --- /dev/null +++ b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php @@ -0,0 +1,39 @@ +sharpForm + ->getBuiltFields() + ->when( + $this->specificFormFields, + fn ($sharpFormFields) => $sharpFormFields + ->filter(fn (SharpFormField $field) => in_array($field->key, $this->specificFormFields)) + ) + ->each(fn (SharpFormField $field) => $formFields->addField($field)); + } + + public function execute(array $data = []): array {} + + public function setFormInstance(SharpForm $form): void + { + $this->sharpForm = $form; + } +} diff --git a/src/EntityList/SharpEntityList.php b/src/EntityList/SharpEntityList.php index fb6e71e6d..ae150d0d3 100644 --- a/src/EntityList/SharpEntityList.php +++ b/src/EntityList/SharpEntityList.php @@ -2,6 +2,7 @@ namespace Code16\Sharp\EntityList; +use Code16\Sharp\EntityList\Commands\QuickCreate\QuickCreationCommand; use Code16\Sharp\EntityList\Commands\ReorderHandler; use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; use Code16\Sharp\EntityList\Traits\HandleEntityCommands; @@ -35,6 +36,8 @@ abstract class SharpEntityList protected bool $deleteHidden = false; protected ?string $deleteConfirmationText = null; protected ?string $createButtonLabel = null; + private bool $quickCreation = false; + private ?array $quickCreationFields = null; final public function initQueryParams(?array $query): self { @@ -114,6 +117,7 @@ final public function listConfig(bool $hasShowPage = false): array 'deleteConfirmationText' => $this->deleteConfirmationText ?: trans('sharp::show.delete_confirmation_text'), 'deleteHidden' => $this->deleteHidden, 'createButtonLabel' => $this->createButtonLabel, + 'quickCreation' => $this->quickCreation, 'filters' => $this->filterContainer()->getFiltersConfigArray(), ]; @@ -152,6 +156,14 @@ final public function configureCreateButtonLabel(string $label): self return $this; } + final public function configureQuickCreation(?array $fields = null): self + { + $this->quickCreation = true; + $this->quickCreationFields = $fields; + + return $this; + } + final public function configureDelete(bool $hide = false, ?string $confirmationText = null): self { $this->deleteHidden = $hide; @@ -180,6 +192,13 @@ final public function reorderHandler(): ?ReorderHandler return $this->reorderHandler; } + final public function quickCreationCommandHandler(): ?QuickCreationCommand + { + return $this->quickCreation + ? new QuickCreationCommand($this->quickCreationFields) + : null; + } + private function checkListIsBuilt(): void { if ($this->fieldsContainer === null) { diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php new file mode 100644 index 000000000..e99554db2 --- /dev/null +++ b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php @@ -0,0 +1,64 @@ +getListInstance($entityKey); + $list->buildListConfig(); + + abort_if( + ($quickCreationHandler = $list->quickCreationCommandHandler()) === null, + 403 + ); + + $quickCreationHandler->setFormInstance( + $this->entityManager->entityFor($entityKey)->getFormOrFail() + ); + + return response()->json( + CommandFormData::from( + $this->getCommandForm($quickCreationHandler, $quickCreationHandler->formData()) + ) + ); + } + + public function store(string $entityKey) + { + // $list = $this->getListInstance($entityKey); + // $list->buildListConfig(); + // $list->initQueryParams(request()->input('query')); + // + // $commandHandler = $this->getEntityCommandHandler($list, $commandKey); + // + // $formattedData = $commandHandler->formatAndValidateRequestData((array) request('data')); + // $result = $this->returnCommandResult($list, $commandHandler->execute($formattedData)); + // $this->uploadManager->dispatchJobs(); + // + // return $result; + } + + private function getEntityCommandHandler(): EntityCommand + { + $commandHandler = app(QuickCreationCommand::class); + $commandHandler->buildCommandConfig(); + + return $commandHandler; + } +} diff --git a/src/Utils/Fields/HandleFields.php b/src/Utils/Fields/HandleFields.php index 78409ccc9..f8c947e65 100644 --- a/src/Utils/Fields/HandleFields.php +++ b/src/Utils/Fields/HandleFields.php @@ -76,10 +76,10 @@ final public function applyFormatters(?array $attributes): ?array return collect($attributes) ->map(function ($value, $key) { - if(isset($this->pageTitleField) && $this->pageTitleField->key == $key) { + if (isset($this->pageTitleField) && $this->pageTitleField->key == $key) { return $this->pageTitleField->formatter()->toFront($this->pageTitleField, $value); } - + $field = $this->findFieldByKey($key); return $field diff --git a/src/routes/api.php b/src/routes/api.php index a18093608..f86d72a88 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -11,6 +11,7 @@ use Code16\Sharp\Http\Controllers\Api\Commands\ApiEntityListEntityCommandController; use Code16\Sharp\Http\Controllers\Api\Commands\ApiEntityListEntityStateController; use Code16\Sharp\Http\Controllers\Api\Commands\ApiEntityListInstanceCommandController; +use Code16\Sharp\Http\Controllers\Api\Commands\ApiEntityListQuickCreationCommandController; use Code16\Sharp\Http\Controllers\Api\Commands\ApiShowEntityStateController; use Code16\Sharp\Http\Controllers\Api\Commands\ApiShowInstanceCommandController; use Code16\Sharp\Http\Controllers\Api\Embeds\ApiEmbedsFormController; @@ -27,6 +28,12 @@ Route::post('/dashboard/{dashboardKey}/command/{commandKey}', [ApiDashboardCommandController::class, 'update']) ->name('code16.sharp.api.dashboard.command'); + Route::get('/list/{entityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'create']) + ->name('code16.sharp.api.list.command.quickCreate.create'); + + Route::post('/list/{entityKey}/create', [ApiEntityListQuickCreationCommandController::class, 'store']) + ->name('code16.sharp.api.list.command.quickCreate.store'); + // EEL Route::get('/list/{entityKey}', [EntityListController::class, 'show']) ->name('code16.sharp.api.list'); diff --git a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php new file mode 100644 index 000000000..d9c8cfa8a --- /dev/null +++ b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php @@ -0,0 +1,86 @@ +config()->addEntity('person', PersonEntity::class); + login(); +}); + +it('allows to call a quick creation command with the standard form', function () { + fakeListFor('person', new class() extends PersonList + { + public function buildListConfig(): void + { + $this->configureQuickCreation(); + } + }); + + fakeFormFor('person', new class() extends PersonForm + { + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields->addField(SharpFormTextField::make('name')) + ->addField(SharpFormTextField::make('job')); + } + }); + + $this + ->getJson( + route('code16.sharp.api.list.command.quickCreate.create', ['person']), + ) + ->assertOk() + ->assertJson([ + 'fields' => [ + 'name' => [ + 'key' => 'name', + ], + 'job' => [ + 'key' => 'job', + ], + ], + ]); +}); + +it('allows to call a quick creation command with custom form fields', function () { + fakeListFor('person', new class() extends PersonList + { + public function buildListConfig(): void + { + $this->configureQuickCreation(['name']); + } + }); + + fakeFormFor('person', new class() extends PersonForm + { + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields->addField(SharpFormTextField::make('name')) + ->addField(SharpFormTextField::make('job')); + } + }); + + $this + ->getJson( + route('code16.sharp.api.list.command.quickCreate.create', ['person']), + ) + ->assertOk() + ->assertJsonCount(1, 'fields'); +}); + +it('fails when calling a quick creation command on a not configured list', function () { + fakeListFor('person', new class() extends PersonList + { + public function buildListConfig(): void {} + }); + + $this + ->getJson( + route('code16.sharp.api.list.command.quickCreate.create', ['person']), + ) + ->assertStatus(403); +}); From 16cc7af243ad62792cb5166c6f0fad06a692af64 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 6 Dec 2024 12:07:48 +0100 Subject: [PATCH 02/20] Fix tests --- src/Data/EntityList/EntityListConfigData.php | 1 + tests/Unit/EntityList/SharpEntityListTest.php | 1 + 2 files changed, 2 insertions(+) diff --git a/src/Data/EntityList/EntityListConfigData.php b/src/Data/EntityList/EntityListConfigData.php index 5d46c84e7..d70ec0dd8 100644 --- a/src/Data/EntityList/EntityListConfigData.php +++ b/src/Data/EntityList/EntityListConfigData.php @@ -20,6 +20,7 @@ public function __construct( public bool $deleteHidden, public ?string $multiformAttribute, public ?string $createButtonLabel = null, + public bool $quickCreation = false, public ?ConfigFiltersData $filters = null, public ?ConfigCommandsData $commands = null, public ?EntityStateData $state = null, diff --git a/tests/Unit/EntityList/SharpEntityListTest.php b/tests/Unit/EntityList/SharpEntityListTest.php index 7dd1b022b..97a6bcb6f 100644 --- a/tests/Unit/EntityList/SharpEntityListTest.php +++ b/tests/Unit/EntityList/SharpEntityListTest.php @@ -228,6 +228,7 @@ public function buildListConfig(): void 'deleteConfirmationText' => trans('sharp::show.delete_confirmation_text'), 'filters' => null, 'createButtonLabel' => null, + 'quickCreation' => false, ]); }); From ec184bbcbafbbef18121a83fbe8efbb59beab558 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 6 Dec 2024 14:24:41 +0100 Subject: [PATCH 03/20] Handle post --- .../QuickCreate/QuickCreationCommand.php | 21 +++++- ...tityListQuickCreationCommandController.php | 37 +++++----- ...ListQuickCreationCommandControllerTest.php | 73 +++++++++++++++++++ 3 files changed, 111 insertions(+), 20 deletions(-) diff --git a/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php index 44d5f0583..dc9d0adae 100644 --- a/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php +++ b/src/EntityList/Commands/QuickCreate/QuickCreationCommand.php @@ -30,7 +30,26 @@ public function buildFormFields(FieldsContainer $formFields): void ->each(fn (SharpFormField $field) => $formFields->addField($field)); } - public function execute(array $data = []): array {} + public function rules(): array + { + return method_exists($this->sharpForm, 'rules') + ? $this->sharpForm->rules() + : []; + } + + public function messages(): array + { + return method_exists($this->sharpForm, 'messages') + ? $this->sharpForm->messages() + : []; + } + + public function execute(array $data = []): array + { + $this->sharpForm->update(null, $data); + + return $this->reload(); + } public function setFormInstance(SharpForm $form): void { diff --git a/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php index e99554db2..fc4ae8c18 100644 --- a/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php +++ b/src/Http/Controllers/Api/Commands/ApiEntityListQuickCreationCommandController.php @@ -3,8 +3,6 @@ namespace Code16\Sharp\Http\Controllers\Api\Commands; use Code16\Sharp\Data\Commands\CommandFormData; -use Code16\Sharp\EntityList\Commands\EntityCommand; -use Code16\Sharp\EntityList\Commands\QuickCreate\QuickCreationCommand; use Code16\Sharp\Http\Controllers\Api\ApiController; use Code16\Sharp\Utils\Uploads\SharpUploadManager; @@ -41,24 +39,25 @@ public function create(string $entityKey) public function store(string $entityKey) { - // $list = $this->getListInstance($entityKey); - // $list->buildListConfig(); - // $list->initQueryParams(request()->input('query')); - // - // $commandHandler = $this->getEntityCommandHandler($list, $commandKey); - // - // $formattedData = $commandHandler->formatAndValidateRequestData((array) request('data')); - // $result = $this->returnCommandResult($list, $commandHandler->execute($formattedData)); - // $this->uploadManager->dispatchJobs(); - // - // return $result; - } + $list = $this->getListInstance($entityKey); + $list->buildListConfig(); - private function getEntityCommandHandler(): EntityCommand - { - $commandHandler = app(QuickCreationCommand::class); - $commandHandler->buildCommandConfig(); + abort_if( + ($quickCreationHandler = $list->quickCreationCommandHandler()) === null, + 403 + ); + + $quickCreationHandler->setFormInstance( + $this->entityManager->entityFor($entityKey)->getFormOrFail() + ); + + $formattedData = $quickCreationHandler->formatAndValidateRequestData((array) request('data')); + $result = $this->returnCommandResult( + $list, + $quickCreationHandler->execute($formattedData) + ); + $this->uploadManager->dispatchJobs(); - return $commandHandler; + return $result; } } diff --git a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php index d9c8cfa8a..1db8154fd 100644 --- a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php +++ b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php @@ -84,3 +84,76 @@ public function buildListConfig(): void {} ) ->assertStatus(403); }); + +it('allows to post a quick creation command', function () { + fakeListFor('person', new class() extends PersonList + { + public function buildListConfig(): void + { + $this->configureQuickCreation(); + } + }); + + fakeFormFor('person', new class() extends PersonForm + { + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields->addField(SharpFormTextField::make('name')) + ->addField(SharpFormTextField::make('job')); + } + + public function update($id, array $data) + { + expect($data)->toBe([ + 'name' => 'Marie Curie', + 'job' => 'Scientist', + ]); + + return 1; + } + }); + + $this + ->postJson( + route('code16.sharp.api.list.command.quickCreate.create', ['person']), + ['data' => ['name' => 'Marie Curie', 'job' => 'Scientist']], + ) + ->assertOk() + ->assertJson(['action' => 'reload']); +}); + +it('validates posted data of a quick creation command', function () { + fakeListFor('person', new class() extends PersonList + { + public function buildListConfig(): void + { + $this->configureQuickCreation(); + } + }); + + fakeFormFor('person', new class() extends PersonForm + { + public function buildFormFields(FieldsContainer $formFields): void + { + $formFields->addField(SharpFormTextField::make('name')); + } + + public function rules(): array + { + return ['name' => 'required', 'min:2']; + } + + public function update($id, array $data) + { + return 1; + } + }); + + $this + ->postJson( + route('code16.sharp.api.list.command.quickCreate.create', ['person']), + ['data' => ['name' => '']], + ) + ->assertStatus(422) + ->assertJsonValidationErrors('name'); +}); From 17a6916adc27d958461bddbceb49dfa1e7d93641 Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 6 Dec 2024 14:28:15 +0100 Subject: [PATCH 04/20] Fix test --- .../ApiEntityListQuickCreationCommandControllerTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php index 1db8154fd..e348f29a3 100644 --- a/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php +++ b/tests/Http/Api/Commands/ApiEntityListQuickCreationCommandControllerTest.php @@ -140,7 +140,7 @@ public function buildFormFields(FieldsContainer $formFields): void public function rules(): array { - return ['name' => 'required', 'min:2']; + return ['name' => 'required']; } public function update($id, array $data) From ab2b84041a1004556f19dddd345d365bf462bbcc Mon Sep 17 00:00:00 2001 From: philippe Date: Fri, 6 Dec 2024 14:37:34 +0100 Subject: [PATCH 05/20] Add quick creation case in demo --- demo/app/Sharp/Categories/CategoryList.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/demo/app/Sharp/Categories/CategoryList.php b/demo/app/Sharp/Categories/CategoryList.php index 0ba1a38db..89f5f4d93 100644 --- a/demo/app/Sharp/Categories/CategoryList.php +++ b/demo/app/Sharp/Categories/CategoryList.php @@ -28,7 +28,8 @@ protected function buildList(EntityListFieldsContainer $fields): void public function buildListConfig(): void { - $this->configureReorderable(new SimpleEloquentReorderHandler(Category::class)); + $this->configureReorderable(new SimpleEloquentReorderHandler(Category::class)) + ->configureQuickCreation(); } protected function getEntityCommands(): ?array From 523273fff716857835c2f676f117b326f0ea763a Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 9 Dec 2024 18:03:57 +0100 Subject: [PATCH 06/20] Always send all fields default data as null in commands --- src/Dashboard/Commands/DashboardCommand.php | 4 +++- src/EntityList/Commands/EntityCommand.php | 4 +++- src/EntityList/Commands/InstanceCommand.php | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Dashboard/Commands/DashboardCommand.php b/src/Dashboard/Commands/DashboardCommand.php index cb4341272..f8b8f5378 100644 --- a/src/Dashboard/Commands/DashboardCommand.php +++ b/src/Dashboard/Commands/DashboardCommand.php @@ -21,7 +21,9 @@ final public function initQueryParams(?DashboardQueryParams $queryParams): void final public function formData(): array { - return collect($this->initialData()) + return collect() + ->merge(collect($this->getDataKeys())->mapWithKeys(fn ($key) => [$key => null])) + ->merge($this->initialData()) ->only([ ...$this->getDataKeys(), ...array_keys($this->transformers), diff --git a/src/EntityList/Commands/EntityCommand.php b/src/EntityList/Commands/EntityCommand.php index a06e5f3d7..0bb14c794 100644 --- a/src/EntityList/Commands/EntityCommand.php +++ b/src/EntityList/Commands/EntityCommand.php @@ -42,7 +42,9 @@ final public function initQueryParams(EntityListQueryParams $params): void final public function formData(): array { - return collect($this->initialData()) + return collect() + ->merge(collect($this->getDataKeys())->mapWithKeys(fn ($key) => [$key => null])) + ->merge($this->initialData()) ->only([ ...$this->getDataKeys(), ...array_keys($this->transformers), diff --git a/src/EntityList/Commands/InstanceCommand.php b/src/EntityList/Commands/InstanceCommand.php index d467d6ea4..37c7c1e59 100644 --- a/src/EntityList/Commands/InstanceCommand.php +++ b/src/EntityList/Commands/InstanceCommand.php @@ -13,7 +13,9 @@ public function type(): string final public function formData(mixed $instanceId): array { - return collect($this->initialData($instanceId)) + return collect() + ->merge(collect($this->getDataKeys())->mapWithKeys(fn ($key) => [$key => null])) + ->merge($this->initialData($instanceId)) ->only([ ...$this->getDataKeys(), ...array_keys($this->transformers), From b585668d111f9c5b3933c0d04962e349a76d5ca5 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 9 Dec 2024 18:04:45 +0100 Subject: [PATCH 07/20] Allow to scroll when popover overflowing in dialog --- .../js/components/ui/popover/PopoverContent.vue | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/resources/js/components/ui/popover/PopoverContent.vue b/resources/js/components/ui/popover/PopoverContent.vue index 36e779a0c..a5583f378 100644 --- a/resources/js/components/ui/popover/PopoverContent.vue +++ b/resources/js/components/ui/popover/PopoverContent.vue @@ -1,11 +1,12 @@