Skip to content

Commit 0cc1d6d

Browse files
Merge pull request #110 from ACT-Training/feature/issue-109-merge-groupby-into-report-builder
Merge GroupsBuilder into ReportBuilder with GroupBy support
2 parents ba2791f + 333722b commit 0cc1d6d

8 files changed

Lines changed: 512 additions & 251 deletions

File tree

.claude/settings.local.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,12 @@
99
"Bash(vendor/bin/pest:*)",
1010
"Bash(vendor/bin/pint:*)",
1111
"Bash(git push:*)",
12-
"Bash(gh pr:*)"
12+
"Bash(gh pr:*)",
13+
"Bash(composer analyse:*)",
14+
"Bash(composer test:*)",
15+
"Bash(composer format:*)",
16+
"Bash(git stash:*)",
17+
"Bash(git commit:*)"
1318
]
1419
}
1520
}

README.md

Lines changed: 238 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,44 +1,62 @@
1-
# A drop in query builder for Laravel models.
1+
# Query Builder for Laravel
22

3-
Add a query builder and report table for youir models.
3+
Drop-in Livewire components for building query builders, tables, and reports on Eloquent models.
44

55
## Installation
66

7-
You can install the package via composer:
8-
97
```bash
108
composer require act-training/query-builder
119
```
1210

13-
Optionally, you can publish the views using
11+
Optionally, publish the views:
1412

1513
```bash
1614
php artisan vendor:publish --tag="query-builder-views"
1715
```
1816

19-
## Usage
20-
Models should use the AppliesCriteria trait.
17+
## Model Setup
18+
19+
Models should use the `AppliesCriteria` trait:
2120

2221
```php
23-
class User extends Authenticatable
22+
use ACTTraining\QueryBuilder\Support\Criteria\AppliesCriteria;
23+
24+
class Employee extends Model
2425
{
2526
use AppliesCriteria;
26-
27-
...
28-
2927
}
3028
```
3129

32-
Create a Livewire component and make sure it extends QueryBuilder.
30+
## Components
31+
32+
The package provides three main abstract Livewire components to extend:
33+
34+
| Component | Purpose |
35+
|---|---|
36+
| `QueryBuilder` | Full query builder with criteria-based AND/OR filtering |
37+
| `TableBuilder` | Simpler table with URL-persisted filters and search |
38+
| `ReportBuilder` | Dynamic column selection, save/load/export, optional groupBy |
39+
40+
### QueryBuilder
41+
42+
Create a Livewire component that extends `QueryBuilder`. Define `query()`, `columns()`, and `conditions()`:
3343

