diff --git a/resources/views/components/actions.blade.php b/resources/views/components/actions.blade.php index 52078b6..2ce2259 100644 --- a/resources/views/components/actions.blade.php +++ b/resources/views/components/actions.blade.php @@ -1,6 +1,7 @@ -
-
+
- - + + + + + - -
-
\ No newline at end of file +@endif \ No newline at end of file diff --git a/resources/views/filters/null.blade.php b/resources/views/filters/null.blade.php new file mode 100644 index 0000000..61c0312 --- /dev/null +++ b/resources/views/filters/null.blade.php @@ -0,0 +1,18 @@ +
+ @if($filter->options()) +
+ + +
+ @endif + @if(! $filter->options()) +
No options set.
+ @endif +
\ No newline at end of file diff --git a/resources/views/group-table.blade.php b/resources/views/group-table.blade.php new file mode 100644 index 0000000..5959b7c --- /dev/null +++ b/resources/views/group-table.blade.php @@ -0,0 +1,220 @@ +
+ + @includeIf($this->headerView()) + + @if($this->availableColumns()) + @include('query-builder::report-editor') + @endif + + @if($this->columns()) + +
+ @if ($this->showQueryBuilder()) +
+ @include('query-builder::editor') + + @if(! $this->rows->count()) + + @endif +
+ @endif + + @if($this->isToolbarVisible()) +
+ +
+ @if($this->isSearchVisible()) + @include('query-builder::components.search') + @endif +
+ +
+ + @if($this->areActionsVisible()) + @include('query-builder::components.actions') + @endif + + @if($this->isColumnSelectorVisible()) + @include('query-builder::components.columns-selector') + @endif + + @if($this->isRowSelectorVisible()) + @include('query-builder::components.rows-selector') + @endif +
+
+ @endif + + @if($this->rows->count()) + +
+ + + + + @if($selectable) + + @endif + + @foreach ($this->columns() as $column) + @if(in_array($column->key, $displayColumns)) + + @endif + @endforeach + + + useLoadingIndicator()) wire:loading.delay.longest.class="{{ $this->loadingClass }}" + wire:target.except="exportReportBuilder, saveReportBuilder, loadReportBuilder" @endif> + @if($selectable && $selectPage && $this->rows->count() < $this->rows->total()) + + + + @endif + @foreach ($this->rows as $row) + + @if($this->rowPreview($row)) + {!! $this->injectRow($row) !!} + @endif + + isClickable()) + {!! $this->renderRowClick($row->id) !!} + @endif + wire:key="row-{{ $row->id }}" + @class([ + 'bg-white border-b', + 'hover:bg-gray-50 cursor-pointer' => $this->isClickable(), + ])> + + @if($selectable) + + @endif + + @foreach ($this->columns() as $column) + @if(in_array($column->key, $displayColumns)) + + @endif + @endforeach + + @endforeach + +
+
+ + +
+
isSortable()) wire:click="sort('{{ $column->key }}')" @endif> + @if ($column->showHeader) +
justify, + 'cursor-pointer' => $column->isSortable(), + ])> + {{ $column->label }} + + @if ($sortBy === $column->key) + @if ($sortDirection === 'desc') + + + + @else + + + + @endif + @endif + + @if($this->isSearchableIconVisible() && $column->isSearchable()) + + @endif + +
+ @endif +
+ @unless($selectAll) +
+ You have selected {{ count($selectedRows) }} {{ Str::of('row')->plural(count($selectedRows)) }}. Do you want to select all {{ $this->rows->total() }}? + +
+ @else + You have selected all {{ $this->rows->total() }} {{ Str::of('row')->plural(count($selectedRows)) }}. + @endif +
+
+ + +
+
+
+ + +
+
+
+ + @if($this->isPaginated() && $this->rows->hasPages()) +
+ @if($this->scroll() === true) +
{{ $this->rows->links() }}
+ @else +
{{ $this->rows->links(data: ['scrollTo' => $this->scroll()]) }}
+ @endif +
+ @endif + @endif + + @if($this->useLoadingIndicator()) + {{-- Table loading spinners... --}} + @if($this->showOverlay) +
+
+ @endif + +
+ + + + +
+ @endif + + +
+ + @includeIf($this->footerView()) + @endif +
\ No newline at end of file diff --git a/resources/views/query-table.blade.php b/resources/views/query-table.blade.php index d326796..8204e86 100644 --- a/resources/views/query-table.blade.php +++ b/resources/views/query-table.blade.php @@ -1,6 +1,8 @@
- @includeIf($this->headerView()) +
+ @includeIf($this->headerView()) +
@if($this->columns()) @@ -74,7 +76,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">label }} @if ($sortBy === $column->key) - @if ($sortDirection === 'asc') + @if ($sortDirection === 'desc') @@ -112,7 +114,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"> You have selected {{ count($selectedRows) }} {{ Str::of('row')->plural(count($selectedRows)) }}. Do you want to select all {{ $this->rows->total() }}? - @@ -210,6 +212,9 @@ class="flex justify-center items-center absolute inset-0"
- @includeIf($this->footerView()) +
+ @includeIf($this->footerView()) +
+ @endif
\ No newline at end of file diff --git a/resources/views/report-table.blade.php b/resources/views/report-table.blade.php index cbe2574..3b5db5a 100644 --- a/resources/views/report-table.blade.php +++ b/resources/views/report-table.blade.php @@ -78,7 +78,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">label }} @if ($sortBy === $column->key) - @if ($sortDirection === 'asc') + @if ($sortDirection === 'desc') @@ -117,7 +117,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"> You have selected {{ count($selectedRows) }} {{ Str::of('row')->plural(count($selectedRows)) }}. Do you want to select all {{ $this->rows->total() }}? - diff --git a/resources/views/table.blade.php b/resources/views/table.blade.php index d01fae1..a41ab3b 100644 --- a/resources/views/table.blade.php +++ b/resources/views/table.blade.php @@ -71,7 +71,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300">label }} @if ($sortBy === $column->key) - @if ($sortDirection === 'asc') + @if ($sortDirection === 'desc') @@ -110,7 +110,7 @@ class="sr-only ml-2 text-sm font-medium text-gray-900 dark:text-gray-300"> You have selected {{ count($selectedRows) }} {{ Str::of('row')->plural(count($selectedRows)) }}. Do you want to select all {{ $this->rows->total() }}? - @@ -142,6 +142,7 @@ class="ml-2 text-blue-500 hover:text-blue-600">
diff --git a/src/GroupsBuilder.php b/src/GroupsBuilder.php new file mode 100755 index 0000000..0574add --- /dev/null +++ b/src/GroupsBuilder.php @@ -0,0 +1,21 @@ +orderBy($this->sortBy, $this->sortDirection); }); + $query->when($this->sortBy !== '', function ($query) { + if (Str::contains($this->sortBy, '.')) { + // If it's a relationship, use orderByRelated + $this->orderByRelated($query, $this->sortBy, $this->sortDirection); + } else { + // Otherwise, use regular orderBy + $query->orderBy($this->sortBy, $this->sortDirection); + } + }); + return $query; } diff --git a/src/Support/Concerns/WithFilters.php b/src/Support/Concerns/WithFilters.php index bf06312..dedc2e0 100644 --- a/src/Support/Concerns/WithFilters.php +++ b/src/Support/Concerns/WithFilters.php @@ -2,12 +2,13 @@ namespace ACTTraining\QueryBuilder\Support\Concerns; -use Livewire\Attributes\Session; +use Livewire\Attributes\Url; trait WithFilters { protected bool $displayFilters = false; + #[Url(as: 'f')] public array $filterValues = []; public function areFiltersAvailable(): bool diff --git a/src/Support/Concerns/WithGroupBuilder.php b/src/Support/Concerns/WithGroupBuilder.php new file mode 100644 index 0000000..dfc60a4 --- /dev/null +++ b/src/Support/Concerns/WithGroupBuilder.php @@ -0,0 +1,166 @@ +findElementByKey($value, $targetValue); + if ($result !== null) { + return $result; + } + } + } + + return null; // Return null if no match is found + } + + public function updatedGroupBy(): void + { + $this->resetPage(); + $this->dispatch('refreshTable')->self(); + } + + public function availableColumns(): array + { + return []; + } + + public function configuredColumns(): array + { + $columns = []; + + foreach ($this->selectedColumns as $column) { + $columns[] = $this->findElementByKey($this->availableColumns(), $column); + } + + return $columns; + } + + public function buildColumns(): array + { + $columns = []; + + $counter = 0; + + foreach ($this->configuredColumns() as $column) { + $columnToAdd = match ($column['type'] ?? null) { + 'number' => Column::make($column['label'], $column['key'])->justify('right'), + 'boolean' => BooleanColumn::make($column['label'], $column['key'])->justify('center')->hideIf(false), + 'date' => DateColumn::make($column['label'], $column['key'])->format(config('settings.date.short-format'))->justify('right'), + 'view' => ViewColumn::make($column['label'])->justify($column['justify'] ?? 'left'), + default => Column::make($column['label'], $column['key']) + }; + + if ($column['view'] ?? false) { + $columnToAdd->component($column['view']); + } elseif ($counter === 0) { + $columnToAdd->component('columns.common.title'); + } + + if ($column['sortable'] ?? false) { + $columnToAdd->sortable(); + } + + if ($column['justify'] ?? false) { + $columnToAdd->justify($column['justify']); + } + + $columns[] = $columnToAdd; + + $counter++; + } + + return $columns; + } + + public function buildConditions(): array + { + $conditions = []; + + foreach ($this->configuredColumns() as $column) { + if ($column['skipCondition'] ?? false) { + continue; + } + + $conditions[] = match ($column['type'] ?? null) { + 'number' => NumberCondition::make($column['label'], $column['key']), + 'enum' => EnumCondition::make($column['label'], $column['key'], $column['options'] ?? []), + 'float' => FloatCondition::make($column['label'], $column['key']), + 'boolean' => BooleanCondition::make($column['label'], $column['key']), + 'date' => DateCondition::make($column['label'], $column['key']), + default => TextCondition::make($column['label'], $column['key']) + }; + + } + + return $conditions; + } + + public function removeCriteria($index): void + { + unset($this->criteria[$index]); + $this->criteria = array_values($this->criteria); + + $this->saveToSession(); + } + + public function resetReportBuilder(): void + { + $this->criteria = []; + $this->selectedColumns = []; + $this->saveToSession(); + } + + public function exportReportBuilder(): Response|BinaryFileResponse|null + { + return null; + } + + public function saveReportBuilder(): void + { + // + } + + public function loadReportBuilder(): void + { + // + } + + public function saveToSession(): void + { + // + } +} diff --git a/src/Support/Concerns/WithPagination.php b/src/Support/Concerns/WithPagination.php index 825dc9b..2f83ae0 100644 --- a/src/Support/Concerns/WithPagination.php +++ b/src/Support/Concerns/WithPagination.php @@ -12,7 +12,7 @@ trait WithPagination private bool $paginate = true; - private bool|string $scrollTo = false; //false disables scroll on pagination + private bool|string $scrollTo = false; // false disables scroll on pagination public function usePagination($usePagination = true): static { diff --git a/src/Support/Concerns/WithReportBuilder.php b/src/Support/Concerns/WithReportBuilder.php index ef384a9..6b465c3 100644 --- a/src/Support/Concerns/WithReportBuilder.php +++ b/src/Support/Concerns/WithReportBuilder.php @@ -5,10 +5,12 @@ use ACTTraining\QueryBuilder\Support\Columns\BooleanColumn; use ACTTraining\QueryBuilder\Support\Columns\Column; use ACTTraining\QueryBuilder\Support\Columns\DateColumn; +use ACTTraining\QueryBuilder\Support\Columns\ViewColumn; use ACTTraining\QueryBuilder\Support\Conditions\BooleanCondition; use ACTTraining\QueryBuilder\Support\Conditions\DateCondition; use ACTTraining\QueryBuilder\Support\Conditions\EnumCondition; use ACTTraining\QueryBuilder\Support\Conditions\FloatCondition; +use ACTTraining\QueryBuilder\Support\Conditions\NullCondition; use ACTTraining\QueryBuilder\Support\Conditions\NumberCondition; use ACTTraining\QueryBuilder\Support\Conditions\TextCondition; use Illuminate\Http\Response; @@ -66,33 +68,41 @@ public function configuredColumns(): array public function buildColumns(): array { $columns = []; - $counter = 0; - foreach ($this->configuredColumns() as $column) { - $columnToAdd = match ($column['type'] ?? null) { + foreach (($this->configuredColumns() ?? []) as $column) { + // Skip nulls/invalid items early + if (! is_array($column) || ! isset($column['label'], $column['key'])) { + continue; + } + + $type = $column['type'] ?? null; + + $columnToAdd = match ($type) { 'number' => Column::make($column['label'], $column['key'])->justify('right'), 'boolean' => BooleanColumn::make($column['label'], $column['key'])->justify('center')->hideIf(false), - 'date' => DateColumn::make($column['label'], $column['key'])->format(config('settings.date.short-format'))->justify('right'), - default => Column::make($column['label'], $column['key']) + 'date' => DateColumn::make($column['label'], $column['key']) + ->format(config('settings.date.short-format')) + ->justify('right'), + 'view' => ViewColumn::make($column['label']), + default => Column::make($column['label'], $column['key']), }; - if ($column['view'] ?? false) { + if (! empty($column['view'])) { $columnToAdd->component($column['view']); } elseif ($counter === 0) { $columnToAdd->component('columns.common.title'); } - if ($column['sortable'] ?? false) { + if (! empty($column['sortable'])) { $columnToAdd->sortable(); } - if ($column['justify'] ?? false) { + if (! empty($column['justify'])) { $columnToAdd->justify($column['justify']); } $columns[] = $columnToAdd; - $counter++; } @@ -103,20 +113,33 @@ public function buildConditions(): array { $conditions = []; - foreach ($this->configuredColumns() as $column) { - if ($column['skipCondition'] ?? false) { + foreach (($this->configuredColumns() ?? []) as $column) { + // Skip nulls/garbage early + if (! is_array($column) || ! isset($column['label'], $column['key'])) { continue; } - $conditions[] = match ($column['type'] ?? null) { + if (! empty($column['skipCondition'])) { + continue; + } + + $type = $column['type'] ?? null; + + // Normalise enum options + $options = $column['options'] ?? []; + if (! is_array($options)) { + $options = []; + } + + $conditions[] = match ($type) { 'number' => NumberCondition::make($column['label'], $column['key']), - 'enum' => EnumCondition::make($column['label'], $column['key'], $column['options'] ?? []), + 'enum' => EnumCondition::make($column['label'], $column['key'], $options), 'float' => FloatCondition::make($column['label'], $column['key']), 'boolean' => BooleanCondition::make($column['label'], $column['key']), 'date' => DateCondition::make($column['label'], $column['key']), - default => TextCondition::make($column['label'], $column['key']) + 'null' => NullCondition::make($column['label'], $column['key']), + default => TextCondition::make($column['label'], $column['key']), }; - } return $conditions; diff --git a/src/Support/Concerns/WithSearch.php b/src/Support/Concerns/WithSearch.php index b0555e0..10791da 100644 --- a/src/Support/Concerns/WithSearch.php +++ b/src/Support/Concerns/WithSearch.php @@ -2,7 +2,7 @@ namespace ACTTraining\QueryBuilder\Support\Concerns; -use Livewire\Attributes\Session; +use Livewire\Attributes\Url; trait WithSearch { @@ -12,6 +12,7 @@ trait WithSearch protected bool $displaySearchableIcon = true; + #[Url(as: 's')] public string $searchBy = ''; protected array $additionalSearchables = []; diff --git a/src/Support/Concerns/WithSelecting.php b/src/Support/Concerns/WithSelecting.php index ce1ef62..e632d96 100644 --- a/src/Support/Concerns/WithSelecting.php +++ b/src/Support/Concerns/WithSelecting.php @@ -23,17 +23,24 @@ public function updatedSelectPage($value): void } $this->dispatch('refreshTable')->self(); + } public function updatedSelectedRows(): void { $this->selectAll = false; $this->selectPage = false; + $this->dispatch('refreshTable')->self(); } - public function selectAll(): void + public function toggleSelectAll(): void { $this->selectAll = ! $this->selectAll; + + $this->selectedRows = $this->selectAll + ? $this->rowsQuery->pluck('id')->toArray() + : []; + $this->dispatch('refreshTable')->self(); } public function clearSelection(): void @@ -41,5 +48,7 @@ public function clearSelection(): void $this->selectAll = false; $this->selectPage = false; $this->selectedRows = []; + + $this->dispatch('refreshTable')->self(); } } diff --git a/src/Support/Concerns/WithSorting.php b/src/Support/Concerns/WithSorting.php index 4bd96d0..03e127d 100644 --- a/src/Support/Concerns/WithSorting.php +++ b/src/Support/Concerns/WithSorting.php @@ -2,6 +2,9 @@ namespace ACTTraining\QueryBuilder\Support\Concerns; +use Illuminate\Database\Eloquent\Builder; +use InvalidArgumentException; + trait WithSorting { use WithPagination; @@ -10,7 +13,7 @@ trait WithSorting public string $sortDirection = 'asc'; - protected $sortable = false; + protected bool $sortable = false; public function isSortable(): bool { @@ -24,13 +27,32 @@ public function sortable(bool $sortable = true): static return $this; } + public function setSort($key, $direction = 'asc'): static + { + $this->sortBy = $key; + $this->sortDirection = $direction; + + return $this; + } + public function sort($key): void { $this->resetPage(); if ($this->sortBy === $key) { - $direction = $this->sortDirection === 'asc' ? 'desc' : 'asc'; - $this->sortDirection = $direction; + + $direction = match ($this->sortDirection) { + 'asc' => 'desc', + 'desc' , => null, + default => 'asc', + }; + + if ($direction === null) { + $this->sortDirection = 'asc'; + $this->sortBy = ''; + } else { + $this->sortDirection = $direction; + } return; } @@ -38,4 +60,26 @@ public function sort($key): void $this->sortBy = $key; $this->sortDirection = 'asc'; } + + protected function orderByRelated(Builder $query, string $relationColumn, string $direction = 'asc'): void + { + // Ensure only one dot is present + if (substr_count($relationColumn, '.') !== 1) { + throw new InvalidArgumentException("Invalid relation column: '{$relationColumn}'. Only single-level relationships are supported."); + } + + [$relation, $column] = explode('.', $relationColumn, 2); + + // Get the related model instance correctly + $relatedModel = $query->getModel()->{$relation}()->getRelated(); + + $query->orderBy( + $relatedModel::select($column) + ->whereColumn( + "{$relatedModel->getTable()}.id", + $query->getModel()->{$relation}()->getForeignKeyName() + ), + $direction + ); + } } diff --git a/src/Support/Conditions/NullCondition.php b/src/Support/Conditions/NullCondition.php new file mode 100644 index 0000000..3a46202 --- /dev/null +++ b/src/Support/Conditions/NullCondition.php @@ -0,0 +1,16 @@ + 'is set', + 'is_not_set' => 'is not set', + ]; + } +} diff --git a/src/Support/Criteria/DateCriteria.php b/src/Support/Criteria/DateCriteria.php index 08f3f99..dbe3fbb 100644 --- a/src/Support/Criteria/DateCriteria.php +++ b/src/Support/Criteria/DateCriteria.php @@ -17,23 +17,8 @@ class DateCriteria extends BaseCriteria implements CriteriaInterface public function __construct(string $field, string|array $value, string $operator = '=') { - // Check if the date is in the 'd/m/Y' format and convert it to 'd-m-Y' - $date = \DateTime::createFromFormat('d/m/Y', $field); - - if ($date) { - // If the date is successfully parsed in the 'd/m/Y' format, convert it to 'd-m-Y' - $this->field = $date->format('d-m-Y'); - } else { - // If the date was not in 'd/m/Y', assume it's already in 'd-m-Y' format - // Optionally, you could check again if it's in the correct format - $date = \DateTime::createFromFormat('d-m-Y', $field); - - if ($date) { - $this->field = $field; // Already in 'd-m-Y', so just assign it - } - } - - $this->value = $value; + $this->field = $field; + $this->value = Carbon::createFromFormat('Y-m-d', $value)->format('d-m-Y'); $this->operator = $operator; } diff --git a/src/Support/Criteria/NullCriteria.php b/src/Support/Criteria/NullCriteria.php index 5140532..0faa6d6 100644 --- a/src/Support/Criteria/NullCriteria.php +++ b/src/Support/Criteria/NullCriteria.php @@ -8,9 +8,9 @@ class NullCriteria extends BaseCriteria implements CriteriaInterface { public string $inputType = 'boolean'; - private $field; + private string $field; - private $operation; + private string $operation; public function __construct(string $field, string $operation) { @@ -22,9 +22,11 @@ public function apply($query): void { $this->applyWhereCondition($query, $this->field, function ($query, $field) { if ($this->operation === 'is_not_set') { - $query->whereNull($field); + $query->whereNull($field) + ->orWhere($field, ''); } else { - $query->whereNotNull($field); + $query->whereNotNull($field) + ->orWhere($field, '!=', ''); } }); } diff --git a/src/Support/Filters/NullFilter.php b/src/Support/Filters/NullFilter.php new file mode 100644 index 0000000..15f2998 --- /dev/null +++ b/src/Support/Filters/NullFilter.php @@ -0,0 +1,43 @@ + 'Is set', + 'isNotSet' => 'Is not set', + ]; + } + + public function prompt(): string + { + return $this->prompt; + } + + public function apply($query, $value) + { + $key = $this->key(); + $isNegation = $value === 'isNotSet'; + + return $query->where(function ($q) use ($key, $isNegation) { + if ($isNegation) { + $q->whereNull($key) + ->orWhere($key, '') + ->orWhereJsonLength($key, 0); + } else { + $q->whereNotNull($key) + ->where($key, '!=', '') + ->whereJsonLength($key, '>', 0); + } + }); + } +} diff --git a/src/Support/Filters/SelectFilter.php b/src/Support/Filters/SelectFilter.php index 15e5473..f14f96e 100644 --- a/src/Support/Filters/SelectFilter.php +++ b/src/Support/Filters/SelectFilter.php @@ -4,20 +4,20 @@ class SelectFilter extends BaseFilter { - public $options = []; + public array $options = []; - public $optionsInGroups = []; + public array $optionsInGroups = []; public string $component = 'select'; private string $prompt = 'Select an option'; - public function options() + public function options(): array { return $this->options; } - public function optionsInGroups() + public function optionsInGroups(): array { return $this->optionsInGroups; } diff --git a/src/TableBuilder.php b/src/TableBuilder.php index b3d33ea..6b5cf8b 100755 --- a/src/TableBuilder.php +++ b/src/TableBuilder.php @@ -17,6 +17,7 @@ use ACTTraining\QueryBuilder\Support\Concerns\WithSelecting; use ACTTraining\QueryBuilder\Support\Concerns\WithSorting; use ACTTraining\QueryBuilder\Support\Concerns\WithViews; +use ACTTraining\QueryBuilder\Support\Filters\NullFilter; use Illuminate\Contracts\View\Factory; use Illuminate\Contracts\View\View; use Illuminate\Database\Eloquent\Builder; @@ -78,7 +79,13 @@ public function config(): void public function getRowsQueryProperty() { $query = $this->query()->when($this->sortBy !== '', function ($query) { - $query->orderBy($this->sortBy, $this->sortDirection); + if (Str::contains($this->sortBy, '.')) { + // If it's a relationship, use orderByRelated + $this->orderByRelated($query, $this->sortBy, $this->sortDirection); + } else { + // Otherwise, use regular orderBy + $query->orderBy($this->sortBy, $this->sortDirection); + } }); if ($this->searchBy && $this->searchBy !== '') { @@ -111,13 +118,55 @@ public function getRowsQueryProperty() $value = $dottedFilterValue[$filter->code()] ?? null; $query->when($value !== null, function ($query) use ($filter, $value) { + // Special case for NullFilter with isSet / isNotSet values + if ($filter instanceof NullFilter && in_array($value, ['isSet', 'isNotSet'])) { + $isNegation = $value === 'isNotSet'; + + if (Str::contains($filter->key(), '.')) { + $relation = Str::beforeLast($filter->key(), '.'); + $column = Str::afterLast($filter->key(), '.'); + + $query->whereHas($relation, function ($q) use ($column, $isNegation) { + $q->where(function ($q) use ($column, $isNegation) { + if ($isNegation) { + $q->whereNull($column) + ->orWhere($column, '') + ->orWhereJsonLength($column, 0); + } else { + $q->whereNotNull($column) + ->where($column, '!=', '') + ->whereJsonLength($column, '>', 0); + } + }); + }); + } else { + $query->where(function ($q) use ($filter, $isNegation) { + $key = $filter->key(); + + if ($isNegation) { + $q->whereNull($key) + ->orWhere($key, '') + ->orWhereJsonLength($key, 0); + } else { + $q->whereNotNull($key) + ->where($key, '!=', '') + ->whereJsonLength($key, '>', 0); + } + }); + } + + return; + } + + // Normal filters $value = $filter->parseValue($value); + if (Str::contains($filter->key(), '.')) { $relation = Str::beforeLast($filter->key(), '.'); $column = Str::afterLast($filter->key(), '.'); - $query->whereHas($relation, function ($query) use ($filter, $value, $column) { - $query->where($column, $filter->operator(), $value); + $query->whereHas($relation, function ($q) use ($filter, $value, $column) { + $q->where($column, $filter->operator(), $value); }); } else { $query->where($filter->key(), $filter->operator(), $value);