Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[5.x] Add filtering to form submissions listing #8906

Merged
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 @@ -65,6 +65,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
Loading