3444
```php
3545
<?php
3646

37-
namespace App\Http\Livewire;
47+
namespace App\Livewire;
3848

39-
use ACTTraining\QueryBuilder\QueryBuilder;use ACTTraining\QueryBuilder\Support\Columns\BooleanColumn;use ACTTraining\QueryBuilder\Support\Columns\Column;use ACTTraining\QueryBuilder\Support\Columns\DateColumn;use ACTTraining\QueryBuilder\Support\Conditions\BooleanCondition;use ACTTraining\QueryBuilder\Support\Conditions\DateCondition;use ACTTraining\QueryBuilder\Support\Conditions\TextCondition;use App\Models\Employee;use Illuminate\Database\Eloquent\Builder;
49+
use ACTTraining\QueryBuilder\QueryBuilder;
50+
use ACTTraining\QueryBuilder\Support\Columns\BooleanColumn;
51+
use ACTTraining\QueryBuilder\Support\Columns\Column;
52+
use ACTTraining\QueryBuilder\Support\Columns\DateColumn;
53+
use ACTTraining\QueryBuilder\Support\Conditions\BooleanCondition;
54+
use ACTTraining\QueryBuilder\Support\Conditions\DateCondition;
55+
use ACTTraining\QueryBuilder\Support\Conditions\TextCondition;
56+
use App\Models\Employee;
57+
use Illuminate\Database\Eloquent\Builder;
4058

41-
class EmployeesReport extends QueryBuilder
59+
class EmployeesTable extends QueryBuilder
4260
{
4361
public function config(): void
4462
{
@@ -47,6 +65,11 @@ class EmployeesReport extends QueryBuilder
4765
->rowClickable(false);
4866
}
4967

68+
public function query(): Builder
69+
{
70+
return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
71+
}
72+
5073
public function columns(): array
5174
{
5275
return [
@@ -74,20 +97,218 @@ class EmployeesReport extends QueryBuilder
7497
DateCondition::make('Start Date', 'contract.start_date'),
7598
];
7699
}
100+
}
101+
```
102+
103+
### TableBuilder
104+
105+
A simpler alternative using filters instead of criteria. Define `query()`, `columns()`, and `filters()`:
106+
107+
```php
108+
<?php
109+
110+
namespace App\Livewire;
111+
112+
use ACTTraining\QueryBuilder\TableBuilder;
113+
use ACTTraining\QueryBuilder\Support\Columns\Column;
114+
use ACTTraining\QueryBuilder\Support\Filters\TextFilter;
115+
use ACTTraining\QueryBuilder\Support\Filters\SelectFilter;
116+
use App\Models\Employee;
117+
use Illuminate\Database\Eloquent\Builder;
118+
119+
class EmployeesList extends TableBuilder
120+
{
121+
public function query(): Builder
122+
{
123+
return Employee::query();
124+
}
125+
126+
public function columns(): array
127+
{
128+
return [
129+
Column::make('Name', 'full_name')->sortable()->searchable(),
130+
Column::make('Department', 'department')->sortable(),
131+
];
132+
}
133+
134+
public function filters(): array
135+
{
136+
return [
137+
TextFilter::make('Name', 'full_name'),
138+
SelectFilter::make('Department', 'department')
139+
->options(['HR' => 'HR', 'IT' => 'IT', 'Finance' => 'Finance']),
140+
];
141+
}
142+
}
143+
```
144+
145+
### ReportBuilder
146+
147+
Extends `QueryBuilder` with dynamic column selection. Users pick columns at runtime from `availableColumns()`, and the component builds columns and conditions automatically:
148+
149+
```php
150+
<?php
77151

152+
namespace App\Livewire;
153+
154+
use ACTTraining\QueryBuilder\ReportBuilder;
155+
use App\Models\Employee;
156+
use Illuminate\Database\Eloquent\Builder;
157+
158+
class EmployeesReport extends ReportBuilder
159+
{
78160
public function query(): Builder
79161
{
80162
return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
81163
}
82164

83-
public function rowClick($row): void
165+
public function availableColumns(): array
84166
{
85-
$this->dispatchBrowserEvent('notify', ['content' => 'The row was clicked', 'type' => 'success']);
167+
return [
168+
'Employee' => [
169+
['label' => 'Name', 'key' => 'full_name'],
170+
['label' => 'Email', 'key' => 'email'],
171+
],
172+
'Contract' => [
173+
['label' => 'Job Title', 'key' => 'contract.job.name'],
174+
['label' => 'Location', 'key' => 'contract.location.name'],
175+
['label' => 'Start Date', 'key' => 'contract.start_date', 'type' => 'date'],
176+
['label' => 'Salary', 'key' => 'contract.salary', 'type' => 'number'],
177+
['label' => 'Line Manager', 'key' => 'contract.line_manager', 'type' => 'boolean'],
178+
],
179+
];
180+
}
181+
}
182+
```
183+
184+
Column definitions in `availableColumns()` support these keys:
185+
186+
| Key | Description |
187+
|---|---|
188+
| `label` | Display label (required) |
189+
| `key` | Column key, supports dot-notation for relationships (required) |
190+
| `type` | Column type: `text` (default), `number`, `float`, `boolean`, `date`, `enum`, `null`, `view` |
191+
| `sortable` | Enable sorting on this column |
192+
| `justify` | Alignment: `left`, `center`, `right` |
193+
| `view` | Custom Blade component for rendering |
194+
| `options` | Options array for `enum` type |
195+
| `skipCondition` | Exclude from query builder conditions |
196+
197+
### ReportBuilder with GroupBy
198+
199+
Enable groupBy to allow users to group results by a column with aggregate functions (COUNT, SUM, AVG, MIN, MAX). Set `$enableGroupBy = true`:
200+
201+
```php
202+
<?php
203+
204+
namespace App\Livewire;
205+
206+
use ACTTraining\QueryBuilder\ReportBuilder;
207+
use App\Models\Employee;
208+
use Illuminate\Database\Eloquent\Builder;
209+
210+
class EmployeesGroupReport extends ReportBuilder
211+
{
212+
public bool $enableGroupBy = true;
213+
214+
public function query(): Builder
215+
{
216+
return Employee::query()->with(['contract', 'contract.job', 'contract.location']);
86217
}
87218

219+
public function availableColumns(): array
220+
{
221+
return [
222+
'Employee' => [
223+
['label' => 'Name', 'key' => 'full_name'],
224+
['label' => 'Department', 'key' => 'department'],
225+
],
226+
'Contract' => [
227+
['label' => 'Job Title', 'key' => 'contract.job.name'],
228+
['label' => 'Location', 'key' => 'contract.location.name'],
229+
['label' => 'Salary', 'key' => 'contract.salary', 'type' => 'number'],
230+
],
231+
];
232+
}
88233
}
89234
```
90235

