diff --git a/app/Form/Actions/UpdateParticipantSearchIndexAction.php b/app/Form/Actions/UpdateParticipantSearchIndexAction.php index edec671c..ceba60e3 100644 --- a/app/Form/Actions/UpdateParticipantSearchIndexAction.php +++ b/app/Form/Actions/UpdateParticipantSearchIndexAction.php @@ -20,7 +20,7 @@ class UpdateParticipantSearchIndexAction [ 'filterableAttributes' => [...$form->getFields()->filterables()->getKeys(), 'parent-id'], 'searchableAttributes' => $form->getFields()->searchables()->getKeys(), - 'sortableAttributes' => [], + 'sortableAttributes' => $form->getFields()->sortables()->getKeys(), 'displayedAttributes' => [...$form->getFields()->filterables()->getKeys(), ...$form->getFields()->searchables()->getKeys(), 'id'], 'pagination' => [ 'maxTotalHits' => 1000000, diff --git a/app/Form/Data/FieldCollection.php b/app/Form/Data/FieldCollection.php index 57df28bb..f1d0348b 100644 --- a/app/Form/Data/FieldCollection.php +++ b/app/Form/Data/FieldCollection.php @@ -124,6 +124,11 @@ class FieldCollection extends Collection return $this; } + public function sortables(): self + { + return $this; + } + public function filterables(): self { return $this->filter(fn ($field) => $field instanceof Filterable); diff --git a/app/Form/Models/Form.php b/app/Form/Models/Form.php index b94f8208..b7c6cdab 100644 --- a/app/Form/Models/Form.php +++ b/app/Form/Models/Form.php @@ -8,6 +8,7 @@ use App\Form\Data\FieldCollection; use App\Form\Data\FormConfigData; use App\Lib\Editor\Condition; use App\Lib\Editor\EditorData; +use App\Lib\Sorting; use Cviebrock\EloquentSluggable\Sluggable; use Database\Factories\Form\Models\FormFactory; use Illuminate\Database\Eloquent\Factories\HasFactory; @@ -182,4 +183,9 @@ class Form extends Model implements HasMedia { return config('scout.prefix') . 'forms_' . $this->id . '_participants'; } + + public function defaultSorting(): Sorting + { + return Sorting::by(data_get($this->meta, 'active_columns.0')); + } } diff --git a/app/Form/Scopes/ParticipantFilterScope.php b/app/Form/Scopes/ParticipantFilterScope.php index e34baa41..1f8fc69e 100644 --- a/app/Form/Scopes/ParticipantFilterScope.php +++ b/app/Form/Scopes/ParticipantFilterScope.php @@ -4,8 +4,8 @@ namespace App\Form\Scopes; use App\Form\Models\Form; use App\Form\Models\Participant; -use App\Lib\Filter; use App\Lib\ScoutFilter; +use App\Lib\Sorting; use Illuminate\Support\Arr; use Laravel\Scout\Builder; use Spatie\LaravelData\Attributes\MapInputName; @@ -31,7 +31,8 @@ class ParticipantFilterScope extends ScoutFilter public array $data = [], public string $search = '', public array $options = [], - public ?int $parent = null + public ?int $parent = null, + public ?Sorting $sort = null ) { } @@ -59,6 +60,8 @@ class ParticipantFilterScope extends ScoutFilter $options['filter'] = $filter->map(fn ($expression) => "($expression)")->implode(' AND '); + $options['sort'] = $this->sort->toMeilisearch(); + return $engine->search($query, [...$this->options, ...$options]); })->within($this->form->participantsSearchableAs()); } @@ -67,6 +70,10 @@ class ParticipantFilterScope extends ScoutFilter { $this->form = $form; + if (is_null($this->sort)) { + $this->sort = $this->form->defaultSorting(); + } + foreach ($form->getFields() as $field) { if (!Arr::has($this->data, $field->key)) { data_set($this->data, $field->key, static::$nan); diff --git a/app/Lib/Sorting.php b/app/Lib/Sorting.php new file mode 100644 index 00000000..1e5204f3 --- /dev/null +++ b/app/Lib/Sorting.php @@ -0,0 +1,25 @@ +withoutMagicalCreation()->from(['by' => $by]); + } + + public function __construct(public string $by, public bool $direction = false) + { + } + + /** + * @return array + */ + public function toMeilisearch(): array + { + return [$this->by . ':' . ($this->direction ? 'desc' : 'asc')]; + } +} diff --git a/tests/EndToEnd/Form/ParticipantIndexActionTest.php b/tests/EndToEnd/Form/ParticipantIndexActionTest.php index 02fc2c1a..cf0d363e 100644 --- a/tests/EndToEnd/Form/ParticipantIndexActionTest.php +++ b/tests/EndToEnd/Form/ParticipantIndexActionTest.php @@ -239,3 +239,40 @@ it('testItShowsPreventionState', function () { ->assertJsonPath('data.0.prevention_items.0.value', false) ->assertJsonPath('data.0.prevention_items.0.tooltip', 'erweitertes Führungszeugnis nicht vorhanden'); }); + +it('test it orders participants by value', function (array $values, array $sorting, array $expected) { + list($key, $direction) = $sorting; + $this->login()->loginNami()->withoutExceptionHandling(); + $form = Form::factory() + ->fields([ + $this->textField('vorname')->name('Vorname'), + $this->checkboxesField('select')->options(['Wölflinge', 'Pfadfinder']), + ]); + foreach ($values as $value) { + $form = $form->has(Participant::factory()->data(['vorname' => 'Max', 'select' => 'Pfadfinder', $key => $value])); + } + $form = $form->create(); + + sleep(2); + $response = $this->callFilter('form.participant.index', ['sort' => ['by' => $key, 'direction' => $direction]], ['form' => $form]); + + foreach ($expected as $index => $value) { + $response->assertJsonPath("data.{$index}.{$key}", $value); + } +})->with([ + [ + ['Anna', 'Sarah', 'Ben'], + ['vorname', false], + ['Anna', 'Ben', 'Sarah'], + ], + [ + ['Anna', 'Sarah', 'Ben'], + ['vorname', true], + ['Sarah', 'Ben', 'Anna'], + ], + [ + ['Wölflinge', 'Pfadfinder'], + ['select', false], + ['Pfadfinder', 'Wölflinge'], + ] +]);