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

Date Range filter in Table Component #162

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions app/app/Http/UserTableView.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use App\Tables\SpatieUsers;
use App\Tables\SpladeUsers;
use Illuminate\Support\Collection;
use ProtoneMedia\Splade\Facades\Splade;
use ProtoneMedia\Splade\SpladeTable;
use Spatie\QueryBuilder\AllowedFilter;
use Spatie\QueryBuilder\QueryBuilder;
Expand Down Expand Up @@ -55,6 +56,8 @@ public function spatieWrapped($paginateMethod)

public function splade($paginateMethod)
{
Splade::setFrontendTimezone(fn () => 'Europe/Amsterdam');

return view('table.users', [
'users' => SpladeTable::for(User::class)
->withGlobalSearch(columns: ['name', 'email'])
Expand All @@ -63,6 +66,9 @@ public function splade($paginateMethod)
->column(key: 'email', searchable: true, sortable: true)
->column(key: 'language_code', label: 'Language')
->column(label: 'Actions')
->column(key: 'created_at', label: 'Created UTC', numeric: true)
->column(key: 'created_at_amsterdam', label: 'Created Europe/Amsterdam', numeric: true)
->dateRangeFilter(key: 'created_at')
->selectFilter(key: 'language_code', options: [
'en' => 'English',
'nl' => 'Dutch',
Expand Down
5 changes: 5 additions & 0 deletions app/app/Models/User.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ class User extends Authenticatable implements HasMedia
'is_admin' => 'boolean',
];

public function getCreatedAtAmsterdamAttribute()
{
return $this->created_at->clone()->timezone('Europe/Amsterdam')->format('Y-m-d H:i:s');
}

public function tags()
{
return $this->belongsToMany(Tag::class);
Expand Down
1 change: 1 addition & 0 deletions app/database/factories/UserFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public function definition()
'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password
'remember_token' => Str::random(10),
'language_code' => fake()->randomElement(['en', 'nl']),
'created_at' => fake()->dateTimeBetween('-1 years'),
];
}

Expand Down
6 changes: 4 additions & 2 deletions app/database/seeders/DatabaseSeeder.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
use Database\Factories\TagFactory;
use Database\Factories\UserFactory;
use Illuminate\Database\Seeder;
use Illuminate\Support\Carbon;

class DatabaseSeeder extends Seeder
{
Expand All @@ -25,8 +26,9 @@ public function run()

/** @var User $user */
$firstUser = UserFactory::new()->create([
'name' => 'Test User',
'email' => '[email protected]',
'name' => 'Test User',
'email' => '[email protected]',
'created_at' => Carbon::createFromFormat('Y-m-d H:i:s', '2022-12-01 00:00:00', 'Europe/Amsterdam')->timezone('UTC'),
]);

static::giveUserMedia($firstUser);
Expand Down
19 changes: 13 additions & 6 deletions lib/Components/Form.vue
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export default {
},
},

emits: ["success", "error"],
emits: ["success", "error", "change"],

data() {
return {
Expand Down Expand Up @@ -183,11 +183,18 @@ export default {
this.missingAttributes = [];

// Create watchers
if(this.submitOnChange === true) {
this.$watch("values", () => {
this.$nextTick(() => this.request());
}, { deep: true });
}else if(isArray(this.submitOnChange)) {

this.$watch("values", () => {
this.$nextTick(() => {
this.$emit("change", this.values);

if(this.submitOnChange === true){
this.request();
}
});
}, { deep: true });

if(isArray(this.submitOnChange)) {
this.submitOnChange.forEach((key) => {
this.$watch(`values.${key}`, () => {
this.$nextTick(() => this.request());
Expand Down
24 changes: 22 additions & 2 deletions lib/Components/Table.vue
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,15 @@ export default {
type: Number,
required: false,
default: 0
}
},

filterValues: {
type: Object,
required: false,
default() {
return {};
}
},
},

data() {
Expand All @@ -65,7 +73,8 @@ export default {
forcedVisibleSearchInputs: [],
debounceUpdateQuery: null,
isLoading: false,
processingAction: false
processingAction: false,
preventReload: false,
};
},

Expand Down Expand Up @@ -153,6 +162,14 @@ export default {
},

methods: {
updateFilterValues(values) {
forOwn(values, (value, key) => {
if(this.filterValues[key] != value) {
this.updateQuery(`filter[${key}]`, value);
}
});
},

visitLink(url, type, $event) {
if($event?.target?.tagName === "A" || $event?.target?.tagName === "BUTTON") {
return;
Expand Down Expand Up @@ -369,6 +386,8 @@ export default {
queryString += "&";
}

value=encodeURIComponent(value);

queryString += `${key}=${value}`;
});

Expand Down Expand Up @@ -483,6 +502,7 @@ export default {
performBulkAction: this.performBulkAction,
processingAction: this.processingAction,
isLoading: this.isLoading,
updateFilterValues: this.updateFilterValues,
});
},
};
Expand Down
1 change: 1 addition & 0 deletions resources/views/form/input.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
'min-w-0 flex-1 rounded-none' => $append || $prepend,
'rounded-l-md' => $append && !$prepend,
'rounded-r-md' => !$append && $prepend,
'text-sm' => $attributes->classHas('text-sm')
])->merge([
'name' => $name,
'type' => $type,
Expand Down
1 change: 1 addition & 0 deletions resources/views/form/select.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
<div v-bind:class="{ 'opacity-50': select.loading }">
<select {{ $attributes->except(['v-if', 'v-show', 'class'])->class([
'block w-full rounded-md border-gray-300 shadow-sm focus:border-indigo-300 focus:ring focus:ring-indigo-200 focus:ring-opacity-50 disabled:opacity-50',
'text-sm' => $attributes->classHas('text-sm')
])->merge([
'multiple' => $multiple,
'name' => $name,
Expand Down
2 changes: 1 addition & 1 deletion resources/views/table/body.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class="rounded border-gray-300 text-indigo-600 shadow-sm focus:border-indigo-300
@foreach($table->columns() as $column)
<td
v-show="table.columnIsVisible(@js($column->key))"
class="whitespace-nowrap text-sm @if($loop->first && $hasBulkActions) pr-6 @else px-6 @endif py-4 @if($column->highlight) text-gray-900 font-medium @else text-gray-500 @endif"
class="whitespace-nowrap text-sm @if($loop->first && $hasBulkActions) pr-6 @else px-6 @endif py-4 @if($column->highlight) text-gray-900 font-medium @else text-gray-500 @endif @if($column->numeric) tabular-nums @endif"
>
@isset(${'spladeTableCell' . $column->keyHash()})
{{ ${'spladeTableCell' . $column->keyHash()}($item, $itemKey) }}
Expand Down
36 changes: 15 additions & 21 deletions resources/views/table/filters.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,28 +15,22 @@
aria-orientation="horizontal"
aria-labelledby="filter-menu"
>
@foreach($table->filters() as $filter)
<div>
<h3 class="text-xs uppercase tracking-wide bg-gray-100 p-3">
{{ $filter->label }}
</h3>
<x-splade-form :default="$table->filterValues()" v-on:change="table.updateFilterValues">
@foreach($table->filters() as $filter)
<div>
<h3 class="text-xs uppercase tracking-wide bg-gray-100 p-3">
{{ $filter->label }}
</h3>

<div class="p-2">
@if($filter->type === 'select')
<select
name="filter-{{ $filter->key }}"
class="block focus:ring-indigo-500 focus:border-indigo-500 w-full shadow-sm text-sm border-gray-300 rounded-md"
@change="table.updateQuery('filter[{{ $filter->key }}]', $event.target.value)"
>
@foreach($filter->options() as $optionKey => $option)
<option @selected($filter->hasValue() && $filter->value == $optionKey) value="{{ $optionKey }}">
{{ $option }}
</option>
@endforeach
</select>
@endif
<div class="p-2">
@if($filter->type === \ProtoneMedia\Splade\Table\Filter::TYPE_DATE_RANGE)
<x-splade-input class="text-sm" date range :name="$filter->key" />
@elseif($filter->type === \ProtoneMedia\Splade\Table\Filter::TYPE_SELECT)
<x-splade-select class="text-sm" :name="$filter->key" :options="$filter->options()" />
@endif
</div>
</div>
</div>
@endforeach
@endforeach
</x-splade-form>
</div>
</x-splade-component>
1 change: 1 addition & 0 deletions resources/views/table/table.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
:items-on-this-page="@js($table->totalOnThisPage())"
:items-on-all-pages="@js($table->totalOnAllPages())"
:base-url="@js(request()->url())"
:filter-values="@js($table->filterValues())"
>
<template #default="{!! $scope !!}">
<div {{ $attributes->only('class') }} :class="{ 'opacity-50': table.isLoading }">
Expand Down
8 changes: 8 additions & 0 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Routing\Middleware\ValidateSignature;
use Illuminate\Support\Arr;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Blade;
use Illuminate\Support\Facades\Redirect;
Expand Down Expand Up @@ -323,6 +324,13 @@ private function registerViewMacros()
return $this->slots[0] ?? [];
});

ComponentAttributeBag::macro('classHas', function ($value) {
/** @var ComponentAttributeBag $this */
$classes = explode(' ', Arr::toCssClasses($this->get('class')));

return in_array($value, $classes);
});

ComponentAttributeBag::macro('mergeVueBinding', function ($attribute, $value, bool $omitBlankValue = true, bool $escape = true) {
/** @var ComponentAttributeBag $this */
if ($omitBlankValue && blank($value)) {
Expand Down
14 changes: 14 additions & 0 deletions src/SpladeCore.php
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ class SpladeCore

private $customToastFactory;

private $frontendTimezone;

/**
* Creates an instance.
*
Expand Down Expand Up @@ -439,4 +441,16 @@ public function redirectAway(string $targetUrl): Response
static::HEADER_REDIRECT_AWAY => $targetUrl,
]);
}

public function setFrontendTimezone($value): self
{
$this->frontendTimezone = $value;

return $this;
}

public function getFrontendTimezone(): string
{
return $this->frontendTimezone ? value($this->frontendTimezone) : config('app.timezone');
}
}
43 changes: 40 additions & 3 deletions src/SpladeQueryBuilder.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,18 @@

namespace ProtoneMedia\Splade;

use Exception;
use Illuminate\Database\Eloquent\Builder as EloquentBuilder;
use Illuminate\Database\MySqlConnection;
use Illuminate\Database\PostgresConnection;
use Illuminate\Database\Query\Builder as BaseQueryBuilder;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Str;
use Kirschbaum\PowerJoins\PowerJoins;
use ProtoneMedia\Splade\Facades\Splade;
use ProtoneMedia\Splade\Table\Column;
use ProtoneMedia\Splade\Table\Filter;
use ProtoneMedia\Splade\Table\PowerJoinsException;
Expand Down Expand Up @@ -202,6 +205,35 @@ private function applyConstraint(array $columns, string $terms)
});
}

private function applyDateRangeConstraint(string $column, string $terms)
{
$builder = $this->builder;

$splitted = Collection::make(explode(' ', $terms));

$timezone = Splade::getFrontendTimezone();

$appTimezone = config('app.timezone');

$dates = [
Carbon::parse($splitted->first(), $timezone)->startOfDay()->timezone($appTimezone),
Carbon::parse($splitted->last(), $timezone)->endOfDay()->timezone($appTimezone),
];

if (!Str::contains($column, '.')) {
// Not a relationship, but a column on the table.
return $builder->whereBetween($builder->qualifyColumn($column), $dates);
}

// Split the column into the relationship name and the key on the relationship.
$relation = Str::beforeLast($column, '.');
$key = Str::afterLast($column, '.');

$builder->whereHas($relation, function (EloquentBuilder $relation) use ($key, $dates) {
return $relation->whereBetween($relation->qualifyColumn($key), $dates);
});
}

/**
* Adds an "order by" clause to the query. If the query needs
* to be sorted by a (nested) relationship, it will
Expand Down Expand Up @@ -247,9 +279,14 @@ private function applyFilters()
$this->ignoreCase(false);
$this->parseTerms(false);

$this->filters()->filter->hasValue()->each(
fn (Filter $filter) => $this->applyConstraint([$filter->key => SearchInput::EXACT], $filter->value)
);
$this->filters()->filter->hasValue()->each(function (Filter $filter) {
match ($filter->type) {
Filter::TYPE_DATE_RANGE => $this->applyDateRangeConstraint($filter->key, $filter->value),
Filter::TYPE_SELECT => $this->applyConstraint([$filter->key => SearchInput::EXACT], $filter->value),

default => throw new Exception('Invalid filter type'),
};
});

$this->ignoreCase($ignoreCaseSetting);
$this->parseTerms($parseTermsSetting);
Expand Down
3 changes: 3 additions & 0 deletions src/Table/Column.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class Column implements Arrayable
* @param bool|Closure $exportAs
* @param Closure|string $exportFormat
* @param Closure|array $exportStyling
* @param bool $numeric
*/
public function __construct(
public string $key,
Expand All @@ -34,6 +35,7 @@ public function __construct(
public bool|Closure $exportAs,
public Closure|string|null $exportFormat = null,
public Closure|array|null $exportStyling = null,
public $numeric = false,
) {
}

Expand All @@ -55,6 +57,7 @@ public function clone(): static
$this->exportAs,
$this->exportFormat,
$this->exportStyling,
$this->numeric,
);
}

Expand Down
4 changes: 4 additions & 0 deletions src/Table/Filter.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@

class Filter implements Arrayable
{
const TYPE_DATE_RANGE = 'date_range';

const TYPE_SELECT = 'select';

/**
* This class represents a filter in a Splade Table.
*
Expand Down
Loading