Skip to content

Commit

Permalink
[5.x] Add filtering to form submissions listing (#8906)
Browse files Browse the repository at this point in the history
Co-authored-by: Duncan McClean <duncan@duncanmcclean.com>
  • Loading branch information
ryanmitchell and duncanmcclean authored Apr 8, 2024
1 parent db06d90 commit fbf7caf
Show file tree
Hide file tree
Showing 8 changed files with 116 additions and 48 deletions.
42 changes: 37 additions & 5 deletions resources/js/components/forms/SubmissionListing.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
<loading-graphic />
</div>

<slot name="no-results" v-if="!loading && !searchQuery && items.length === 0" />

<data-list
v-else-if="!initializing"
:columns="columns"
Expand All @@ -18,11 +16,45 @@
>
<div slot-scope="{ hasSelections }">
<div class="card overflow-hidden p-0 relative">
<div class="flex flex-wrap items-center justify-between p-2 text-sm border-b">
<data-list-search class="h-8 min-w-[240px] w-full" ref="search" v-model="searchQuery" :placeholder="searchPlaceholder" />
<data-list-column-picker class="rtl:mr-2 ltr:ml-2" :preferences-key="preferencesKey('columns')" />
<div class="flex flex-wrap items-center justify-between px-2 pb-2 text-sm border-b">
<data-list-filter-presets
ref="presets"
:active-preset="activePreset"
:active-preset-payload="activePresetPayload"
:active-filters="activeFilters"
:has-active-filters="hasActiveFilters"
:preferences-prefix="preferencesPrefix"
:search-query="searchQuery"
@selected="selectPreset"
@reset="filtersReset"
/>

<data-list-search class="h-8 mt-2 min-w-[240px] w-full" ref="search" v-model="searchQuery" :placeholder="searchPlaceholder" />

<div class="flex space-x-2 mt-2">
<button class="btn btn-sm rtl:mr-2 ltr:ml-2" v-text="__('Reset')" v-show="isDirty" @click="$refs.presets.refreshPreset()" />
<button class="btn btn-sm rtl:mr-2 ltr:ml-2" v-text="__('Save')" v-show="isDirty" @click="$refs.presets.savePreset()" />
<data-list-column-picker :preferences-key="preferencesKey('columns')" />
</div>
</div>

<data-list-filters
ref="filters"
:filters="filters"
:active-preset="activePreset"
:active-preset-payload="activePresetPayload"
:active-filters="activeFilters"
:active-filter-badges="activeFilterBadges"
:active-count="activeFilterCount"
:search-query="searchQuery"
:is-searching="true"
:saves-presets="true"
:preferences-prefix="preferencesPrefix"
@changed="filterChanged"
@saved="$refs.presets.setPreset($event)"
@deleted="$refs.presets.refreshPresets()"
/>

<div v-show="items.length === 0" class="p-6 text-center text-gray-500" v-text="__('No results')" />

<data-list-bulk-actions
Expand Down
1 change: 1 addition & 0 deletions resources/views/forms/show.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
initial-sort-column="datestamp"
initial-sort-direction="desc"
:initial-columns="{{ $columns->toJson() }}"
:filters="{{ $filters->toJson() }}"
v-cloak
>
<div slot="no-results" class="text-center border-2 border-dashed rounded-lg">
Expand Down
2 changes: 1 addition & 1 deletion src/Facades/FormSubmission.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Facade;
use Statamic\Contracts\Forms\Submission as SubmissionContract;
use Statamic\Contracts\Forms\SubmissionQueryBuilder;
use Statamic\Contracts\Forms\SubmissionRepository;
use Statamic\Stache\Query\SubmissionQueryBuilder;

/**
* @method static Collection all()
Expand Down
6 changes: 6 additions & 0 deletions src/Forms/Form.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
use Statamic\Contracts\Data\Augmented;
use Statamic\Contracts\Forms\Form as FormContract;
use Statamic\Contracts\Forms\Submission;
use Statamic\Contracts\Forms\SubmissionQueryBuilder;
use Statamic\Data\HasAugmentedInstance;
use Statamic\Events\FormBlueprintFound;
use Statamic\Events\FormCreated;
Expand Down Expand Up @@ -296,6 +297,11 @@ public function submissions()
return FormSubmission::whereForm($this->handle());
}

public function querySubmissions(): SubmissionQueryBuilder
{
return FormSubmission::query()->where('form', $this->handle());
}

/**
* Get a submission.
*
Expand Down
76 changes: 36 additions & 40 deletions src/Http/Controllers/CP/Forms/FormSubmissionsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,68 +2,64 @@

namespace Statamic\Http\Controllers\CP\Forms;

use Statamic\Extensions\Pagination\LengthAwarePaginator;
use Statamic\Facades\Config;
use Statamic\Fields\Field;
use Statamic\Http\Controllers\CP\CpController;
use Statamic\Http\Requests\FilteredRequest;
use Statamic\Http\Resources\CP\Submissions\Submissions;
use Statamic\Support\Str;
use Statamic\Query\Scopes\Filters\Concerns\QueriesFilters;

class FormSubmissionsController extends CpController
{
public function index($form)
use QueriesFilters;

public function index(FilteredRequest $request, $form)
{
$this->authorize('view', $form);

if (! $form->blueprint()) {
return ['data' => [], 'meta' => ['columns' => []]];
}

$submissions = $form->submissions()->values();
$query = $this->indexQuery($form);

// Search submissions.
if ($search = $this->request->search) {
$submissions = $this->searchSubmissions($submissions);
}
$activeFilterBadges = $this->queryFilters($query, $request->filters, [
'form' => $form->handle(),
]);

// Sort submissions.
$sort = $this->request->sort ?? 'datestamp';
$order = $this->request->order ?? ($sort === 'datestamp' ? 'desc' : 'asc');
$submissions = $this->sortSubmissions($submissions, $sort, $order);
$sortField = request('sort', 'date');
$sortDirection = request('order', $sortField === 'date' ? 'desc' : 'asc');

// Paginate submissions.
$totalSubmissionCount = $submissions->count();
$perPage = request('perPage') ?? Config::get('statamic.cp.pagination_size');
$currentPage = (int) $this->request->page ?: 1;
$offset = ($currentPage - 1) * $perPage;
$submissions = $submissions->slice($offset, $perPage);
$paginator = new LengthAwarePaginator($submissions, $totalSubmissionCount, $perPage, $currentPage);
if ($sortField) {
$query->orderBy($sortField, $sortDirection);
}

$submissions = $query->paginate(request('perPage'));

return (new Submissions($paginator))
return (new Submissions($submissions))
->blueprint($form->blueprint())
->columnPreferenceKey("forms.{$form->handle()}.columns");
->columnPreferenceKey("forms.{$form->handle()}.columns")
->additional(['meta' => [
'activeFilterBadges' => $activeFilterBadges,
]]);
}

private function searchSubmissions($submissions)
protected function indexQuery($form)
{
return $submissions->filter(function ($submission) {
return collect($submission->data())
->filter(function ($value) {
return $value && is_string($value);
})
->filter(function ($value) {
return Str::contains(strtolower($value), strtolower($this->request->search));
$query = $form->querySubmissions();

if ($search = request('search')) {
$query->where('date', 'like', '%'.$search.'%');

$form->blueprint()->fields()->all()
->filter(function (Field $field): bool {
return in_array($field->type(), ['text', 'textarea', 'integer']);
})
->isNotEmpty();
})->values();
}
->each(function (Field $field) use ($query, $search): void {
$query->orWhere($field->handle(), 'like', '%'.$search.'%');
});
}

private function sortSubmissions($submissions, $sortBy, $sortOrder)
{
return $submissions->sortBy(function ($submission) use ($sortBy) {
return $sortBy === 'datestamp'
? $submission->date()->timestamp
: $submission->get($sortBy);
}, null, $sortOrder === 'desc')->values();
return $query;
}

public function destroy($form, $id)
Expand Down
11 changes: 10 additions & 1 deletion src/Http/Controllers/CP/Forms/FormsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Statamic\Facades\Action;
use Statamic\Facades\Blueprint;
use Statamic\Facades\Form;
use Statamic\Facades\Scope;
use Statamic\Facades\User;
use Statamic\Http\Controllers\CP\CpController;
use Statamic\Rules\Handle;
Expand Down Expand Up @@ -72,7 +73,15 @@ public function show($form)
->rejectUnlisted()
->values();

return view('statamic::forms.show', compact('form', 'columns'));
$viewData = [
'form' => $form,
'columns' => $columns,
'filters' => Scope::filters('form-submissions', [
'form' => $form->handle(),
]),
];

return view('statamic::forms.show', $viewData);
}

/**
Expand Down
14 changes: 13 additions & 1 deletion src/Query/Scopes/Filters/Fields.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Statamic\Facades\Blueprint;
use Statamic\Facades\Collection;
use Statamic\Facades\Form;
use Statamic\Facades\Taxonomy;
use Statamic\Facades\User;
use Statamic\Query\Scopes\Filter;
Expand Down Expand Up @@ -63,7 +64,7 @@ public function badge($values)

public function visibleTo($key)
{
return in_array($key, ['entries', 'entries-fieldtype', 'terms', 'users', 'usergroup-users']);
return in_array($key, ['entries', 'entries-fieldtype', 'form-submissions', 'terms', 'users', 'usergroup-users']);
}

protected function getFields()
Expand Down Expand Up @@ -91,6 +92,17 @@ protected function getBlueprints()
});
}

if ($forms = Arr::getFirst($this->context, ['form', 'forms'])) {
return collect(Arr::wrap($forms))->map(function ($form) {
return Form::find($form)
->blueprint()
->ensureField('date', [
'type' => 'date',
'filterable' => true,
]);
});
}

if (isset($this->context['blueprints'])) {
return collect($this->context['blueprints'])->map(function ($handle) {
return $handle === 'user'
Expand Down
12 changes: 12 additions & 0 deletions src/Stache/Query/SubmissionQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Illuminate\Support\Collection;
use Statamic\Contracts\Forms\SubmissionQueryBuilder as QueryBuilderContract;
use Statamic\Facades;
use Statamic\Query\OrderBy;

class SubmissionQueryBuilder extends Builder implements QueryBuilderContract
{
Expand Down Expand Up @@ -32,6 +33,17 @@ public function whereIn($column, $values, $boolean = 'and')
return parent::whereIn($column, $values, $boolean);
}

public function orderBy($column, $direction = 'asc')
{
if ($column === 'datestamp') {
$column = 'date';
}

$this->orderBys[] = new OrderBy($column, $direction);

return $this;
}

protected function collect($items = [])
{
return Collection::make($items);
Expand Down

0 comments on commit fbf7caf

Please sign in to comment.