236+
When `enableGroupBy` is true, the report editor UI shows additional controls:
237+
238+
- **Group By Column** - select which column to group by
239+
- **Function** - aggregate function (COUNT, SUM, AVG, MIN, MAX)
240+
- **Aggregate Column** - which column to aggregate (defaults to `id`, numeric columns from `availableColumns()` are included automatically)
241+
242+
An "Aggregate" column is automatically appended to the table when grouping is active.
243+
244+
You can customise the available aggregate functions and groupable/aggregatable columns by overriding:
245+
246+
```php
247+
public function aggregateFunctions(): array
248+
{
249+
return ['COUNT', 'SUM', 'AVG'];
250+
}
251+
252+
public function groupableColumns(): array
253+
{
254+
// Return a flat array of ['label' => ..., 'key' => ...] items
255+
return [
256+
['label' => 'Department', 'key' => 'department'],
257+
['label' => 'Location', 'key' => 'contract.location.name'],
258+
];
259+
}
260+
261+
public function aggregatableColumns(): array
262+
{
263+
return [
264+
['label' => 'ID', 'key' => 'id'],
265+
['label' => 'Salary', 'key' => 'contract.salary'],
266+
];
267+
}
268+
```
269+
270+
## Columns
271+
272+
All column types use a fluent `make($label, $key)` constructor. If `$key` is omitted, it defaults to `Str::snake($label)`.
273+
274+
| Column Type | Description |
275+
|---|---|
276+
| `Column` | General text column |
277+
| `BooleanColumn` | Boolean/checkbox display |
278+
| `DateColumn` | Date formatting with `->format()` and `->humanDiff()` |
279+
| `ViewColumn` | Custom Blade view rendering |
280+
281+
Columns support: `->sortable()`, `->searchable()`, `->justify('right')`, `->component('view.name')`, `->reformatUsing(callable)`, `->withSubTitle(callable)`, `->hideIf(condition)`, `->hideHeader()`.
282+
283+
## Conditions (QueryBuilder)
284+
285+
Used with `QueryBuilder` and `ReportBuilder` for criteria-based filtering:
286+
287+
| Condition Type | Operations |
288+
|---|---|
289+
| `TextCondition` | equals, not_equals, contains, starts_with, ends_with |
290+
| `NumberCondition` | equals, not_equals, greater_than, less_than, is_between |
291+
| `FloatCondition` | equals, not_equals, greater_than, less_than, is_between |
292+
| `BooleanCondition` | is_true, is_false |
293+
| `DateCondition` | equals, before, after, is_between |
294+
| `EnumCondition` | equals, not_equals (with defined options) |
295+
| `NullCondition` | is_set, is_not_set |
296+
297+
## Filters (TableBuilder)
298+
299+
Used with `TableBuilder` for simpler key/operator/value filtering:
300+
301+
`TextFilter`, `NumberFilter`, `DateFilter`, `BooleanFilter`, `SelectFilter`, `NullFilter`
302+
303+
## Relationship Support
304+
305+
Dot-notation keys work throughout columns, conditions, and filters to traverse Eloquent relationships:
306+
307+
```php
308+
Column::make('Job Title', 'contract.job.name')
309+
TextCondition::make('Location', 'contract.location.name')
310+
```
311+
91312
## Testing
92313

93314
```bash

0 commit comments

Comments
 (0)