From 9e480b26ed715c9d88c228753127f9ddacaec160 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 27 May 2025 14:58:28 +0200 Subject: [PATCH 01/14] wip autocomplete filter --- src/EntityList/Filters/HiddenFilter.php | 5 +++ .../Controllers/GlobalFilterController.php | 9 +--- src/Utils/Filters/CheckFilter.php | 7 +++ .../Concerns/BuildsFiltersConfigArray.php | 43 +----------------- src/Utils/Filters/DateRangeFilter.php | 13 ++++++ src/Utils/Filters/Filter.php | 10 +++++ src/Utils/Filters/FilterContainer.php | 16 ------- src/Utils/Filters/GlobalRequiredFilter.php | 6 ++- .../Filters/RemoteAutocompleteFilter.php | 44 +++++++++++++++++++ .../RemoteAutocompleteMultipleFilter.php | 18 ++++++++ .../RemoteAutocompleteRequiredFilter.php | 8 ++++ src/Utils/Filters/SelectFilter.php | 29 ++++++++++++ 12 files changed, 141 insertions(+), 67 deletions(-) create mode 100644 src/Utils/Filters/RemoteAutocompleteFilter.php create mode 100644 src/Utils/Filters/RemoteAutocompleteMultipleFilter.php create mode 100644 src/Utils/Filters/RemoteAutocompleteRequiredFilter.php diff --git a/src/EntityList/Filters/HiddenFilter.php b/src/EntityList/Filters/HiddenFilter.php index bb7987ef2..968201516 100644 --- a/src/EntityList/Filters/HiddenFilter.php +++ b/src/EntityList/Filters/HiddenFilter.php @@ -22,4 +22,9 @@ public function toQueryParam($value): mixed { return $value; } + + public function toArray(): array + { + return []; + } } diff --git a/src/Http/Controllers/GlobalFilterController.php b/src/Http/Controllers/GlobalFilterController.php index 98109b2c1..4a2336b85 100644 --- a/src/Http/Controllers/GlobalFilterController.php +++ b/src/Http/Controllers/GlobalFilterController.php @@ -14,14 +14,7 @@ public function update(string $filterKey, GlobalFilters $globalFilters): Redirec abort_if(! $handler instanceof GlobalRequiredFilter, 404); - // Ensure value is in the filter value-set - $value = request('value') - ? collect($globalFilters->filterContainer()->formatSelectFilterValues($handler)) - ->where('id', request('value')) - ->first() - : null; - - $handler->setCurrentValue($value ? $value['id'] : null); + $handler->setCurrentValue(request('value')); return redirect()->route('code16.sharp.home'); } diff --git a/src/Utils/Filters/CheckFilter.php b/src/Utils/Filters/CheckFilter.php index d6322a5ba..397d762f2 100644 --- a/src/Utils/Filters/CheckFilter.php +++ b/src/Utils/Filters/CheckFilter.php @@ -13,4 +13,11 @@ public function toQueryParam($value): mixed { return $value; } + + public function toArray(): array + { + return parent::buildArray([ + 'type' => 'check', + ]); + } } diff --git a/src/Utils/Filters/Concerns/BuildsFiltersConfigArray.php b/src/Utils/Filters/Concerns/BuildsFiltersConfigArray.php index 2e328dcb0..fee6c4eae 100644 --- a/src/Utils/Filters/Concerns/BuildsFiltersConfigArray.php +++ b/src/Utils/Filters/Concerns/BuildsFiltersConfigArray.php @@ -3,13 +3,7 @@ namespace Code16\Sharp\Utils\Filters\Concerns; use Code16\Sharp\EntityList\Filters\HiddenFilter; -use Code16\Sharp\Utils\Filters\CheckFilter; -use Code16\Sharp\Utils\Filters\DateRangeFilter; -use Code16\Sharp\Utils\Filters\DateRangeRequiredFilter; use Code16\Sharp\Utils\Filters\Filter; -use Code16\Sharp\Utils\Filters\SelectFilter; -use Code16\Sharp\Utils\Filters\SelectMultipleFilter; -use Code16\Sharp\Utils\Filters\SelectRequiredFilter; use Illuminate\Support\Collection; trait BuildsFiltersConfigArray @@ -20,42 +14,7 @@ public function getFiltersConfigArray(): ?array ->map(function (Collection $filterHandlers) { return $filterHandlers ->filter(fn (Filter $handler) => ! $this->isHiddenFilter($handler)) - ->map(function (Filter $filterHandler) { - $filterConfigData = [ - 'key' => $filterHandler->getKey(), - 'label' => $filterHandler->getLabel(), - ]; - - if ($filterHandler instanceof SelectFilter) { - $multiple = $filterHandler instanceof SelectMultipleFilter; - - $filterConfigData += [ - 'type' => 'select', - 'multiple' => $multiple, - 'required' => ! $multiple && $filterHandler instanceof SelectRequiredFilter, - 'values' => $this->formatSelectFilterValues($filterHandler), - 'master' => $filterHandler->isMaster(), - 'searchable' => $filterHandler->isSearchable(), - 'searchKeys' => $filterHandler->getSearchKeys(), - ]; - } elseif ($filterHandler instanceof DateRangeFilter) { - $filterConfigData += [ - 'type' => 'daterange', - 'required' => $filterHandler instanceof DateRangeRequiredFilter, - 'mondayFirst' => $filterHandler->isMondayFirst(), - 'presets' => collect($filterHandler->getPresets()) - ->map(fn ($preset, $key) => ['key' => $key, ...$preset->toArray()]) - ->values() - ->toArray(), - ]; - } elseif ($filterHandler instanceof CheckFilter) { - $filterConfigData += [ - 'type' => 'check', - ]; - } - - return $filterConfigData; - }) + ->map(fn (Filter $handler) => $handler->toArray()) ->values(); }) ->filter(fn (Collection $filters) => count($filters) > 0) diff --git a/src/Utils/Filters/DateRangeFilter.php b/src/Utils/Filters/DateRangeFilter.php index 726d3cfd8..aec3ddc30 100644 --- a/src/Utils/Filters/DateRangeFilter.php +++ b/src/Utils/Filters/DateRangeFilter.php @@ -154,6 +154,19 @@ final public function toQueryParam($value): ?string ); } + public function toArray(): array + { + return parent::buildArray([ + 'type' => 'daterange', + 'required' => $this instanceof DateRangeRequiredFilter, + 'mondayFirst' => $this->isMondayFirst(), + 'presets' => collect($this->getPresets()) + ->map(fn ($preset, $key) => ['key' => $key, ...$preset->toArray()]) + ->values() + ->toArray(), + ]); + } + public function formatRawValue(mixed $value): DateRangeFilterValue { return new DateRangeFilterValue(Carbon::parse($value['start']), Carbon::parse($value['end'])); diff --git a/src/Utils/Filters/Filter.php b/src/Utils/Filters/Filter.php index 3d6044c2f..c3dd1fc4b 100644 --- a/src/Utils/Filters/Filter.php +++ b/src/Utils/Filters/Filter.php @@ -51,6 +51,16 @@ public function formatRawValue(mixed $value): mixed public function buildFilterConfig(): void {} + protected function buildArray(array $childArray): array + { + return [ + 'key' => $this->getKey(), + 'label' => $this->getLabel(), + ...$childArray, + ]; + } + + abstract public function toArray(): array; abstract public function fromQueryParam($value): mixed; abstract public function toQueryParam($value): mixed; } diff --git a/src/Utils/Filters/FilterContainer.php b/src/Utils/Filters/FilterContainer.php index 8b96b1341..a194a8625 100644 --- a/src/Utils/Filters/FilterContainer.php +++ b/src/Utils/Filters/FilterContainer.php @@ -78,22 +78,6 @@ public function findFilterHandler(string $filterFullClassNameOrKey): ?Filter ->first(); } - public function formatSelectFilterValues(SelectFilter $handler): array - { - $values = $handler->values(); - - if (! is_array(collect($values)->first())) { - return collect($values) - ->map(function ($label, $id) { - return compact('id', 'label'); - }) - ->values() - ->all(); - } - - return $values; - } - public function getCurrentFilterValues(?array $query): array { return [ diff --git a/src/Utils/Filters/GlobalRequiredFilter.php b/src/Utils/Filters/GlobalRequiredFilter.php index 7a98b4114..7a586be92 100644 --- a/src/Utils/Filters/GlobalRequiredFilter.php +++ b/src/Utils/Filters/GlobalRequiredFilter.php @@ -25,7 +25,11 @@ final public function setCurrentValue(mixed $value): void return; } - session()->put($this->getSessionKey(), $value); + $value = collect($this->formattedValues()) + ->where('id', request('value')) + ->first(); + + session()->put($this->getSessionKey(), $value ? $value['id'] : null); } private function getSessionKey(): string diff --git a/src/Utils/Filters/RemoteAutocompleteFilter.php b/src/Utils/Filters/RemoteAutocompleteFilter.php new file mode 100644 index 000000000..b86ffacef --- /dev/null +++ b/src/Utils/Filters/RemoteAutocompleteFilter.php @@ -0,0 +1,44 @@ +isMaster; + } + + final public function configureMaster(bool $isMaster = true): self + { + $this->isMaster = $isMaster; + + return $this; + } + + public function fromQueryParam($value): mixed + { + return $value ? $this->valuesFor([$value]) : null; + } + + public function toQueryParam($value): mixed + { + return $value; + } + + public function toArray(): array + { + return parent::buildArray([ + 'type' => 'remoteAutocomplete', + 'master' => $this->isMaster(), + 'required' => $this instanceof RemoteAutocompleteRequiredFilter, + 'multiple' => $this instanceof RemoteAutocompleteMultipleFilter, + ]); + } + + abstract public function values(string $query): array; + + abstract public function valuesFor(array $ids): array; +} diff --git a/src/Utils/Filters/RemoteAutocompleteMultipleFilter.php b/src/Utils/Filters/RemoteAutocompleteMultipleFilter.php new file mode 100644 index 000000000..ddf5b31c2 --- /dev/null +++ b/src/Utils/Filters/RemoteAutocompleteMultipleFilter.php @@ -0,0 +1,18 @@ + 'select', + 'multiple' => $this instanceof SelectMultipleFilter, + 'required' => $this instanceof SelectRequiredFilter, + 'values' => $this->formattedValues(), + 'master' => $this->isMaster(), + 'searchable' => $this->isSearchable(), + 'searchKeys' => $this->getSearchKeys(), + ]); + } + + protected function formattedValues(): array + { + $values = $this->values(); + + if (! is_array(collect($values)->first())) { + return collect($values) + ->map(function ($label, $id) { + return compact('id', 'label'); + }) + ->values() + ->all(); + } + + return $values; + } + abstract public function values(): array; } From e963e5b8c5c77027e61d4025b966082578e2e731 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 27 May 2025 18:04:34 +0200 Subject: [PATCH 02/14] Move filters classes & deprecate old ones --- demo/app/Sharp/Categories/CategoryList.php | 2 +- demo/app/Sharp/DummyGlobalFilter.php | 2 +- demo/app/Sharp/Posts/PostList.php | 2 +- src/Config/SharpConfigBuilder.php | 2 +- src/Dashboard/DashboardQueryParams.php | 4 +- .../Filters/DashboardCheckFilter.php | 5 +- .../Filters/DashboardDateRangeFilter.php | 5 +- .../DashboardDateRangeRequiredFilter.php | 5 +- .../Filters/DashboardSelectFilter.php | 5 +- .../Filters/DashboardSelectMultipleFilter.php | 5 +- .../Filters/DashboardSelectRequiredFilter.php | 5 +- src/Dashboard/SharpDashboard.php | 4 +- .../Filters/RemoteAutocompleteFilterData.php | 37 ++++ src/EntityList/EntityListQueryParams.php | 4 +- .../Filters/EntityListCheckFilter.php | 5 +- .../Filters/EntityListDateRangeFilter.php | 5 +- .../EntityListDateRangeRequiredFilter.php | 5 +- .../Filters/EntityListSelectFilter.php | 5 +- .../EntityListSelectMultipleFilter.php | 5 +- .../EntityListSelectRequiredFilter.php | 5 +- src/EntityList/Filters/HiddenFilter.php | 2 +- src/EntityList/SharpEntityList.php | 4 +- src/Enums/FilterType.php | 1 + src/Filters/CheckFilter.php | 23 +++ .../Concerns/HasFilters.php} | 6 +- .../Concerns}/HasFiltersInQuery.php | 4 +- .../DateRange/DateRangeFilterValue.php | 38 ++++ .../DateRange}/DateRangePreset.php | 4 +- src/Filters/DateRangeFilter.php | 176 ++++++++++++++++++ src/Filters/DateRangeRequiredFilter.php | 11 ++ src/{Utils => }/Filters/Filter.php | 2 +- .../Concerns/BuildsFiltersConfigArray.php | 4 +- .../Concerns/HandlesFiltersInQueryParams.php | 4 +- .../Concerns/HandlesFiltersInSession.php | 4 +- .../Concerns/ProvidesFilterValuesToFront.php | 6 +- .../FilterContainer}/FilterContainer.php | 13 +- .../GlobalFilters}/GlobalFilters.php | 7 +- src/Filters/GlobalRequiredFilter.php | 44 +++++ .../Filters/RemoteAutocompleteFilter.php | 2 +- .../RemoteAutocompleteMultipleFilter.php | 2 +- .../RemoteAutocompleteRequiredFilter.php | 2 +- src/Filters/SelectFilter.php | 87 +++++++++ src/Filters/SelectMultipleFilter.php | 18 ++ src/Filters/SelectRequiredFilter.php | 8 + src/Http/Context/SharpContext.php | 4 +- .../Controllers/GlobalFilterController.php | 4 +- src/Http/Middleware/HandleInertiaRequests.php | 2 +- src/Show/Fields/SharpShowEntityListField.php | 2 +- src/Utils/Filters/CheckFilter.php | 23 +-- src/Utils/Filters/DateRangeFilter.php | 174 +---------------- src/Utils/Filters/DateRangeFilterValue.php | 38 +--- src/Utils/Filters/DateRangeRequiredFilter.php | 11 +- src/Utils/Filters/GlobalRequiredFilter.php | 44 +---- src/Utils/Filters/SelectFilter.php | 87 +-------- src/Utils/Filters/SelectMultipleFilter.php | 18 +- src/Utils/Filters/SelectRequiredFilter.php | 8 +- .../app/Sharp/TestModels/TestModelList.php | 2 +- tests/Http/GlobalFilterControllerTest.php | 2 +- .../SharpEntityListQueryParamsTest.php | 8 +- tests/Unit/Utils/SharpLinkToTest.php | 2 +- 60 files changed, 584 insertions(+), 434 deletions(-) create mode 100644 src/Data/Filters/RemoteAutocompleteFilterData.php create mode 100644 src/Filters/CheckFilter.php rename src/{Utils/Filters/HandleFilters.php => Filters/Concerns/HasFilters.php} (79%) rename src/{Utils/Filters => Filters/Concerns}/HasFiltersInQuery.php (82%) create mode 100644 src/Filters/DateRange/DateRangeFilterValue.php rename src/{Utils/Filters => Filters/DateRange}/DateRangePreset.php (84%) create mode 100644 src/Filters/DateRangeFilter.php create mode 100644 src/Filters/DateRangeRequiredFilter.php rename src/{Utils => }/Filters/Filter.php (97%) rename src/{Utils/Filters => Filters/FilterContainer}/Concerns/BuildsFiltersConfigArray.php (89%) rename src/{Utils/Filters => Filters/FilterContainer}/Concerns/HandlesFiltersInQueryParams.php (91%) rename src/{Utils/Filters => Filters/FilterContainer}/Concerns/HandlesFiltersInSession.php (95%) rename src/{Utils/Filters => Filters/FilterContainer}/Concerns/ProvidesFilterValuesToFront.php (93%) rename src/{Utils/Filters => Filters/FilterContainer}/FilterContainer.php (88%) rename src/{Utils/Filters => Filters/GlobalFilters}/GlobalFilters.php (88%) create mode 100644 src/Filters/GlobalRequiredFilter.php rename src/{Utils => }/Filters/RemoteAutocompleteFilter.php (96%) rename src/{Utils => }/Filters/RemoteAutocompleteMultipleFilter.php (90%) rename src/{Utils => }/Filters/RemoteAutocompleteRequiredFilter.php (79%) create mode 100644 src/Filters/SelectFilter.php create mode 100644 src/Filters/SelectMultipleFilter.php create mode 100644 src/Filters/SelectRequiredFilter.php diff --git a/demo/app/Sharp/Categories/CategoryList.php b/demo/app/Sharp/Categories/CategoryList.php index 6a51f0904..fbf604305 100644 --- a/demo/app/Sharp/Categories/CategoryList.php +++ b/demo/app/Sharp/Categories/CategoryList.php @@ -8,7 +8,7 @@ use Code16\Sharp\EntityList\Fields\EntityListField; use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; use Code16\Sharp\EntityList\SharpEntityList; -use Code16\Sharp\Utils\Filters\CheckFilter; +use Code16\Sharp\Filters\CheckFilter; use Illuminate\Contracts\Support\Arrayable; class CategoryList extends SharpEntityList diff --git a/demo/app/Sharp/DummyGlobalFilter.php b/demo/app/Sharp/DummyGlobalFilter.php index 43e5a7bfc..2523278c5 100644 --- a/demo/app/Sharp/DummyGlobalFilter.php +++ b/demo/app/Sharp/DummyGlobalFilter.php @@ -2,7 +2,7 @@ namespace App\Sharp; -use Code16\Sharp\Utils\Filters\GlobalRequiredFilter; +use Code16\Sharp\Filters\GlobalRequiredFilter; class DummyGlobalFilter extends GlobalRequiredFilter { diff --git a/demo/app/Sharp/Posts/PostList.php b/demo/app/Sharp/Posts/PostList.php index 9b0d79cdb..b2f43ad15 100644 --- a/demo/app/Sharp/Posts/PostList.php +++ b/demo/app/Sharp/Posts/PostList.php @@ -17,7 +17,7 @@ use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; use Code16\Sharp\EntityList\Fields\EntityListStateField; use Code16\Sharp\EntityList\SharpEntityList; -use Code16\Sharp\Utils\Filters\DateRangeFilterValue; +use Code16\Sharp\Filters\DateRange\DateRangeFilterValue; use Code16\Sharp\Utils\Links\LinkToEntityList; use Code16\Sharp\Utils\PageAlerts\PageAlert; use Code16\Sharp\Utils\Transformers\Attributes\Eloquent\SharpTagsTransformer; diff --git a/src/Config/SharpConfigBuilder.php b/src/Config/SharpConfigBuilder.php index b6d02e0c3..6ec709234 100644 --- a/src/Config/SharpConfigBuilder.php +++ b/src/Config/SharpConfigBuilder.php @@ -8,12 +8,12 @@ use Code16\Sharp\Auth\TwoFactor\Sharp2faHandler; use Code16\Sharp\Exceptions\SharpInvalidConfigException; use Code16\Sharp\Exceptions\SharpInvalidEntityKeyException; +use Code16\Sharp\Filters\GlobalRequiredFilter; use Code16\Sharp\Search\SharpSearchEngine; use Code16\Sharp\Utils\Entities\BaseSharpEntity; use Code16\Sharp\Utils\Entities\SharpDashboardEntity; use Code16\Sharp\Utils\Entities\SharpEntity; use Code16\Sharp\Utils\Entities\SharpEntityResolver; -use Code16\Sharp\Utils\Filters\GlobalRequiredFilter; use Code16\Sharp\Utils\Menu\SharpMenu; use Illuminate\Contracts\Auth\Authenticatable; use Illuminate\Contracts\Auth\PasswordBroker; diff --git a/src/Dashboard/DashboardQueryParams.php b/src/Dashboard/DashboardQueryParams.php index a91583e67..0d0830782 100644 --- a/src/Dashboard/DashboardQueryParams.php +++ b/src/Dashboard/DashboardQueryParams.php @@ -2,8 +2,8 @@ namespace Code16\Sharp\Dashboard; -use Code16\Sharp\Utils\Filters\FilterContainer; -use Code16\Sharp\Utils\Filters\HasFiltersInQuery; +use Code16\Sharp\Filters\Concerns\HasFiltersInQuery; +use Code16\Sharp\Filters\FilterContainer\FilterContainer; class DashboardQueryParams { diff --git a/src/Dashboard/Filters/DashboardCheckFilter.php b/src/Dashboard/Filters/DashboardCheckFilter.php index dde07936b..eb3cfd195 100644 --- a/src/Dashboard/Filters/DashboardCheckFilter.php +++ b/src/Dashboard/Filters/DashboardCheckFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\CheckFilter; +use Code16\Sharp\Filters\CheckFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\CheckFilter + */ class DashboardCheckFilter extends CheckFilter {} diff --git a/src/Dashboard/Filters/DashboardDateRangeFilter.php b/src/Dashboard/Filters/DashboardDateRangeFilter.php index d8c8bc4da..20a0632de 100644 --- a/src/Dashboard/Filters/DashboardDateRangeFilter.php +++ b/src/Dashboard/Filters/DashboardDateRangeFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\DateRangeFilter; +use Code16\Sharp\Filters\DateRangeFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + */ abstract class DashboardDateRangeFilter extends DateRangeFilter {} diff --git a/src/Dashboard/Filters/DashboardDateRangeRequiredFilter.php b/src/Dashboard/Filters/DashboardDateRangeRequiredFilter.php index 959661c1d..60d9c46dc 100644 --- a/src/Dashboard/Filters/DashboardDateRangeRequiredFilter.php +++ b/src/Dashboard/Filters/DashboardDateRangeRequiredFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\DateRangeRequiredFilter; +use Code16\Sharp\Filters\DateRangeRequiredFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeRequiredFilter + */ abstract class DashboardDateRangeRequiredFilter extends DateRangeRequiredFilter {} diff --git a/src/Dashboard/Filters/DashboardSelectFilter.php b/src/Dashboard/Filters/DashboardSelectFilter.php index e9724a9b4..821dcece9 100644 --- a/src/Dashboard/Filters/DashboardSelectFilter.php +++ b/src/Dashboard/Filters/DashboardSelectFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\SelectFilter; +use Code16\Sharp\Filters\SelectFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectFilter instead. + */ abstract class DashboardSelectFilter extends SelectFilter {} diff --git a/src/Dashboard/Filters/DashboardSelectMultipleFilter.php b/src/Dashboard/Filters/DashboardSelectMultipleFilter.php index 541f6c792..5d2266bc9 100644 --- a/src/Dashboard/Filters/DashboardSelectMultipleFilter.php +++ b/src/Dashboard/Filters/DashboardSelectMultipleFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\SelectMultipleFilter; +use Code16\Sharp\Filters\SelectMultipleFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectMultipleFilter instead. + */ abstract class DashboardSelectMultipleFilter extends SelectMultipleFilter {} diff --git a/src/Dashboard/Filters/DashboardSelectRequiredFilter.php b/src/Dashboard/Filters/DashboardSelectRequiredFilter.php index 6688b8992..5eacf5925 100644 --- a/src/Dashboard/Filters/DashboardSelectRequiredFilter.php +++ b/src/Dashboard/Filters/DashboardSelectRequiredFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\Dashboard\Filters; -use Code16\Sharp\Utils\Filters\SelectRequiredFilter; +use Code16\Sharp\Filters\SelectRequiredFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectRequiredFilter instead. + */ abstract class DashboardSelectRequiredFilter extends SelectRequiredFilter {} diff --git a/src/Dashboard/SharpDashboard.php b/src/Dashboard/SharpDashboard.php index fe247ebc2..7c16dbf56 100644 --- a/src/Dashboard/SharpDashboard.php +++ b/src/Dashboard/SharpDashboard.php @@ -7,15 +7,15 @@ use Code16\Sharp\Dashboard\Widgets\SharpWidget; use Code16\Sharp\Dashboard\Widgets\WidgetsContainer; use Code16\Sharp\EntityList\Traits\HandleDashboardCommands; -use Code16\Sharp\Utils\Filters\HandleFilters; +use Code16\Sharp\Filters\Concerns\HasFilters; use Code16\Sharp\Utils\Traits\HandlePageAlertMessage; use Illuminate\Support\Arr; abstract class SharpDashboard { use HandleDashboardCommands; - use HandleFilters; use HandlePageAlertMessage; + use HasFilters; protected bool $dashboardBuilt = false; protected array $graphWidgetDataSets = []; diff --git a/src/Data/Filters/RemoteAutocompleteFilterData.php b/src/Data/Filters/RemoteAutocompleteFilterData.php new file mode 100644 index 000000000..0a591b3e3 --- /dev/null +++ b/src/Data/Filters/RemoteAutocompleteFilterData.php @@ -0,0 +1,37 @@ +')] + public mixed $value; + + public function __construct( + public string $key, + public ?string $label, + #[LiteralTypeScriptType('"'.FilterType::Select->value.'"')] + public FilterType $type, + public bool $multiple, + public bool $required, + public array $values, + public bool $master, + public bool $searchable, + /** string[] */ + public array $searchKeys, + ) {} + + public static function from(array $filter): self + { + return new self(...$filter); + } +} diff --git a/src/EntityList/EntityListQueryParams.php b/src/EntityList/EntityListQueryParams.php index 0280c8d03..1c432ac61 100644 --- a/src/EntityList/EntityListQueryParams.php +++ b/src/EntityList/EntityListQueryParams.php @@ -2,8 +2,8 @@ namespace Code16\Sharp\EntityList; -use Code16\Sharp\Utils\Filters\FilterContainer; -use Code16\Sharp\Utils\Filters\HasFiltersInQuery; +use Code16\Sharp\Filters\Concerns\HasFiltersInQuery; +use Code16\Sharp\Filters\FilterContainer\FilterContainer; use Code16\Sharp\Utils\StringUtil; class EntityListQueryParams diff --git a/src/EntityList/Filters/EntityListCheckFilter.php b/src/EntityList/Filters/EntityListCheckFilter.php index d7b5c295c..c49763c4b 100644 --- a/src/EntityList/Filters/EntityListCheckFilter.php +++ b/src/EntityList/Filters/EntityListCheckFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\CheckFilter; +use Code16\Sharp\Filters\CheckFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\CheckFilter + */ class EntityListCheckFilter extends CheckFilter {} diff --git a/src/EntityList/Filters/EntityListDateRangeFilter.php b/src/EntityList/Filters/EntityListDateRangeFilter.php index 9df30d667..867592662 100644 --- a/src/EntityList/Filters/EntityListDateRangeFilter.php +++ b/src/EntityList/Filters/EntityListDateRangeFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\DateRangeFilter; +use Code16\Sharp\Filters\DateRangeFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter + */ abstract class EntityListDateRangeFilter extends DateRangeFilter {} diff --git a/src/EntityList/Filters/EntityListDateRangeRequiredFilter.php b/src/EntityList/Filters/EntityListDateRangeRequiredFilter.php index 8513993bd..c8228974e 100644 --- a/src/EntityList/Filters/EntityListDateRangeRequiredFilter.php +++ b/src/EntityList/Filters/EntityListDateRangeRequiredFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\DateRangeRequiredFilter; +use Code16\Sharp\Filters\DateRangeRequiredFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeRequiredFilter + */ abstract class EntityListDateRangeRequiredFilter extends DateRangeRequiredFilter {} diff --git a/src/EntityList/Filters/EntityListSelectFilter.php b/src/EntityList/Filters/EntityListSelectFilter.php index 4defd6b4b..9124b817d 100644 --- a/src/EntityList/Filters/EntityListSelectFilter.php +++ b/src/EntityList/Filters/EntityListSelectFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\SelectFilter; +use Code16\Sharp\Filters\SelectFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectFilter + */ abstract class EntityListSelectFilter extends SelectFilter {} diff --git a/src/EntityList/Filters/EntityListSelectMultipleFilter.php b/src/EntityList/Filters/EntityListSelectMultipleFilter.php index 33b7b7261..cf4ca9527 100644 --- a/src/EntityList/Filters/EntityListSelectMultipleFilter.php +++ b/src/EntityList/Filters/EntityListSelectMultipleFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\SelectMultipleFilter; +use Code16\Sharp\Filters\SelectMultipleFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectMultipleFilter + */ abstract class EntityListSelectMultipleFilter extends SelectMultipleFilter {} diff --git a/src/EntityList/Filters/EntityListSelectRequiredFilter.php b/src/EntityList/Filters/EntityListSelectRequiredFilter.php index 698f93313..b855161dd 100644 --- a/src/EntityList/Filters/EntityListSelectRequiredFilter.php +++ b/src/EntityList/Filters/EntityListSelectRequiredFilter.php @@ -2,6 +2,9 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\SelectRequiredFilter; +use Code16\Sharp\Filters\SelectRequiredFilter; +/** + * @deprecated Use \Code16\Sharp\Filters\SelectRequiredFilter + */ abstract class EntityListSelectRequiredFilter extends SelectRequiredFilter {} diff --git a/src/EntityList/Filters/HiddenFilter.php b/src/EntityList/Filters/HiddenFilter.php index 968201516..8546589e4 100644 --- a/src/EntityList/Filters/HiddenFilter.php +++ b/src/EntityList/Filters/HiddenFilter.php @@ -2,7 +2,7 @@ namespace Code16\Sharp\EntityList\Filters; -use Code16\Sharp\Utils\Filters\Filter; +use Code16\Sharp\Filters\Filter; class HiddenFilter extends Filter { diff --git a/src/EntityList/SharpEntityList.php b/src/EntityList/SharpEntityList.php index ab6a02871..74f4f2f0d 100644 --- a/src/EntityList/SharpEntityList.php +++ b/src/EntityList/SharpEntityList.php @@ -8,7 +8,7 @@ use Code16\Sharp\EntityList\Traits\HandleEntityCommands; use Code16\Sharp\EntityList\Traits\HandleEntityState; use Code16\Sharp\EntityList\Traits\HandleInstanceCommands; -use Code16\Sharp\Utils\Filters\HandleFilters; +use Code16\Sharp\Filters\Concerns\HasFilters; use Code16\Sharp\Utils\Traits\HandlePageAlertMessage; use Code16\Sharp\Utils\Transformers\WithCustomTransformers; use Illuminate\Contracts\Support\Arrayable; @@ -19,9 +19,9 @@ abstract class SharpEntityList { use HandleEntityCommands; use HandleEntityState; - use HandleFilters; use HandleInstanceCommands; use HandlePageAlertMessage; + use HasFilters; use WithCustomTransformers; private ?EntityListFieldsContainer $fieldsContainer = null; diff --git a/src/Enums/FilterType.php b/src/Enums/FilterType.php index 2799c9bd5..e1163ccf1 100644 --- a/src/Enums/FilterType.php +++ b/src/Enums/FilterType.php @@ -4,6 +4,7 @@ enum FilterType: string { + case Autocomplete = 'autocomplete'; case Select = 'select'; case DateRange = 'daterange'; case Check = 'check'; diff --git a/src/Filters/CheckFilter.php b/src/Filters/CheckFilter.php new file mode 100644 index 000000000..79dbde1da --- /dev/null +++ b/src/Filters/CheckFilter.php @@ -0,0 +1,23 @@ + 'check', + ]); + } +} diff --git a/src/Utils/Filters/HandleFilters.php b/src/Filters/Concerns/HasFilters.php similarity index 79% rename from src/Utils/Filters/HandleFilters.php rename to src/Filters/Concerns/HasFilters.php index 303e695db..d01d24aee 100644 --- a/src/Utils/Filters/HandleFilters.php +++ b/src/Filters/Concerns/HasFilters.php @@ -1,8 +1,10 @@ start; + } + + public function getEnd(): Carbon + { + return $this->end; + } + + public function offsetExists(mixed $offset): bool + { + return property_exists($this, $offset); + } + + public function offsetGet(mixed $offset): mixed + { + return $this->$offset; + } + + public function offsetSet(mixed $offset, mixed $value): void {} + + public function offsetUnset(mixed $offset): void {} +} diff --git a/src/Utils/Filters/DateRangePreset.php b/src/Filters/DateRange/DateRangePreset.php similarity index 84% rename from src/Utils/Filters/DateRangePreset.php rename to src/Filters/DateRange/DateRangePreset.php index 879990414..86fd2e9bc 100644 --- a/src/Utils/Filters/DateRangePreset.php +++ b/src/Filters/DateRange/DateRangePreset.php @@ -1,11 +1,11 @@ dateFormat = $dateFormat; + + return $this; + } + + final public function configureMondayFirst(bool $isMondayFirst = true): self + { + $this->isMondayFirst = $isMondayFirst; + + return $this; + } + + final public function configureShowPresets(bool $showPresets = true): self + { + $this->showPresets = $showPresets; + + return $this; + } + + final public function getDateFormat(): string + { + return $this->dateFormat; + } + + final public function isMondayFirst(): bool + { + return $this->isMondayFirst; + } + + /** + * @return array + */ + final public function getPresets(): array + { + if (! $this->showPresets) { + return []; + } + + return [ + 'today' => new DateRangePreset( + start: today(), + end: today(), + label: __('sharp::filters.daterange.preset.today') + ), + 'yesterday' => new DateRangePreset( + start: today()->subDay(), + end: today()->subDay(), + label: __('sharp::filters.daterange.preset.yesterday') + ), + 'last_7_days' => new DateRangePreset( + start: today()->subDays(7), + end: today(), + label: __('sharp::filters.daterange.preset.last_7_days') + ), + 'last_30_days' => new DateRangePreset( + start: today()->subDays(30), + end: today(), + label: __('sharp::filters.daterange.preset.last_30_days') + ), + 'last_365_days' => new DateRangePreset( + start: today()->subDays(365), + end: today(), + label: __('sharp::filters.daterange.preset.last_365_days') + ), + 'this_month' => new DateRangePreset( + start: today()->startOfMonth(), + end: today()->endOfMonth(), + label: __('sharp::filters.daterange.preset.this_month') + ), + 'last_month' => new DateRangePreset( + start: today()->subMonth()->startOfMonth(), + end: today()->subMonth()->endOfMonth(), + label: __('sharp::filters.daterange.preset.last_month') + ), + 'this_year' => new DateRangePreset( + start: today()->startOfYear(), + end: today()->endOfYear(), + label: __('sharp::filters.daterange.preset.this_year') + ), + 'last_year' => new DateRangePreset( + start: today()->subYear()->startOfYear(), + end: today()->subYear()->endOfYear(), + label: __('sharp::filters.daterange.preset.last_year') + ), + ]; + } + + /** + * @internal + */ + final public function fromQueryParam($value): ?array + { + if (! $value) { + return null; + } + + [$start, $end] = explode('..', $value); + $start = Carbon::createFromFormat('Ymd', $start)->startOfDay(); + $end = Carbon::createFromFormat('Ymd', $end)->endOfDay(); + $presetKey = collect($this->getPresets()) + ->search(fn (DateRangePreset $preset) => $preset->getStart()->isSameDay($start) + && $preset->getEnd()->isSameDay($end)); + + return [ + 'start' => $start->format('Y-m-d'), + 'end' => $end->format('Y-m-d'), + 'formatted' => [ + 'start' => $start->isoFormat($this->getDateFormat()), + 'end' => $end->isoFormat($this->getDateFormat()), + ], + 'preset' => $presetKey ?: null, + ]; + } + + /** + * @internal + */ + final public function toQueryParam($value): ?string + { + if (! $value) { + return null; + } + + if (isset($value['preset'])) { + if ($preset = $this->getPresets()[$value['preset']] ?? null) { + return sprintf( + '%s..%s', + $preset->getStart()->format('Ymd'), + $preset->getEnd()->format('Ymd'), + ); + } + + return null; + } + + return sprintf( + '%s..%s', + Carbon::parse($value['start'])->format('Ymd'), + Carbon::parse($value['end'])->format('Ymd'), + ); + } + + public function toArray(): array + { + return parent::buildArray([ + 'type' => 'daterange', + 'required' => $this instanceof DateRangeRequiredFilter, + 'mondayFirst' => $this->isMondayFirst(), + 'presets' => collect($this->getPresets()) + ->map(fn ($preset, $key) => ['key' => $key, ...$preset->toArray()]) + ->values() + ->toArray(), + ]); + } + + public function formatRawValue(mixed $value): DateRangeFilterValue + { + return new DateRangeFilterValue(Carbon::parse($value['start']), Carbon::parse($value['end'])); + } +} diff --git a/src/Filters/DateRangeRequiredFilter.php b/src/Filters/DateRangeRequiredFilter.php new file mode 100644 index 000000000..82b9193a7 --- /dev/null +++ b/src/Filters/DateRangeRequiredFilter.php @@ -0,0 +1,11 @@ + Carbon::yesterday(), "end" => Carbon::today()] + */ + abstract public function defaultValue(): array; +} diff --git a/src/Utils/Filters/Filter.php b/src/Filters/Filter.php similarity index 97% rename from src/Utils/Filters/Filter.php rename to src/Filters/Filter.php index c3dd1fc4b..d94db43c1 100644 --- a/src/Utils/Filters/Filter.php +++ b/src/Filters/Filter.php @@ -1,6 +1,6 @@ get($this->getSessionKey()); + + if ($value === null) { + if (($value = $this->defaultValue()) !== null) { + session()->put($this->getSessionKey(), $value); + } + } + + return $value; + } + + final public function setCurrentValue(mixed $value): void + { + if ($value === null) { + session()->forget($this->getSessionKey()); + + return; + } + + $value = collect($this->formattedValues()) + ->where('id', request('value')) + ->first(); + + session()->put($this->getSessionKey(), $value ? $value['id'] : null); + } + + private function getSessionKey(): string + { + return '_sharp_retained_global_filter_'.$this->getKey(); + } + + public function authorize(): bool + { + return true; + } +} diff --git a/src/Utils/Filters/RemoteAutocompleteFilter.php b/src/Filters/RemoteAutocompleteFilter.php similarity index 96% rename from src/Utils/Filters/RemoteAutocompleteFilter.php rename to src/Filters/RemoteAutocompleteFilter.php index b86ffacef..1e4105e9a 100644 --- a/src/Utils/Filters/RemoteAutocompleteFilter.php +++ b/src/Filters/RemoteAutocompleteFilter.php @@ -1,6 +1,6 @@ isMaster; + } + + final public function isSearchable(): bool + { + return $this->isSearchable; + } + + final public function getSearchKeys(): array + { + return $this->searchKeys; + } + + final public function configureSearchable(bool $isSearchable = true): self + { + $this->isSearchable = $isSearchable; + + return $this; + } + + final public function configureSearchKeys(array $searchKeys): self + { + $this->searchKeys = $searchKeys; + + return $this; + } + + final public function configureMaster(bool $isMaster = true): self + { + $this->isMaster = $isMaster; + + return $this; + } + + public function fromQueryParam($value): mixed + { + return $value; + } + + public function toQueryParam($value): mixed + { + return $value; + } + + public function toArray(): array + { + return parent::buildArray([ + 'type' => 'select', + 'multiple' => $this instanceof SelectMultipleFilter, + 'required' => $this instanceof SelectRequiredFilter, + 'values' => $this->formattedValues(), + 'master' => $this->isMaster(), + 'searchable' => $this->isSearchable(), + 'searchKeys' => $this->getSearchKeys(), + ]); + } + + protected function formattedValues(): array + { + $values = $this->values(); + + if (! is_array(collect($values)->first())) { + return collect($values) + ->map(function ($label, $id) { + return compact('id', 'label'); + }) + ->values() + ->all(); + } + + return $values; + } + + abstract public function values(): array; +} diff --git a/src/Filters/SelectMultipleFilter.php b/src/Filters/SelectMultipleFilter.php new file mode 100644 index 000000000..133491f37 --- /dev/null +++ b/src/Filters/SelectMultipleFilter.php @@ -0,0 +1,18 @@ + 'check', - ]); - } -} +/** + * @deprecated Use \Code16\Sharp\Filters\CheckFilter instead. + */ +abstract class CheckFilter extends \Code16\Sharp\Filters\CheckFilter {} diff --git a/src/Utils/Filters/DateRangeFilter.php b/src/Utils/Filters/DateRangeFilter.php index aec3ddc30..c23ebd809 100644 --- a/src/Utils/Filters/DateRangeFilter.php +++ b/src/Utils/Filters/DateRangeFilter.php @@ -2,173 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -use Carbon\Carbon; - -abstract class DateRangeFilter extends Filter -{ - private string $dateFormat = 'YYYY-MM-DD'; - private bool $isMondayFirst = true; - private bool $showPresets = false; - - final public function configureDateFormat(string $dateFormat): self - { - $this->dateFormat = $dateFormat; - - return $this; - } - - final public function configureMondayFirst(bool $isMondayFirst = true): self - { - $this->isMondayFirst = $isMondayFirst; - - return $this; - } - - final public function configureShowPresets(bool $showPresets = true): self - { - $this->showPresets = $showPresets; - - return $this; - } - - final public function getDateFormat(): string - { - return $this->dateFormat; - } - - final public function isMondayFirst(): bool - { - return $this->isMondayFirst; - } - - /** - * @return array - */ - final public function getPresets(): array - { - if (! $this->showPresets) { - return []; - } - - return [ - 'today' => new DateRangePreset( - start: today(), - end: today(), - label: __('sharp::filters.daterange.preset.today') - ), - 'yesterday' => new DateRangePreset( - start: today()->subDay(), - end: today()->subDay(), - label: __('sharp::filters.daterange.preset.yesterday') - ), - 'last_7_days' => new DateRangePreset( - start: today()->subDays(7), - end: today(), - label: __('sharp::filters.daterange.preset.last_7_days') - ), - 'last_30_days' => new DateRangePreset( - start: today()->subDays(30), - end: today(), - label: __('sharp::filters.daterange.preset.last_30_days') - ), - 'last_365_days' => new DateRangePreset( - start: today()->subDays(365), - end: today(), - label: __('sharp::filters.daterange.preset.last_365_days') - ), - 'this_month' => new DateRangePreset( - start: today()->startOfMonth(), - end: today()->endOfMonth(), - label: __('sharp::filters.daterange.preset.this_month') - ), - 'last_month' => new DateRangePreset( - start: today()->subMonth()->startOfMonth(), - end: today()->subMonth()->endOfMonth(), - label: __('sharp::filters.daterange.preset.last_month') - ), - 'this_year' => new DateRangePreset( - start: today()->startOfYear(), - end: today()->endOfYear(), - label: __('sharp::filters.daterange.preset.this_year') - ), - 'last_year' => new DateRangePreset( - start: today()->subYear()->startOfYear(), - end: today()->subYear()->endOfYear(), - label: __('sharp::filters.daterange.preset.last_year') - ), - ]; - } - - /** - * @internal - */ - final public function fromQueryParam($value): ?array - { - if (! $value) { - return null; - } - - [$start, $end] = explode('..', $value); - $start = Carbon::createFromFormat('Ymd', $start)->startOfDay(); - $end = Carbon::createFromFormat('Ymd', $end)->endOfDay(); - $presetKey = collect($this->getPresets()) - ->search(fn (DateRangePreset $preset) => $preset->getStart()->isSameDay($start) - && $preset->getEnd()->isSameDay($end)); - - return [ - 'start' => $start->format('Y-m-d'), - 'end' => $end->format('Y-m-d'), - 'formatted' => [ - 'start' => $start->isoFormat($this->getDateFormat()), - 'end' => $end->isoFormat($this->getDateFormat()), - ], - 'preset' => $presetKey ?: null, - ]; - } - - /** - * @internal - */ - final public function toQueryParam($value): ?string - { - if (! $value) { - return null; - } - - if (isset($value['preset'])) { - if ($preset = $this->getPresets()[$value['preset']] ?? null) { - return sprintf( - '%s..%s', - $preset->getStart()->format('Ymd'), - $preset->getEnd()->format('Ymd'), - ); - } - - return null; - } - - return sprintf( - '%s..%s', - Carbon::parse($value['start'])->format('Ymd'), - Carbon::parse($value['end'])->format('Ymd'), - ); - } - - public function toArray(): array - { - return parent::buildArray([ - 'type' => 'daterange', - 'required' => $this instanceof DateRangeRequiredFilter, - 'mondayFirst' => $this->isMondayFirst(), - 'presets' => collect($this->getPresets()) - ->map(fn ($preset, $key) => ['key' => $key, ...$preset->toArray()]) - ->values() - ->toArray(), - ]); - } - - public function formatRawValue(mixed $value): DateRangeFilterValue - { - return new DateRangeFilterValue(Carbon::parse($value['start']), Carbon::parse($value['end'])); - } -} +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + */ +abstract class DateRangeFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/DateRangeFilterValue.php b/src/Utils/Filters/DateRangeFilterValue.php index 6c289d4f8..512bce15c 100644 --- a/src/Utils/Filters/DateRangeFilterValue.php +++ b/src/Utils/Filters/DateRangeFilterValue.php @@ -2,37 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -use ArrayAccess; -use Carbon\Carbon; - -final class DateRangeFilterValue implements ArrayAccess -{ - public function __construct( - protected Carbon $start, - protected Carbon $end, - ) {} - - public function getStart(): Carbon - { - return $this->start; - } - - public function getEnd(): Carbon - { - return $this->end; - } - - public function offsetExists(mixed $offset): bool - { - return property_exists($this, $offset); - } - - public function offsetGet(mixed $offset): mixed - { - return $this->$offset; - } - - public function offsetSet(mixed $offset, mixed $value): void {} - - public function offsetUnset(mixed $offset): void {} -} +/** + * @deprecated Use \Code16\Sharp\Filters\DateRange\DateRangeFilterValue instead. + */ +class DateRangeFilterValue extends \Code16\Sharp\Filters\DateRange\DateRangeFilterValue {} diff --git a/src/Utils/Filters/DateRangeRequiredFilter.php b/src/Utils/Filters/DateRangeRequiredFilter.php index 41734c6f9..ec278a8ae 100644 --- a/src/Utils/Filters/DateRangeRequiredFilter.php +++ b/src/Utils/Filters/DateRangeRequiredFilter.php @@ -2,10 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -abstract class DateRangeRequiredFilter extends DateRangeFilter -{ - /** - * @return array Expected format: ["start" => Carbon::yesterday(), "end" => Carbon::today()] - */ - abstract public function defaultValue(): array; -} +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + */ +abstract class DateRangeRequiredFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/GlobalRequiredFilter.php b/src/Utils/Filters/GlobalRequiredFilter.php index 7a586be92..7b56e6af7 100644 --- a/src/Utils/Filters/GlobalRequiredFilter.php +++ b/src/Utils/Filters/GlobalRequiredFilter.php @@ -2,43 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -abstract class GlobalRequiredFilter extends SelectRequiredFilter -{ - final public function currentValue(): mixed - { - $value = session()->get($this->getSessionKey()); - - if ($value === null) { - if (($value = $this->defaultValue()) !== null) { - session()->put($this->getSessionKey(), $value); - } - } - - return $value; - } - - final public function setCurrentValue(mixed $value): void - { - if ($value === null) { - session()->forget($this->getSessionKey()); - - return; - } - - $value = collect($this->formattedValues()) - ->where('id', request('value')) - ->first(); - - session()->put($this->getSessionKey(), $value ? $value['id'] : null); - } - - private function getSessionKey(): string - { - return '_sharp_retained_global_filter_'.$this->getKey(); - } - - public function authorize(): bool - { - return true; - } -} +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + */ +abstract class GlobalRequiredFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/SelectFilter.php b/src/Utils/Filters/SelectFilter.php index 14656e861..a04ab7dd5 100644 --- a/src/Utils/Filters/SelectFilter.php +++ b/src/Utils/Filters/SelectFilter.php @@ -2,86 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -abstract class SelectFilter extends Filter -{ - private bool $isMaster = false; - private bool $isSearchable = false; - private array $searchKeys = ['label']; - - final public function isMaster(): bool - { - return $this->isMaster; - } - - final public function isSearchable(): bool - { - return $this->isSearchable; - } - - final public function getSearchKeys(): array - { - return $this->searchKeys; - } - - final public function configureSearchable(bool $isSearchable = true): self - { - $this->isSearchable = $isSearchable; - - return $this; - } - - final public function configureSearchKeys(array $searchKeys): self - { - $this->searchKeys = $searchKeys; - - return $this; - } - - final public function configureMaster(bool $isMaster = true): self - { - $this->isMaster = $isMaster; - - return $this; - } - - public function fromQueryParam($value): mixed - { - return $value; - } - - public function toQueryParam($value): mixed - { - return $value; - } - - public function toArray(): array - { - return parent::buildArray([ - 'type' => 'select', - 'multiple' => $this instanceof SelectMultipleFilter, - 'required' => $this instanceof SelectRequiredFilter, - 'values' => $this->formattedValues(), - 'master' => $this->isMaster(), - 'searchable' => $this->isSearchable(), - 'searchKeys' => $this->getSearchKeys(), - ]); - } - - protected function formattedValues(): array - { - $values = $this->values(); - - if (! is_array(collect($values)->first())) { - return collect($values) - ->map(function ($label, $id) { - return compact('id', 'label'); - }) - ->values() - ->all(); - } - - return $values; - } - - abstract public function values(): array; -} +/** + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + */ +abstract class SelectFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/SelectMultipleFilter.php b/src/Utils/Filters/SelectMultipleFilter.php index 6d4ae08c2..7c7612162 100644 --- a/src/Utils/Filters/SelectMultipleFilter.php +++ b/src/Utils/Filters/SelectMultipleFilter.php @@ -2,17 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -use Illuminate\Support\Arr; - -abstract class SelectMultipleFilter extends SelectFilter -{ - public function fromQueryParam($value): array - { - return $value ? explode(',', $value) : []; - } - - public function toQueryParam($value): ?string - { - return $value ? implode(',', Arr::wrap($value)) : null; - } -} +/** + * @deprecated Use \Code16\Sharp\Filters\SelectMultipleFilter instead. + */ +abstract class SelectMultipleFilter extends \Code16\Sharp\Filters\SelectMultipleFilter {} diff --git a/src/Utils/Filters/SelectRequiredFilter.php b/src/Utils/Filters/SelectRequiredFilter.php index 2567f9053..4b9ccc4d6 100644 --- a/src/Utils/Filters/SelectRequiredFilter.php +++ b/src/Utils/Filters/SelectRequiredFilter.php @@ -2,7 +2,7 @@ namespace Code16\Sharp\Utils\Filters; -abstract class SelectRequiredFilter extends SelectFilter -{ - abstract public function defaultValue(): mixed; -} +/** + * @deprecated Use \Code16\Sharp\Filters\SelectRequiredFilter instead. + */ +abstract class SelectRequiredFilter extends \Code16\Sharp\Filters\SelectRequiredFilter {} diff --git a/tests-e2e/site/app/Sharp/TestModels/TestModelList.php b/tests-e2e/site/app/Sharp/TestModels/TestModelList.php index ac362232f..0ae14d35f 100644 --- a/tests-e2e/site/app/Sharp/TestModels/TestModelList.php +++ b/tests-e2e/site/app/Sharp/TestModels/TestModelList.php @@ -29,7 +29,7 @@ use Code16\Sharp\EntityList\Fields\EntityListField; use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; use Code16\Sharp\EntityList\SharpEntityList; -use Code16\Sharp\Utils\Filters\DateRangeFilterValue; +use Code16\Sharp\Filters\DateRange\DateRangeFilterValue; use Illuminate\Contracts\Support\Arrayable; use Illuminate\Database\Eloquent\Builder; diff --git a/tests/Http/GlobalFilterControllerTest.php b/tests/Http/GlobalFilterControllerTest.php index bdbae6568..a440310ed 100644 --- a/tests/Http/GlobalFilterControllerTest.php +++ b/tests/Http/GlobalFilterControllerTest.php @@ -1,7 +1,7 @@ '20190201..20190210'])->filterFor('range')) - ->toBeInstanceOf(\Code16\Sharp\Utils\Filters\DateRangeFilterValue::class) + ->toBeInstanceOf(\Code16\Sharp\Filters\DateRange\DateRangeFilterValue::class) ->getStart()->toEqual(Carbon::parse('20190201')) ->getEnd()->toEqual(Carbon::parse('20190210')); diff --git a/tests/Unit/Utils/SharpLinkToTest.php b/tests/Unit/Utils/SharpLinkToTest.php index d55f4db58..90800b0b1 100644 --- a/tests/Unit/Utils/SharpLinkToTest.php +++ b/tests/Unit/Utils/SharpLinkToTest.php @@ -2,8 +2,8 @@ use Code16\Sharp\Config\SharpConfigBuilder; use Code16\Sharp\Exceptions\SharpInvalidBreadcrumbItemException; +use Code16\Sharp\Filters\SelectFilter; use Code16\Sharp\Tests\Fixtures\Entities\PersonEntity; -use Code16\Sharp\Utils\Filters\SelectFilter; use Code16\Sharp\Utils\Links\BreadcrumbBuilder; use Code16\Sharp\Utils\Links\LinkToDashboard; use Code16\Sharp\Utils\Links\LinkToEntityList; From 99ac771b5fc4fa1f418811fc9df15d1cbe20c9a9 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 27 May 2025 18:23:56 +0200 Subject: [PATCH 03/14] wip --- ...FilterData.php => AutocompleteRemoteFilterData.php} | 8 ++------ src/Data/Filters/FilterData.php | 8 +++++--- src/Enums/FilterType.php | 2 +- ...completeFilter.php => AutocompleteRemoteFilter.php} | 10 ++++++---- ...Filter.php => AutocompleteRemoteMultipleFilter.php} | 2 +- ...Filter.php => AutocompleteRemoteRequiredFilter.php} | 2 +- src/Filters/CheckFilter.php | 4 +++- src/Filters/DateRangeFilter.php | 3 ++- src/Filters/SelectFilter.php | 4 +++- 9 files changed, 24 insertions(+), 19 deletions(-) rename src/Data/Filters/{RemoteAutocompleteFilterData.php => AutocompleteRemoteFilterData.php} (74%) rename src/Filters/{RemoteAutocompleteFilter.php => AutocompleteRemoteFilter.php} (74%) rename src/Filters/{RemoteAutocompleteMultipleFilter.php => AutocompleteRemoteMultipleFilter.php} (79%) rename src/Filters/{RemoteAutocompleteRequiredFilter.php => AutocompleteRemoteRequiredFilter.php} (54%) diff --git a/src/Data/Filters/RemoteAutocompleteFilterData.php b/src/Data/Filters/AutocompleteRemoteFilterData.php similarity index 74% rename from src/Data/Filters/RemoteAutocompleteFilterData.php rename to src/Data/Filters/AutocompleteRemoteFilterData.php index 0a591b3e3..810634c05 100644 --- a/src/Data/Filters/RemoteAutocompleteFilterData.php +++ b/src/Data/Filters/AutocompleteRemoteFilterData.php @@ -10,7 +10,7 @@ /** * @internal */ -final class RemoteAutocompleteFilterData extends Data +final class AutocompleteRemoteFilterData extends Data { #[Optional] #[LiteralTypeScriptType('Array<{ id: string|number, label: string }>')] @@ -19,15 +19,11 @@ final class RemoteAutocompleteFilterData extends Data public function __construct( public string $key, public ?string $label, - #[LiteralTypeScriptType('"'.FilterType::Select->value.'"')] + #[LiteralTypeScriptType('"'.FilterType::AutocompleteRemote->value.'"')] public FilterType $type, public bool $multiple, public bool $required, - public array $values, public bool $master, - public bool $searchable, - /** string[] */ - public array $searchKeys, ) {} public static function from(array $filter): self diff --git a/src/Data/Filters/FilterData.php b/src/Data/Filters/FilterData.php index 9d32d3efe..b7e7606b9 100644 --- a/src/Data/Filters/FilterData.php +++ b/src/Data/Filters/FilterData.php @@ -7,7 +7,8 @@ use Spatie\TypeScriptTransformer\Attributes\TypeScriptType; #[TypeScriptType( - CheckFilterData::class + AutocompleteRemoteFilterData::class + .'|'.CheckFilterData::class .'|'.DateRangeFilterData::class .'|'.SelectFilterData::class )] @@ -23,9 +24,10 @@ public static function from(array $filter): Data $filter['type'] = FilterType::from($filter['type']); return match ($filter['type']) { - FilterType::Select => SelectFilterData::from($filter), - FilterType::DateRange => DateRangeFilterData::from($filter), + FilterType::AutocompleteRemote => AutocompleteRemoteFilterData::from($filter), FilterType::Check => CheckFilterData::from($filter), + FilterType::DateRange => DateRangeFilterData::from($filter), + FilterType::Select => SelectFilterData::from($filter), }; } } diff --git a/src/Enums/FilterType.php b/src/Enums/FilterType.php index e1163ccf1..671ac69aa 100644 --- a/src/Enums/FilterType.php +++ b/src/Enums/FilterType.php @@ -4,7 +4,7 @@ enum FilterType: string { - case Autocomplete = 'autocomplete'; + case AutocompleteRemote = 'autocompleteRemote'; case Select = 'select'; case DateRange = 'daterange'; case Check = 'check'; diff --git a/src/Filters/RemoteAutocompleteFilter.php b/src/Filters/AutocompleteRemoteFilter.php similarity index 74% rename from src/Filters/RemoteAutocompleteFilter.php rename to src/Filters/AutocompleteRemoteFilter.php index 1e4105e9a..9d42d61ce 100644 --- a/src/Filters/RemoteAutocompleteFilter.php +++ b/src/Filters/AutocompleteRemoteFilter.php @@ -2,7 +2,9 @@ namespace Code16\Sharp\Filters; -abstract class RemoteAutocompleteFilter extends Filter +use Code16\Sharp\Enums\FilterType; + +abstract class AutocompleteRemoteFilter extends Filter { private bool $isMaster = false; @@ -31,10 +33,10 @@ public function toQueryParam($value): mixed public function toArray(): array { return parent::buildArray([ - 'type' => 'remoteAutocomplete', + 'type' => FilterType::AutocompleteRemote->value, 'master' => $this->isMaster(), - 'required' => $this instanceof RemoteAutocompleteRequiredFilter, - 'multiple' => $this instanceof RemoteAutocompleteMultipleFilter, + 'required' => $this instanceof AutocompleteRemoteRequiredFilter, + 'multiple' => $this instanceof AutocompleteRemoteMultipleFilter, ]); } diff --git a/src/Filters/RemoteAutocompleteMultipleFilter.php b/src/Filters/AutocompleteRemoteMultipleFilter.php similarity index 79% rename from src/Filters/RemoteAutocompleteMultipleFilter.php rename to src/Filters/AutocompleteRemoteMultipleFilter.php index b7c6edfa7..fbee8e9dc 100644 --- a/src/Filters/RemoteAutocompleteMultipleFilter.php +++ b/src/Filters/AutocompleteRemoteMultipleFilter.php @@ -4,7 +4,7 @@ use Illuminate\Support\Arr; -abstract class RemoteAutocompleteMultipleFilter extends RemoteAutocompleteFilter +abstract class AutocompleteRemoteMultipleFilter extends AutocompleteRemoteFilter { public function fromQueryParam($value): array { diff --git a/src/Filters/RemoteAutocompleteRequiredFilter.php b/src/Filters/AutocompleteRemoteRequiredFilter.php similarity index 54% rename from src/Filters/RemoteAutocompleteRequiredFilter.php rename to src/Filters/AutocompleteRemoteRequiredFilter.php index bb427869b..baa285055 100644 --- a/src/Filters/RemoteAutocompleteRequiredFilter.php +++ b/src/Filters/AutocompleteRemoteRequiredFilter.php @@ -2,7 +2,7 @@ namespace Code16\Sharp\Filters; -abstract class RemoteAutocompleteRequiredFilter extends RemoteAutocompleteFilter +abstract class AutocompleteRemoteRequiredFilter extends AutocompleteRemoteFilter { abstract public function defaultValue(): mixed; } diff --git a/src/Filters/CheckFilter.php b/src/Filters/CheckFilter.php index 79dbde1da..eb8ad0048 100644 --- a/src/Filters/CheckFilter.php +++ b/src/Filters/CheckFilter.php @@ -2,6 +2,8 @@ namespace Code16\Sharp\Filters; +use Code16\Sharp\Enums\FilterType; + class CheckFilter extends Filter { public function fromQueryParam($value): mixed @@ -17,7 +19,7 @@ public function toQueryParam($value): mixed public function toArray(): array { return parent::buildArray([ - 'type' => 'check', + 'type' => FilterType::Check->value, ]); } } diff --git a/src/Filters/DateRangeFilter.php b/src/Filters/DateRangeFilter.php index 5fcbb54dc..4557bc9d2 100644 --- a/src/Filters/DateRangeFilter.php +++ b/src/Filters/DateRangeFilter.php @@ -3,6 +3,7 @@ namespace Code16\Sharp\Filters; use Carbon\Carbon; +use Code16\Sharp\Enums\FilterType; use Code16\Sharp\Filters\DateRange\DateRangeFilterValue; use Code16\Sharp\Filters\DateRange\DateRangePreset; @@ -159,7 +160,7 @@ final public function toQueryParam($value): ?string public function toArray(): array { return parent::buildArray([ - 'type' => 'daterange', + 'type' => FilterType::DateRange->value, 'required' => $this instanceof DateRangeRequiredFilter, 'mondayFirst' => $this->isMondayFirst(), 'presets' => collect($this->getPresets()) diff --git a/src/Filters/SelectFilter.php b/src/Filters/SelectFilter.php index 33f550c20..a95050256 100644 --- a/src/Filters/SelectFilter.php +++ b/src/Filters/SelectFilter.php @@ -2,6 +2,8 @@ namespace Code16\Sharp\Filters; +use Code16\Sharp\Enums\FilterType; + abstract class SelectFilter extends Filter { private bool $isMaster = false; @@ -57,7 +59,7 @@ public function toQueryParam($value): mixed public function toArray(): array { return parent::buildArray([ - 'type' => 'select', + 'type' => FilterType::Select->value, 'multiple' => $this instanceof SelectMultipleFilter, 'required' => $this instanceof SelectRequiredFilter, 'values' => $this->formattedValues(), From 74d67715df328069490e93abc335aaee605306e1 Mon Sep 17 00:00:00 2001 From: antoine Date: Tue, 27 May 2025 18:24:53 +0200 Subject: [PATCH 04/14] Typo --- src/Utils/Filters/CheckFilter.php | 2 +- src/Utils/Filters/DateRangeFilter.php | 2 +- src/Utils/Filters/DateRangeFilterValue.php | 2 +- src/Utils/Filters/DateRangeRequiredFilter.php | 2 +- src/Utils/Filters/GlobalRequiredFilter.php | 2 +- src/Utils/Filters/SelectFilter.php | 2 +- src/Utils/Filters/SelectMultipleFilter.php | 2 +- src/Utils/Filters/SelectRequiredFilter.php | 2 +- 8 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Utils/Filters/CheckFilter.php b/src/Utils/Filters/CheckFilter.php index 3d85a7301..f7ffd02ba 100644 --- a/src/Utils/Filters/CheckFilter.php +++ b/src/Utils/Filters/CheckFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\CheckFilter instead. + * @deprecated Use \Code16\Sharp\Filters\CheckFilter */ abstract class CheckFilter extends \Code16\Sharp\Filters\CheckFilter {} diff --git a/src/Utils/Filters/DateRangeFilter.php b/src/Utils/Filters/DateRangeFilter.php index c23ebd809..813db778c 100644 --- a/src/Utils/Filters/DateRangeFilter.php +++ b/src/Utils/Filters/DateRangeFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter */ abstract class DateRangeFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/DateRangeFilterValue.php b/src/Utils/Filters/DateRangeFilterValue.php index 512bce15c..0f0f22ee0 100644 --- a/src/Utils/Filters/DateRangeFilterValue.php +++ b/src/Utils/Filters/DateRangeFilterValue.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\DateRange\DateRangeFilterValue instead. + * @deprecated Use \Code16\Sharp\Filters\DateRange\DateRangeFilterValue */ class DateRangeFilterValue extends \Code16\Sharp\Filters\DateRange\DateRangeFilterValue {} diff --git a/src/Utils/Filters/DateRangeRequiredFilter.php b/src/Utils/Filters/DateRangeRequiredFilter.php index ec278a8ae..2d7a71fed 100644 --- a/src/Utils/Filters/DateRangeRequiredFilter.php +++ b/src/Utils/Filters/DateRangeRequiredFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter */ abstract class DateRangeRequiredFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/GlobalRequiredFilter.php b/src/Utils/Filters/GlobalRequiredFilter.php index 7b56e6af7..161d74db9 100644 --- a/src/Utils/Filters/GlobalRequiredFilter.php +++ b/src/Utils/Filters/GlobalRequiredFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter */ abstract class GlobalRequiredFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/SelectFilter.php b/src/Utils/Filters/SelectFilter.php index a04ab7dd5..ee09fb3db 100644 --- a/src/Utils/Filters/SelectFilter.php +++ b/src/Utils/Filters/SelectFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter instead. + * @deprecated Use \Code16\Sharp\Filters\DateRangeFilter */ abstract class SelectFilter extends \Code16\Sharp\Filters\DateRangeFilter {} diff --git a/src/Utils/Filters/SelectMultipleFilter.php b/src/Utils/Filters/SelectMultipleFilter.php index 7c7612162..45a23dc4a 100644 --- a/src/Utils/Filters/SelectMultipleFilter.php +++ b/src/Utils/Filters/SelectMultipleFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\SelectMultipleFilter instead. + * @deprecated Use \Code16\Sharp\Filters\SelectMultipleFilter */ abstract class SelectMultipleFilter extends \Code16\Sharp\Filters\SelectMultipleFilter {} diff --git a/src/Utils/Filters/SelectRequiredFilter.php b/src/Utils/Filters/SelectRequiredFilter.php index 4b9ccc4d6..d9123bc24 100644 --- a/src/Utils/Filters/SelectRequiredFilter.php +++ b/src/Utils/Filters/SelectRequiredFilter.php @@ -3,6 +3,6 @@ namespace Code16\Sharp\Utils\Filters; /** - * @deprecated Use \Code16\Sharp\Filters\SelectRequiredFilter instead. + * @deprecated Use \Code16\Sharp\Filters\SelectRequiredFilter */ abstract class SelectRequiredFilter extends \Code16\Sharp\Filters\SelectRequiredFilter {} From ae6d70da41834a7a1595c248cfc16bbce6b1aa41 Mon Sep 17 00:00:00 2001 From: antoine Date: Wed, 28 May 2025 18:53:02 +0200 Subject: [PATCH 05/14] Front autocomplete remote filter --- demo/app/Sharp/Utils/Filters/AuthorFilter.php | 15 +- .../Sharp/Utils/Filters/CategoryFilter.php | 4 +- demo/app/Sharp/Utils/Filters/PeriodFilter.php | 4 +- .../Utils/Filters/PeriodRequiredFilter.php | 4 +- demo/app/Sharp/Utils/Filters/StateFilter.php | 4 +- resources/js/Pages/Dashboard/Dashboard.vue | 5 +- .../js/composables/useRemoteAutocomplete.ts | 72 +++++++ .../js/entity-list/components/EntityList.vue | 2 + resources/js/filters/FilterManager.ts | 2 +- resources/js/filters/components/Filter.vue | 2 + .../filters/AutocompleteRemoteFilter.vue | 179 ++++++++++++++++++ .../filters/AutocompleteRemoteFilterValue.vue | 25 +++ .../components/filters/SelectButton.vue | 42 ++++ .../components/filters/SelectFilter.vue | 36 +--- .../components/filters/SelectFilterValue.vue | 2 +- resources/js/filters/types.ts | 1 + .../form/components/fields/Autocomplete.vue | 82 +++----- resources/js/types/generated.d.ts | 18 +- resources/js/types/routes.d.ts | 9 + .../Filters/AutocompleteRemoteFilterData.php | 2 + src/Filters/AutocompleteRemoteFilter.php | 55 +++++- .../AutocompleteRemoteMultipleFilter.php | 13 +- .../Concerns/ProvidesFilterValuesToFront.php | 3 +- .../Api/ApiFilterAutocompleteController.php | 34 ++++ src/routes/api.php | 4 + 25 files changed, 499 insertions(+), 120 deletions(-) create mode 100644 resources/js/composables/useRemoteAutocomplete.ts create mode 100644 resources/js/filters/components/filters/AutocompleteRemoteFilter.vue create mode 100644 resources/js/filters/components/filters/AutocompleteRemoteFilterValue.vue create mode 100644 resources/js/filters/components/filters/SelectButton.vue create mode 100644 src/Http/Controllers/Api/ApiFilterAutocompleteController.php diff --git a/demo/app/Sharp/Utils/Filters/AuthorFilter.php b/demo/app/Sharp/Utils/Filters/AuthorFilter.php index f1c9d4d41..e16d73f46 100644 --- a/demo/app/Sharp/Utils/Filters/AuthorFilter.php +++ b/demo/app/Sharp/Utils/Filters/AuthorFilter.php @@ -3,21 +3,30 @@ namespace App\Sharp\Utils\Filters; use App\Models\User; -use Code16\Sharp\EntityList\Filters\EntityListSelectFilter; +use Code16\Sharp\Filters\AutocompleteRemoteFilter; -class AuthorFilter extends EntityListSelectFilter +class AuthorFilter extends AutocompleteRemoteFilter { public function buildFilterConfig(): void { $this->configureLabel('Author'); } - public function values(): array + public function values(string $query): array { return User::whereHas('posts') ->orderBy('name') + ->when($query, function ($queryBuilder) use ($query) { + $queryBuilder->where('name', 'like', "%$query%"); + }) + ->get() ->pluck('name', 'id') ->map(fn ($name, $id) => auth()->id() === $id ? "$name (me)" : $name) ->toArray(); } + + public function valuesFor(array $ids): array + { + return User::whereIn('id', $ids)->get()->pluck('name', 'id')->toArray(); + } } diff --git a/demo/app/Sharp/Utils/Filters/CategoryFilter.php b/demo/app/Sharp/Utils/Filters/CategoryFilter.php index 7ab17e2eb..59cdf4184 100644 --- a/demo/app/Sharp/Utils/Filters/CategoryFilter.php +++ b/demo/app/Sharp/Utils/Filters/CategoryFilter.php @@ -3,9 +3,9 @@ namespace App\Sharp\Utils\Filters; use App\Models\Category; -use Code16\Sharp\EntityList\Filters\EntityListSelectMultipleFilter; +use Code16\Sharp\Filters\SelectMultipleFilter; -class CategoryFilter extends EntityListSelectMultipleFilter +class CategoryFilter extends SelectMultipleFilter { public function buildFilterConfig(): void { diff --git a/demo/app/Sharp/Utils/Filters/PeriodFilter.php b/demo/app/Sharp/Utils/Filters/PeriodFilter.php index 9b955a893..b9c4fc94e 100644 --- a/demo/app/Sharp/Utils/Filters/PeriodFilter.php +++ b/demo/app/Sharp/Utils/Filters/PeriodFilter.php @@ -2,9 +2,9 @@ namespace App\Sharp\Utils\Filters; -use Code16\Sharp\EntityList\Filters\EntityListDateRangeFilter; +use Code16\Sharp\Filters\DateRangeFilter; -class PeriodFilter extends EntityListDateRangeFilter +class PeriodFilter extends DateRangeFilter { public function buildFilterConfig(): void { diff --git a/demo/app/Sharp/Utils/Filters/PeriodRequiredFilter.php b/demo/app/Sharp/Utils/Filters/PeriodRequiredFilter.php index 8804c6e6e..ef01d716b 100644 --- a/demo/app/Sharp/Utils/Filters/PeriodRequiredFilter.php +++ b/demo/app/Sharp/Utils/Filters/PeriodRequiredFilter.php @@ -2,9 +2,9 @@ namespace App\Sharp\Utils\Filters; -use Code16\Sharp\Dashboard\Filters\DashboardDateRangeRequiredFilter; +use Code16\Sharp\Filters\DateRangeRequiredFilter; -class PeriodRequiredFilter extends DashboardDateRangeRequiredFilter +class PeriodRequiredFilter extends DateRangeRequiredFilter { public function buildFilterConfig(): void { diff --git a/demo/app/Sharp/Utils/Filters/StateFilter.php b/demo/app/Sharp/Utils/Filters/StateFilter.php index 2d8397a7a..0b50afe9c 100644 --- a/demo/app/Sharp/Utils/Filters/StateFilter.php +++ b/demo/app/Sharp/Utils/Filters/StateFilter.php @@ -2,9 +2,9 @@ namespace App\Sharp\Utils\Filters; -use Code16\Sharp\EntityList\Filters\EntityListSelectFilter; +use Code16\Sharp\Filters\SelectFilter; -class StateFilter extends EntityListSelectFilter +class StateFilter extends SelectFilter { public function buildFilterConfig(): void { diff --git a/resources/js/Pages/Dashboard/Dashboard.vue b/resources/js/Pages/Dashboard/Dashboard.vue index a2bcf8f6a..93a8b9caf 100644 --- a/resources/js/Pages/Dashboard/Dashboard.vue +++ b/resources/js/Pages/Dashboard/Dashboard.vue @@ -29,7 +29,6 @@ DialogTrigger } from "@/components/ui/dialog"; import { Filter } from "lucide-vue-next"; - import { CardContent, CardHeader, CardTitle } from "@/components/ui/card"; import { Badge } from "@/components/ui/badge"; const props = defineProps<{ @@ -108,6 +107,7 @@ :filter="filter" :value="filters.currentValues[filter.key]" :valuated="filters.isValuated([filter])" + :entity-key="dashboardKey" inline @input="onFilterChange(filter, $event)" /> @@ -140,6 +140,7 @@ :filter="filter" :value="filters.currentValues[filter.key]" :valuated="filters.isValuated([filter])" + :entity-key="dashboardKey" @input="onFilterChange(filter, $event)" /> @@ -205,6 +206,7 @@ :filter="filter" :value="filters.currentValues[filter.key]" :valuated="filters.isValuated([filter])" + :entity-key="dashboardKey" inline @input="onFilterChange(filter, $event)" /> @@ -237,6 +239,7 @@ :filter="filter" :value="filters.currentValues[filter.key]" :valuated="filters.isValuated([filter])" + :entity-key="dashboardKey" @input="onFilterChange(filter, $event)" /> diff --git a/resources/js/composables/useRemoteAutocomplete.ts b/resources/js/composables/useRemoteAutocomplete.ts new file mode 100644 index 000000000..4d7e91bd7 --- /dev/null +++ b/resources/js/composables/useRemoteAutocomplete.ts @@ -0,0 +1,72 @@ +import { AxiosError, AxiosResponse, isCancel } from "axios"; +import { Ref, ref } from "vue"; + + +export function useRemoteAutocomplete( + post: (props: ReturnType) => Promise, + options: { + minLength: number; + debounceDelay: number; + } +) { + let abortController: AbortController | null = null; + let timeout = null; + let loadingTimeout = null; + const results = ref([]); + const loading = ref(false); + + function postProps(query: string) { + return { + query, + signal: abortController.signal, + onSuccess: (response: AxiosResponse) => { + clearTimeout(loadingTimeout); + loading.value = false; + return response; + }, + onError: (e: AxiosError) => { + if(isCancel(e)) { + clearTimeout(loadingTimeout); + } + return Promise.reject(e); + } + } + } + + function search(query: string) { + clearTimeout(loadingTimeout); + loadingTimeout = setTimeout(() => { + loading.value = true; + }, 200); + abortController?.abort(); + abortController = new AbortController(); + + return post(postProps(query)) + .then(r => results.value = r); + } + + return { + results, + loading, + async search(query: string, immediate?: boolean) { + return new Promise((resolve, reject) => { + clearTimeout(timeout); + if(query.length >= options.minLength) { + if(!results.value.length) { + loading.value = true; + } + if(immediate) { + search(query).then(resolve, reject) + } else { + timeout = setTimeout(() => search(query).then(resolve, reject), options.debounceDelay) + } + } else { + clearTimeout(timeout); + loading.value = false; + results.value = []; + resolve([] as T); + } + }); + } + } +} diff --git a/resources/js/entity-list/components/EntityList.vue b/resources/js/entity-list/components/EntityList.vue index 491ca2d38..cfd6776dd 100644 --- a/resources/js/entity-list/components/EntityList.vue +++ b/resources/js/entity-list/components/EntityList.vue @@ -543,6 +543,7 @@ :value="filters.currentValues[filter.key]" :disabled="reordering" :valuated="filters.isValuated([filter])" + :entity-key="entityKey" @input="onFilterChange(filter, $event)" /> @@ -586,6 +587,7 @@ :value="filters.currentValues[filter.key]" :disabled="reordering || selecting" :valuated="filters.isValuated([filter])" + :entity-key="entityKey" inline @input="onFilterChange(filter, $event)" /> diff --git a/resources/js/filters/FilterManager.ts b/resources/js/filters/FilterManager.ts index 6cba9b4b8..0ca736551 100644 --- a/resources/js/filters/FilterManager.ts +++ b/resources/js/filters/FilterManager.ts @@ -37,7 +37,7 @@ export class FilterManager { } nextValues(filter: FilterData, value: ParsedValue): FilterValues { - if(filter.type === 'select' && filter.master) { + if((filter.type === 'select' || filter.type === 'autocompleteRemote') && filter.master) { return { ...Object.fromEntries(Object.entries(this.currentValues).map(([key, value]) => [key, null])), [filter.key]: value, diff --git a/resources/js/filters/components/Filter.vue b/resources/js/filters/components/Filter.vue index 3a54aec2f..33ed4775c 100644 --- a/resources/js/filters/components/Filter.vue +++ b/resources/js/filters/components/Filter.vue @@ -5,9 +5,11 @@ import SelectFilter from "./filters/SelectFilter.vue"; import type { Component } from "vue"; import { FilterProps } from "@/filters/types"; + import AutocompleteRemoteFilter from "@/filters/components/filters/AutocompleteRemoteFilter.vue"; const props = defineProps>(); const components: Record = { + 'autocompleteRemote': AutocompleteRemoteFilter, 'check': CheckFilter, 'daterange': DateRangeFilter, 'select': SelectFilter, diff --git a/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue b/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue new file mode 100644 index 000000000..7a04f23e2 --- /dev/null +++ b/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue @@ -0,0 +1,179 @@ + + + diff --git a/resources/js/filters/components/filters/AutocompleteRemoteFilterValue.vue b/resources/js/filters/components/filters/AutocompleteRemoteFilterValue.vue new file mode 100644 index 000000000..ad1d3428c --- /dev/null +++ b/resources/js/filters/components/filters/AutocompleteRemoteFilterValue.vue @@ -0,0 +1,25 @@ + + + diff --git a/resources/js/filters/components/filters/SelectButton.vue b/resources/js/filters/components/filters/SelectButton.vue new file mode 100644 index 000000000..07d3f218b --- /dev/null +++ b/resources/js/filters/components/filters/SelectButton.vue @@ -0,0 +1,42 @@ + + + diff --git a/resources/js/filters/components/filters/SelectFilter.vue b/resources/js/filters/components/filters/SelectFilter.vue index 5946b88db..e318df904 100644 --- a/resources/js/filters/components/filters/SelectFilter.vue +++ b/resources/js/filters/components/filters/SelectFilter.vue @@ -20,6 +20,7 @@ import { computed, ref } from "vue"; import SelectFilterValue from "@/filters/components/filters/SelectFilterValue.vue"; import { FilterEmits, FilterProps } from "@/filters/types"; + import SelectButton from "@/filters/components/filters/SelectButton.vue"; const props = defineProps>(); const emit = defineEmits>(); @@ -54,36 +55,14 @@ - + + diff --git a/resources/js/filters/types.ts b/resources/js/filters/types.ts index c8f1c5f9b..579d6dd5a 100644 --- a/resources/js/filters/types.ts +++ b/resources/js/filters/types.ts @@ -9,6 +9,7 @@ export type FilterProps = { filter: Omit; value: Value; valuated: boolean; + entityKey: string; disabled?: boolean; inline?: boolean; } diff --git a/resources/js/form/components/fields/Autocomplete.vue b/resources/js/form/components/fields/Autocomplete.vue index a2459fb8e..0a49d6016 100644 --- a/resources/js/form/components/fields/Autocomplete.vue +++ b/resources/js/form/components/fields/Autocomplete.vue @@ -26,6 +26,7 @@ import { useParentCommands } from "@/commands/useCommands"; import { useIsInDialog } from "@/components/ui/dialog/Dialog.vue"; import { useFullTextSearch } from "@/composables/useFullTextSearch"; + import { useRemoteAutocomplete } from "@/composables/useRemoteAutocomplete"; const props = defineProps>(); const emit = defineEmits>(); @@ -34,11 +35,6 @@ const open = ref(false); const searchTerm = ref(''); const results = ref([]); - const loading = ref(false); - - let abortController: AbortController | null = null; - let timeout = null; - let loadingTimeout = null; const parentCommands = useParentCommands(); const isInDialog = useIsInDialog(); @@ -49,41 +45,9 @@ searchKeys: props.field.mode === 'local' ? props.field.searchKeys : [], } ); - - function search(query: string, immediate?: boolean) { - if(props.field.mode === 'remote') { - const field = props.field as FormAutocompleteRemoteFieldData; - clearTimeout(timeout); - if(query.length >= field.searchMinChars) { - if(!results.value.length) { - loading.value = true; - } - if(immediate) { - remoteSearch(query); - } else { - timeout = setTimeout(() => remoteSearch(query), props.field.debounceDelay) - } - } else { - clearTimeout(timeout); - loading.value = false; - results.value = []; - } - } else { - results.value = !query.length - ? props.field.localValues - : fullTextSearch(query); - } - } - - async function remoteSearch(query: string) { + const { loading, search: remoteSearch } = useRemoteAutocomplete(({ query, signal, onSuccess, onError }) => { const field = props.field as FormAutocompleteRemoteFieldData; - clearTimeout(loadingTimeout); - loadingTimeout = setTimeout(() => { - loading.value = true; - }, 200); - abortController?.abort(); - abortController = new AbortController(); - results.value = await api.post( + return api.post( route('code16.sharp.api.form.autocomplete.index', { entityKey: form.entityKey, autocompleteFieldKey: props.parentField ? `${props.parentField.key}.${field.key}` : field.key, @@ -99,31 +63,31 @@ Object.entries(props.parentData).filter(([fieldKey]) => field.callbackLinkedFields.includes(fieldKey)) ) : null, - }, { - signal: abortController.signal, - } + }, + { signal } ) - .then(response => { - clearTimeout(loadingTimeout); - loading.value = false; - return response; - }, (e) => { - if(isCancel(e)) { - clearTimeout(loadingTimeout); - } - return Promise.reject(e); - }) - .then(response => response.data.data) - ; - } + .then(onSuccess, onError) + .then(response => response.data.data); + }, { + debounceDelay: (props.field as FormAutocompleteRemoteFieldData).debounceDelay, + minLength: (props.field as FormAutocompleteRemoteFieldData).searchMinChars, + }); - let hasTyped = false; + async function search(query: string, immediate?: boolean) { + if(props.field.mode === 'remote') { + results.value = await remoteSearch(query, immediate); + } else { + results.value = !query.length + ? props.field.localValues + : fullTextSearch(query); + } + } function onSearchInput(query: string) { - if(!query.length && !hasTyped) { + if(!query.length && !searchTerm.value) { return; } - hasTyped = true; + searchTerm.value = query; search(query); } @@ -208,7 +172,7 @@ @update:model-value="onSelect($event as any)" > ; + key: string; + label: string | null; + type: "autocompleteRemote"; + multiple: boolean; + required: boolean; + master: boolean; + debounceDelay: number; + searchMinChars: number; +}; export type BreadcrumbData = { items: Array; }; @@ -199,10 +210,15 @@ export type FigureWidgetData = { link: string | null; }; export type FilterData = + | AutocompleteRemoteFilterData | CheckFilterData | DateRangeFilterData | SelectFilterData; -export type FilterType = "select" | "daterange" | "check"; +export type FilterType = + | "autocompleteRemote" + | "select" + | "daterange" + | "check"; export type FilterValuesData = { current: { [key: string]: any }; default: { [key: string]: any }; diff --git a/resources/js/types/routes.d.ts b/resources/js/types/routes.d.ts index 113422edc..31664ff36 100644 --- a/resources/js/types/routes.d.ts +++ b/resources/js/types/routes.d.ts @@ -307,6 +307,15 @@ declare module 'ziggy-js' { "name": "autocompleteFieldKey" } ], + "code16.sharp.api.filters.autocomplete.index": [ + { + "name": "entityKey", + "binding": "key" + }, + { + "name": "filterKey" + } + ], "code16.sharp.login": [], "code16.sharp.login.post": [], "code16.sharp.login.2fa": [], diff --git a/src/Data/Filters/AutocompleteRemoteFilterData.php b/src/Data/Filters/AutocompleteRemoteFilterData.php index 810634c05..f8df56d27 100644 --- a/src/Data/Filters/AutocompleteRemoteFilterData.php +++ b/src/Data/Filters/AutocompleteRemoteFilterData.php @@ -24,6 +24,8 @@ public function __construct( public bool $multiple, public bool $required, public bool $master, + public int $debounceDelay, + public int $searchMinChars, ) {} public static function from(array $filter): self diff --git a/src/Filters/AutocompleteRemoteFilter.php b/src/Filters/AutocompleteRemoteFilter.php index 9d42d61ce..0ae0b0cc5 100644 --- a/src/Filters/AutocompleteRemoteFilter.php +++ b/src/Filters/AutocompleteRemoteFilter.php @@ -7,39 +7,80 @@ abstract class AutocompleteRemoteFilter extends Filter { private bool $isMaster = false; + private int $debounceDelay = 300; + private int $searchMinChars = 1; + private array $cache = []; - final public function isMaster(): bool + final public function configureMaster(bool $isMaster = true): self { - return $this->isMaster; + $this->isMaster = $isMaster; + + return $this; } - final public function configureMaster(bool $isMaster = true): self + final public function configureDebounceDelay(int $delay): self { - $this->isMaster = $isMaster; + $this->debounceDelay = $delay; + + return $this; + } + + final public function configureSearchMinChars(int $minChars): self + { + $this->searchMinChars = $minChars; return $this; } public function fromQueryParam($value): mixed { - return $value ? $this->valuesFor([$value]) : null; + if ($value) { + $value = collect(explode(',', $value)); + + return $this->cache[$value->sort()->implode(',')] ??= $this->format($this->valuesFor($value->all())); + } + + return []; } public function toQueryParam($value): mixed { - return $value; + return $value + ? collect($value)->pluck('id')->join(',') + : null; } public function toArray(): array { return parent::buildArray([ 'type' => FilterType::AutocompleteRemote->value, - 'master' => $this->isMaster(), + 'master' => $this->isMaster, 'required' => $this instanceof AutocompleteRemoteRequiredFilter, 'multiple' => $this instanceof AutocompleteRemoteMultipleFilter, + 'debounceDelay' => $this->debounceDelay, + 'searchMinChars' => $this->searchMinChars, ]); } + final public function format(array $values) + { + if (! is_array(collect($values)->first())) { + return collect($values) + ->map(function ($label, $id) { + return compact('id', 'label'); + }) + ->values() + ->all(); + } + + return $values; + } + + public function formatRawValue(mixed $value): mixed + { + return $value ? $value[0]['id'] : null; + } + abstract public function values(string $query): array; abstract public function valuesFor(array $ids): array; diff --git a/src/Filters/AutocompleteRemoteMultipleFilter.php b/src/Filters/AutocompleteRemoteMultipleFilter.php index fbee8e9dc..c7050265a 100644 --- a/src/Filters/AutocompleteRemoteMultipleFilter.php +++ b/src/Filters/AutocompleteRemoteMultipleFilter.php @@ -2,17 +2,12 @@ namespace Code16\Sharp\Filters; -use Illuminate\Support\Arr; - abstract class AutocompleteRemoteMultipleFilter extends AutocompleteRemoteFilter { - public function fromQueryParam($value): array - { - return $value ? explode(',', $value) : []; - } - - public function toQueryParam($value): ?string + public function formatRawValue(mixed $value): mixed { - return $value ? implode(',', Arr::wrap($value)) : null; + return $value + ? collect($value)->pluck('id')->all() + : null; } } diff --git a/src/Filters/FilterContainer/Concerns/ProvidesFilterValuesToFront.php b/src/Filters/FilterContainer/Concerns/ProvidesFilterValuesToFront.php index 5c0c4e161..067aeacef 100644 --- a/src/Filters/FilterContainer/Concerns/ProvidesFilterValuesToFront.php +++ b/src/Filters/FilterContainer/Concerns/ProvidesFilterValuesToFront.php @@ -2,6 +2,7 @@ namespace Code16\Sharp\Filters\FilterContainer\Concerns; +use Code16\Sharp\Filters\AutocompleteRemoteFilter; use Code16\Sharp\Filters\Filter; use Code16\Sharp\Filters\SelectMultipleFilter; use Illuminate\Support\Arr; @@ -36,7 +37,7 @@ public function getCurrentFilterValuesForFront(?array $query): array ->mapWithKeys(function (Filter $handler) use ($currentValues, $defaultValues) { $current = $currentValues[$handler->getKey()] ?? null; $default = $defaultValues[$handler->getKey()] ?? null; - if ($handler instanceof SelectMultipleFilter) { + if ($handler instanceof SelectMultipleFilter || $handler instanceof AutocompleteRemoteFilter) { $current = is_array($current) ? Arr::sort($current) : []; $default = is_array($default) ? Arr::sort($default) : []; } diff --git a/src/Http/Controllers/Api/ApiFilterAutocompleteController.php b/src/Http/Controllers/Api/ApiFilterAutocompleteController.php new file mode 100644 index 000000000..5cf1df39c --- /dev/null +++ b/src/Http/Controllers/Api/ApiFilterAutocompleteController.php @@ -0,0 +1,34 @@ +entityManager->entityFor($entityKey); + + if ($entity instanceof SharpDashboardEntity) { + $filter = $entity->getViewOrFail()->filterContainer()->findFilterHandler($filterKey); + } else { + $filter = $entity->getListOrFail()->filterContainer()->findFilterHandler($filterKey); + } + + if (! $filter instanceof AutocompleteRemoteFilter) { + return [ + 'data' => [], + ]; + } + + $query = request()->input('query', ''); + $values = $filter->values($query); + + return [ + 'data' => $filter->format($values), + ]; + } +} diff --git a/src/routes/api.php b/src/routes/api.php index 34d6ee3fd..3010b6f34 100644 --- a/src/routes/api.php +++ b/src/routes/api.php @@ -2,6 +2,7 @@ use Code16\Sharp\Http\Controllers\Api\ApiEntityListController; use Code16\Sharp\Http\Controllers\Api\ApiEntityListFiltersController; +use Code16\Sharp\Http\Controllers\Api\ApiFilterAutocompleteController; use Code16\Sharp\Http\Controllers\Api\ApiFormAutocompleteController; use Code16\Sharp\Http\Controllers\Api\ApiFormEditorUploadFormController; use Code16\Sharp\Http\Controllers\Api\ApiFormUploadController; @@ -103,4 +104,7 @@ Route::post('/form/autocomplete/{entityKey}/{autocompleteFieldKey}', [ApiFormAutocompleteController::class, 'index']) ->name('code16.sharp.api.form.autocomplete.index'); + + Route::post('/filters/autocomplete/{entityKey}/{filterKey}', [ApiFilterAutocompleteController::class, 'index']) + ->name('code16.sharp.api.filters.autocomplete.index'); }); From c18eaf8d3de93ea60a89ac88c1b429dc3caa41d8 Mon Sep 17 00:00:00 2001 From: antoine Date: Mon, 2 Jun 2025 16:55:56 +0200 Subject: [PATCH 06/14] remove multiple autocomplete --- demo/app/Sharp/Posts/PostList.php | 11 ++ demo/app/Sharp/Utils/Filters/AuthorFilter.php | 14 +- .../Utils/Filters/PostAttachmentFilter.php | 37 +++++ .../factories/PostAttachmentFactory.php | 5 +- demo/database/seeders/WithTextFixtures.php | 10 ++ .../filters/AutocompleteRemoteFilter.vue | 137 +++++++++--------- .../filters/AutocompleteRemoteFilterValue.vue | 15 +- .../components/filters/SelectFilter.vue | 8 +- resources/js/types/generated.d.ts | 3 +- resources/lang/en/form.php | 2 +- resources/lang/fr/form.php | 2 +- .../Filters/AutocompleteRemoteFilterData.php | 4 +- src/Filters/AutocompleteRemoteFilter.php | 16 +- .../AutocompleteRemoteMultipleFilter.php | 13 -- .../Concerns/HandlesFiltersInQueryParams.php | 2 +- .../Concerns/HandlesFiltersInSession.php | 3 +- .../Concerns/ProvidesFilterValuesToFront.php | 5 +- .../FilterContainer/FilterContainer.php | 15 +- .../SharpEntityListQueryParamsTest.php | 2 +- 19 files changed, 169 insertions(+), 135 deletions(-) create mode 100644 demo/app/Sharp/Utils/Filters/PostAttachmentFilter.php delete mode 100644 src/Filters/AutocompleteRemoteMultipleFilter.php diff --git a/demo/app/Sharp/Posts/PostList.php b/demo/app/Sharp/Posts/PostList.php index b2f43ad15..cbaee4edf 100644 --- a/demo/app/Sharp/Posts/PostList.php +++ b/demo/app/Sharp/Posts/PostList.php @@ -3,6 +3,7 @@ namespace App\Sharp\Posts; use App\Models\Post; +use App\Models\PostAttachment; use App\Sharp\Entities\PostEntity; use App\Sharp\Posts\Commands\BulkPublishPostsCommand; use App\Sharp\Posts\Commands\ComposeEmailWithPostsWizardCommand; @@ -12,6 +13,7 @@ use App\Sharp\Utils\Filters\AuthorFilter; use App\Sharp\Utils\Filters\CategoryFilter; use App\Sharp\Utils\Filters\PeriodFilter; +use App\Sharp\Utils\Filters\PostAttachmentFilter; use App\Sharp\Utils\Filters\StateFilter; use Code16\Sharp\EntityList\Fields\EntityListField; use Code16\Sharp\EntityList\Fields\EntityListFieldsContainer; @@ -86,6 +88,7 @@ protected function getFilters(): ?array StateFilter::class, AuthorFilter::class, CategoryFilter::class, + PostAttachmentFilter::class, PeriodFilter::class, ]; } @@ -147,6 +150,14 @@ function (Builder $builder, $categories) { }); }, ) + ->when( + $this->queryParams->filterFor(PostAttachmentFilter::class), + function (Builder $builder, int $attachmentId) { + $builder->whereHas('attachments', function (Builder $builder) use ($attachmentId) { + $builder->where('title', PostAttachment::find($attachmentId)->title); + }); + }, + ) // Handle search words ->when( diff --git a/demo/app/Sharp/Utils/Filters/AuthorFilter.php b/demo/app/Sharp/Utils/Filters/AuthorFilter.php index e16d73f46..4032edacf 100644 --- a/demo/app/Sharp/Utils/Filters/AuthorFilter.php +++ b/demo/app/Sharp/Utils/Filters/AuthorFilter.php @@ -3,30 +3,22 @@ namespace App\Sharp\Utils\Filters; use App\Models\User; -use Code16\Sharp\Filters\AutocompleteRemoteFilter; +use Code16\Sharp\Filters\SelectFilter; -class AuthorFilter extends AutocompleteRemoteFilter +class AuthorFilter extends SelectFilter { public function buildFilterConfig(): void { $this->configureLabel('Author'); } - public function values(string $query): array + public function values(): array { return User::whereHas('posts') ->orderBy('name') - ->when($query, function ($queryBuilder) use ($query) { - $queryBuilder->where('name', 'like', "%$query%"); - }) ->get() ->pluck('name', 'id') ->map(fn ($name, $id) => auth()->id() === $id ? "$name (me)" : $name) ->toArray(); } - - public function valuesFor(array $ids): array - { - return User::whereIn('id', $ids)->get()->pluck('name', 'id')->toArray(); - } } diff --git a/demo/app/Sharp/Utils/Filters/PostAttachmentFilter.php b/demo/app/Sharp/Utils/Filters/PostAttachmentFilter.php new file mode 100644 index 000000000..210708e01 --- /dev/null +++ b/demo/app/Sharp/Utils/Filters/PostAttachmentFilter.php @@ -0,0 +1,37 @@ +configureLabel('Attachment'); + } + + public function values(string $query): array + { + return PostAttachment::query() + ->orderBy('title') + ->when($query, function ($builder) use ($query) { + $builder->where('title', 'like', "%$query%"); + }) + ->get() + ->pluck('title', 'id') + ->unique() + ->toArray(); + } + + public function valueLabelFor(string $id): string + { + return PostAttachment::find($id)->title ?? ''; + } + + public function defaultValue(): mixed + { + return PostAttachment::first()?->id; + } +} diff --git a/demo/database/factories/PostAttachmentFactory.php b/demo/database/factories/PostAttachmentFactory.php index 255c680f9..4f5219446 100644 --- a/demo/database/factories/PostAttachmentFactory.php +++ b/demo/database/factories/PostAttachmentFactory.php @@ -2,14 +2,17 @@ namespace Database\Factories; +use Database\Seeders\WithTextFixtures; use Illuminate\Database\Eloquent\Factories\Factory; class PostAttachmentFactory extends Factory { + use WithTextFixtures; + public function definition() { return [ - 'title' => $this->faker->sentence, + 'title' => $this->faker->randomElement(static::$attachmentTitles), 'is_link' => $this->faker->boolean, 'link_url' => $this->faker->url, ]; diff --git a/demo/database/seeders/WithTextFixtures.php b/demo/database/seeders/WithTextFixtures.php index ea87d0b73..46ee38163 100644 --- a/demo/database/seeders/WithTextFixtures.php +++ b/demo/database/seeders/WithTextFixtures.php @@ -149,4 +149,14 @@ trait WithTextFixtures 'The chair sat in the corner where it had been for over 25 years. The only difference was there was someone actually sitting in it. How long had it been since someone had done that? Ten years or more he imagined. Yet there was no denying the presence in the chair now.', "Things aren't going well at all with mom today. She is just a limp noodle and wants to sleep all the time. I sure hope that things get better soon.", ]; + protected static array $attachmentTitles = [ + 'Newspaper article', + 'Charts', + 'PDF specifications', + 'Book preview', + 'Code example', + 'Photo of my lamborghini', + 'Photo of my cat', + 'Sales growth chart', + ]; } diff --git a/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue b/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue index 7a04f23e2..2400cb75d 100644 --- a/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue +++ b/resources/js/filters/components/filters/AutocompleteRemoteFilter.vue @@ -22,13 +22,14 @@ import { computed, ref } from "vue"; import AutocompleteRemoteFilterValue from "@/filters/components/filters/AutocompleteRemoteFilterValue.vue"; import { route } from "@/utils/url"; + import { Button } from "@/components/ui/button"; const props = defineProps>(); const emit = defineEmits>(); const open = ref(false); const searchTerm = ref(''); - const { results, loading, search } = useRemoteAutocomplete( + const { results, loading, search } = useRemoteAutocomplete( ({ query, signal, onSuccess, onError }) => api.post( route('code16.sharp.api.filters.autocomplete.index', { @@ -47,27 +48,25 @@ } ) - const valuated = computed(() => !!props.value?.length); - - function isSelected(selectValue: AutocompleteRemoteFilterData['value'][0]) { + function isSelected(selectValue: AutocompleteRemoteFilterData['value']) { return Array.isArray(props.value) ? !!props.value.find(v => selectValue.id == v.id) - : props.value == selectValue.id; + : props.value?.id == selectValue.id; } - function onSelect(selectValue: AutocompleteRemoteFilterData['value'][0]) { - if(props.filter.multiple) { - const value = Object.values({ - ...Object.fromEntries( - Object.entries(props.value || []).map(([i,v]) => [v.id, v]) - ), - [selectValue.id]: selectValue, - }); - emit('input', value); - } else { + function onSelect(selectValue: AutocompleteRemoteFilterData['value']) { + // if(props.filter.multiple) { + // const value = Object.values({ + // ...Object.fromEntries( + // Object.entries(props.value || []).map(([i,v]) => [v.id, v]) + // ), + // [selectValue.id]: selectValue, + // }); + // emit('input', value); + // } else { open.value = false; - emit('input', props.value?.[0]?.id == selectValue.id ? null : [selectValue]); - } + emit('input', props.value?.id == selectValue.id ? null : selectValue); + // } } function onSearchInput(query: string) { @@ -88,31 +87,34 @@