diff --git a/.github/workflows/run-tests-pcov-pull.yml b/.github/workflows/run-tests-pcov-pull.yml index 3a24a91ba..ef73e4b42 100644 --- a/.github/workflows/run-tests-pcov-pull.yml +++ b/.github/workflows/run-tests-pcov-pull.yml @@ -1,6 +1,11 @@ name: run-tests-pcov-pull on: + push: + branches: + - 'develop' + - 'development' + - 'master' pull_request: branches: - 'develop' @@ -18,7 +23,7 @@ jobs: laravel: [10] stability: [prefer-dist] - name: PCOV-PULL - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} + name: PCOV - ${{ matrix.os }} - P${{ matrix.php }} - L${{ matrix.laravel }} - ${{ matrix.stability }} env: extensionKey: phpextensions-${{ matrix.os }}-P${{ matrix.php }}-withpcov extensions: dom, curl, libxml, mbstring, zip, pcntl, pdo, sqlite, pcov,pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo @@ -86,7 +91,7 @@ jobs: run: php ./vendor/bin/paratest --cache-directory=".phpunit.cache/code-coverage" --strict-coverage --coverage-clover ./coverage.xml --processes=4 - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 + uses: codecov/codecov-action@v4 env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} with: diff --git a/.gitignore b/.gitignore index a36981422..d3e4b8ca4 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,6 @@ phpunit.xml.dist.dev .history/* .env phpunit.xml.bak -phpstan.txt \ No newline at end of file +phpstan.txt +coverage.xml +./tmp/** \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 6993a50e3..7b13aa903 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## UNRELEASED +### New Features +- Add new columns (ArrayColumn, AvgColumn, CountColumn, SumColumn) by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1761 + ## [v3.2.8] - 2024-07-03 ### Bug Fixes - Fix hide bulk actions when empty not reflecting in frontend by @lrljoe in https://github.com/rappasoft/laravel-livewire-tables/pull/1747 diff --git a/coverage.xml b/coverage.xml deleted file mode 100644 index 7428459d2..000000000 --- a/coverage.xml +++ /dev/null @@ -1,2894 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/docs/bulk-actions/_index.md b/docs/bulk-actions/_index.md index f76ab3244..48cb71a17 100644 --- a/docs/bulk-actions/_index.md +++ b/docs/bulk-actions/_index.md @@ -1,4 +1,4 @@ --- title: Bulk Actions -weight: 9 +weight: 10 --- diff --git a/docs/column-types/_index.md b/docs/column-types/_index.md new file mode 100644 index 000000000..821c869a2 --- /dev/null +++ b/docs/column-types/_index.md @@ -0,0 +1,4 @@ +--- +title: Column Types +weight: 5 +--- diff --git a/docs/column-types/array_column.md b/docs/column-types/array_column.md new file mode 100644 index 000000000..1571da2b9 --- /dev/null +++ b/docs/column-types/array_column.md @@ -0,0 +1,22 @@ +--- +title: Array Columns (beta) +weight: 1 +--- + +Array columns provide an easy way to work with and display an array of data from a field. + +``` +ArrayColumn::make('notes', 'name') + ->data(fn($value, $row) => ($row->notes)) + ->outputFormat(fn($index, $value) => "".$value->name."") + ->separator('
') + ->sortable(), +``` + +### Empty Value +You may define the default/empty value using the "emptyValue" method + +``` +ArrayColumn::make('notes', 'name') + ->emptyValue('Unknown'), +``` \ No newline at end of file diff --git a/docs/column-types/avg_column.md b/docs/column-types/avg_column.md new file mode 100644 index 000000000..faa653ca2 --- /dev/null +++ b/docs/column-types/avg_column.md @@ -0,0 +1,14 @@ +--- +title: Avg Columns (beta) +weight: 2 +--- + +Avg columns provide an easy way to display the "Average" of a field on a relation. + +``` + AvgColumn::make('Average Related User Age') + ->setDataSource('users','age') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/column-types/boolean_columns.md b/docs/column-types/boolean_columns.md new file mode 100644 index 000000000..32a759918 --- /dev/null +++ b/docs/column-types/boolean_columns.md @@ -0,0 +1,81 @@ +--- +title: Boolean Columns +weight: 3 +--- + +Boolean columns are good if you have a column type that is a true/false, or 0/1 value. + +For example: + +```php +BooleanColumn::make('Active') +``` + +Would yield: + +![Boolean Column](https://imgur.com/LAk6gHY.png) + +### Using your own view + +If you don't want to use the default view and icons you can set your own: + +```php +BooleanColumn::make('Active') + ->setView('my.active.view') +``` + +You will have access to `$component`, `$status`, and `$successValue`. + +To help you better understand, this is the Tailwind implementation of BooleanColumn: + +```html +@if ($status) + + + +@else + + + +@endif +``` + +### Setting the truthy value + +If you want the false value to be the green option, you can set: + +```php +BooleanColumn::make('Active') + ->setSuccessValue(false); // Makes false the 'successful' option +``` + +That would swap the colors of the icons in the image above. + +### Setting the status value + +By default, the `$status` is set to: + +```php +(bool)$value === true +``` + +You can override this functionality: + +```php +BooleanColumn::make('Active') + // Note: Parameter `$row` available as of v2.4 + ->setCallback(function(string $value, $row) { + // Figure out what makes $value true + }), +``` + +### Different types of boolean display + +By default, the BooleanColumn displays icons. + +If you would like the BooleanColumn to display a plain Yes/No, you can set: + +```php +BooleanColumn::make('Active') + ->yesNo() +``` diff --git a/docs/column-types/button_group_column.md b/docs/column-types/button_group_column.md new file mode 100644 index 000000000..5a3390049 --- /dev/null +++ b/docs/column-types/button_group_column.md @@ -0,0 +1,34 @@ +--- +title: Button Group Columns +weight: 4 +--- + +Button group columns let you provide an array of LinkColumns to display in a single cell. + +```php +ButtonGroupColumn::make('Actions') + ->attributes(function($row) { + return [ + 'class' => 'space-x-2', + ]; + }) + ->buttons([ + LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway + ->title(fn($row) => 'View ' . $row->name) + ->location(fn($row) => route('user.show', $row)) + ->attributes(function($row) { + return [ + 'class' => 'underline text-blue-500 hover:no-underline', + ]; + }), + LinkColumn::make('Edit') + ->title(fn($row) => 'Edit ' . $row->name) + ->location(fn($row) => route('user.edit', $row)) + ->attributes(function($row) { + return [ + 'target' => '_blank', + 'class' => 'underline text-blue-500 hover:no-underline', + ]; + }), + ]), +``` diff --git a/docs/column-types/color_columns.md b/docs/column-types/color_columns.md new file mode 100644 index 000000000..e4920144d --- /dev/null +++ b/docs/column-types/color_columns.md @@ -0,0 +1,41 @@ +--- +title: Color Columns +weight: 5 +--- + +Color columns provide an easy way to a Color in a Column + +You may pass either pass a CSS-compliant colour as a field +```php +ColorColumn::make('Favourite Colour', 'favourite_color'), +``` + +Or you may use a Callback +```php +ColorColumn::make('Favourite Colour') + ->color( + function ($row) { + if ($row->success_rate < 40) + { + return '#ff0000'; + } + else if ($row->success_rate > 90) + { + return '#008000'; + } + else return '#ffa500'; + + } + ), +``` + +You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance. +```php + ColorColumn::make('Favourite Colour') + ->attributes(function ($row) { + return [ + 'class' => '!rounded-lg self-center', + 'default' => true, + ]; + }), +``` diff --git a/docs/column-types/component_column.md b/docs/column-types/component_column.md new file mode 100644 index 000000000..0db9c9483 --- /dev/null +++ b/docs/column-types/component_column.md @@ -0,0 +1,28 @@ +--- +title: Component Columns +weight: 6 +--- + +Component columns let you specify a component name and attributes and provides the column value to the slot. + +```php +// Before +Column::make("Email", "email") + ->format(function ($value) { + return view('components.alert') + ->with('attributes', new ComponentAttributeBag([ + 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', + 'dismissible' => true, + ])) + ->with('slot', $value); + }), + +// After +ComponentColumn::make('E-mail', 'email') + ->component('email') + ->attributes(fn ($value, $row, Column $column) => [ + 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', + 'dismissible' => true, + ]), +``` + diff --git a/docs/column-types/count_column.md b/docs/column-types/count_column.md new file mode 100644 index 000000000..d33cf5df9 --- /dev/null +++ b/docs/column-types/count_column.md @@ -0,0 +1,14 @@ +--- +title: Count Columns (beta) +weight: 7 +--- + +Count columns provide an easy way to display the "Count" of a relation. + +``` + CountColumn::make('Related Users') + ->setDataSource('users') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/column-types/date_columns.md b/docs/column-types/date_columns.md new file mode 100644 index 000000000..abc8bbb42 --- /dev/null +++ b/docs/column-types/date_columns.md @@ -0,0 +1,28 @@ +--- +title: Date Columns +weight: 8 +--- + +Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views. + +You may pass either a DateTime object, in which you can define an "outputFormat" +```php +DateColumn::make('Updated At', 'updated_at') + ->outputFormat('Y-m-d H:i:s), +``` + +Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat: +```php +DateColumn::make('Last Charged', 'last_charged_at') + ->inputFormat('Y-m-d H:i:s') + ->outputFormat('Y-m-d'), +``` + +You may also set an "emptyValue" to use when there is no value from the database: +```php +DateColumn::make('Last Charged', 'last_charged_at') + ->inputFormat('Y-m-d H:i:s') + ->outputFormat('Y-m-d') + ->emptyValue('Not Found'), +``` + diff --git a/docs/column-types/image_columns.md b/docs/column-types/image_columns.md new file mode 100644 index 000000000..f816280af --- /dev/null +++ b/docs/column-types/image_columns.md @@ -0,0 +1,26 @@ +--- +title: Image Columns +weight: 9 +--- + +Image columns provide a way to display images in your table without having to use `format()` or partial views: + +```php +ImageColumn::make('Avatar') + ->location( + fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') + ), +``` + +You may also pass an array of attributes to apply to the image tag: + +```php +ImageColumn::make('Avatar') + ->location( + fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') + ) + ->attributes(fn($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name . ' Avatar', + ]), +``` diff --git a/docs/column-types/link_columns.md b/docs/column-types/link_columns.md new file mode 100644 index 000000000..cba4bc887 --- /dev/null +++ b/docs/column-types/link_columns.md @@ -0,0 +1,24 @@ +--- +title: Link Columns +weight: 10 +--- + +Link columns provide a way to display HTML links in your table without having to use `format()` or partial views: + +```php +LinkColumn::make('Action') + ->title(fn($row) => 'Edit') + ->location(fn($row) => route('admin.users.edit', $row)), +``` + +You may also pass an array of attributes to apply to the `a` tag: + +```php +LinkColumn::make('Action') + ->title(fn($row) => 'Edit') + ->location(fn($row) => route('admin.users.edit', $row)) + ->attributes(fn($row) => [ + 'class' => 'rounded-full', + 'alt' => $row->name . ' Avatar', + ]), +``` diff --git a/docs/column-types/livewire_component_column.md b/docs/column-types/livewire_component_column.md new file mode 100644 index 000000000..b75172881 --- /dev/null +++ b/docs/column-types/livewire_component_column.md @@ -0,0 +1,5 @@ +--- +title: Livewire Component (beta) +weight: 11 +--- + diff --git a/docs/column-types/sum_column.md b/docs/column-types/sum_column.md new file mode 100644 index 000000000..7f99f7cab --- /dev/null +++ b/docs/column-types/sum_column.md @@ -0,0 +1,14 @@ +--- +title: Sum Columns (beta) +weight: 12 +--- + +Sum columns provide an easy way to display the "Sum" of a field on a relation. + +``` + SumColumn::make('Total Age of Related Users') + ->setDataSource('users','age') + ->sortable(), +``` + +The "sortable()" callback can accept a callback, or you can use the default behaviour, which calculates the correct field to sort on. \ No newline at end of file diff --git a/docs/columns/other-column-types.md b/docs/columns/other-column-types.md index dc80da449..2e515eedf 100644 --- a/docs/columns/other-column-types.md +++ b/docs/columns/other-column-types.md @@ -3,247 +3,14 @@ title: Other Column Types weight: 4 --- -## Boolean Columns -Boolean columns are good if you have a column type that is a true/false, or 0/1 value. -For example: -```php -BooleanColumn::make('Active') -``` -Would yield: +## Aggregate Columns -![Boolean Column](https://imgur.com/LAk6gHY.png) +### AvgColumn -### Using your own view +### CountColumn -If you don't want to use the default view and icons you can set your own: - -```php -BooleanColumn::make('Active') - ->setView('my.active.view') -``` - -You will have access to `$component`, `$status`, and `$successValue`. - -To help you better understand, this is the Tailwind implementation of BooleanColumn: - -```html -@if ($status) - - - -@else - - - -@endif -``` - -### Setting the truthy value - -If you want the false value to be the green option, you can set: - -```php -BooleanColumn::make('Active') - ->setSuccessValue(false); // Makes false the 'successful' option -``` - -That would swap the colors of the icons in the image above. - -### Setting the status value - -By default, the `$status` is set to: - -```php -(bool)$value === true -``` - -You can override this functionality: - -```php -BooleanColumn::make('Active') - // Note: Parameter `$row` available as of v2.4 - ->setCallback(function(string $value, $row) { - // Figure out what makes $value true - }), -``` - -### Different types of boolean display - -By default, the BooleanColumn displays icons. - -If you would like the BooleanColumn to display a plain Yes/No, you can set: - -```php -BooleanColumn::make('Active') - ->yesNo() -``` -## Color Columns - -Color columns provide an easy way to a Color in a Column - -You may pass either pass a CSS-compliant colour as a field -```php -ColorColumn::make('Favourite Colour', 'favourite_color'), -``` - -Or you may use a Callback -```php -ColorColumn::make('Favourite Colour') - ->color( - function ($row) { - if ($row->success_rate < 40) - { - return '#ff0000'; - } - else if ($row->success_rate > 90) - { - return '#008000'; - } - else return '#ffa500'; - - } - ), -``` - -You may also specify attributes to use on the div displaying the color, to adjust the size or appearance, this receives the full row. By default, this will replace the standard classes, to retain them, set "default" to true. To then over-ride, you should prefix your classes with "!" to signify importance. -```php - ColorColumn::make('Favourite Colour') - ->attributes(function ($row) { - return [ - 'class' => '!rounded-lg self-center', - 'default' => true, - ]; - }), -``` - -## Date Columns - -Date columns provide an easy way to display dates in a given format, without having to use repetitive format() methods or partial views. - -You may pass either a DateTime object, in which you can define an "outputFormat" -```php -DateColumn::make('Updated At', 'updated_at') - ->outputFormat('Y-m-d H:i:s), -``` - -Or you may pass a string, in which case you can define an "inputFormat" in addition to the outputFormat: -```php -DateColumn::make('Last Charged', 'last_charged_at') - ->inputFormat('Y-m-d H:i:s') - ->outputFormat('Y-m-d'), -``` - -You may also set an "emptyValue" to use when there is no value from the database: -```php -DateColumn::make('Last Charged', 'last_charged_at') - ->inputFormat('Y-m-d H:i:s') - ->outputFormat('Y-m-d') - ->emptyValue('Not Found'), -``` - -## Image Columns - -Image columns provide a way to display images in your table without having to use `format()` or partial views: - -```php -ImageColumn::make('Avatar') - ->location( - fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') - ), -``` - -You may also pass an array of attributes to apply to the image tag: - -```php -ImageColumn::make('Avatar') - ->location( - fn($row) => storage_path('app/public/avatars/' . $row->id . '.jpg') - ) - ->attributes(fn($row) => [ - 'class' => 'rounded-full', - 'alt' => $row->name . ' Avatar', - ]), -``` - -## Link Columns - -Link columns provide a way to display HTML links in your table without having to use `format()` or partial views: - -```php -LinkColumn::make('Action') - ->title(fn($row) => 'Edit') - ->location(fn($row) => route('admin.users.edit', $row)), -``` - -You may also pass an array of attributes to apply to the `a` tag: - -```php -LinkColumn::make('Action') - ->title(fn($row) => 'Edit') - ->location(fn($row) => route('admin.users.edit', $row)) - ->attributes(fn($row) => [ - 'class' => 'rounded-full', - 'alt' => $row->name . ' Avatar', - ]), -``` - -## Button Group Columns - -Button group columns let you provide an array of LinkColumns to display in a single cell. - -```php -ButtonGroupColumn::make('Actions') - ->attributes(function($row) { - return [ - 'class' => 'space-x-2', - ]; - }) - ->buttons([ - LinkColumn::make('View') // make() has no effect in this case but needs to be set anyway - ->title(fn($row) => 'View ' . $row->name) - ->location(fn($row) => route('user.show', $row)) - ->attributes(function($row) { - return [ - 'class' => 'underline text-blue-500 hover:no-underline', - ]; - }), - LinkColumn::make('Edit') - ->title(fn($row) => 'Edit ' . $row->name) - ->location(fn($row) => route('user.edit', $row)) - ->attributes(function($row) { - return [ - 'target' => '_blank', - 'class' => 'underline text-blue-500 hover:no-underline', - ]; - }), - ]), -``` - -## Component Columns - -Component columns let you specify a component name and attributes and provides the column value to the slot. - -```php -// Before -Column::make("Email", "email") - ->format(function ($value) { - return view('components.alert') - ->with('attributes', new ComponentAttributeBag([ - 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', - 'dismissible' => true, - ])) - ->with('slot', $value); - }), - -// After -ComponentColumn::make('E-mail', 'email') - ->component('email') - ->attributes(fn ($value, $row, Column $column) => [ - 'type' => Str::endsWith($value, 'example.org') ? 'success' : 'danger', - 'dismissible' => true, - ]), -``` +### SumColumn \ No newline at end of file diff --git a/docs/examples/_index.md b/docs/examples/_index.md index a831d1a26..03b1d982b 100644 --- a/docs/examples/_index.md +++ b/docs/examples/_index.md @@ -1,4 +1,4 @@ --- title: Examples -weight: 14 +weight: 16 --- diff --git a/docs/filter-types/_index.md b/docs/filter-types/_index.md index 6c6d8ccf4..3631183ba 100644 --- a/docs/filter-types/_index.md +++ b/docs/filter-types/_index.md @@ -1,4 +1,4 @@ --- title: Filter Types -weight: 11 +weight: 12 --- diff --git a/docs/filters/_index.md b/docs/filters/_index.md index 43af373bd..e0ac299f1 100644 --- a/docs/filters/_index.md +++ b/docs/filters/_index.md @@ -1,4 +1,4 @@ --- title: Filters -weight: 10 +weight: 11 --- diff --git a/docs/footer/_index.md b/docs/footer/_index.md index e16f7efb5..4edcb2460 100644 --- a/docs/footer/_index.md +++ b/docs/footer/_index.md @@ -1,4 +1,4 @@ --- title: Footer -weight: 13 +weight: 15 --- diff --git a/docs/misc/_index.md b/docs/misc/_index.md index 9f806b32e..193fabdf4 100644 --- a/docs/misc/_index.md +++ b/docs/misc/_index.md @@ -1,4 +1,4 @@ --- title: Misc. -weight: 15 +weight: 17 --- diff --git a/docs/pagination/_index.md b/docs/pagination/_index.md index 723cb0c11..50491640d 100644 --- a/docs/pagination/_index.md +++ b/docs/pagination/_index.md @@ -1,4 +1,4 @@ --- title: Pagination -weight: 7 +weight: 8 --- diff --git a/docs/reordering/_index.md b/docs/reordering/_index.md index bc21a450b..e66081c28 100644 --- a/docs/reordering/_index.md +++ b/docs/reordering/_index.md @@ -1,4 +1,4 @@ --- title: Reordering -weight: 11 +weight: 13 --- diff --git a/docs/rows/_index.md b/docs/rows/_index.md index f731c19d2..7d5ebba99 100644 --- a/docs/rows/_index.md +++ b/docs/rows/_index.md @@ -1,4 +1,4 @@ --- title: Rows -weight: 5 +weight: 6 --- diff --git a/docs/search/_index.md b/docs/search/_index.md index edb174fde..4aac1542b 100644 --- a/docs/search/_index.md +++ b/docs/search/_index.md @@ -1,4 +1,4 @@ --- title: Search -weight: 8 +weight: 9 --- diff --git a/docs/secondary-header/_index.md b/docs/secondary-header/_index.md index 5e2b9aba5..f334c9e68 100644 --- a/docs/secondary-header/_index.md +++ b/docs/secondary-header/_index.md @@ -1,4 +1,4 @@ --- title: Secondary Header -weight: 12 +weight: 14 --- diff --git a/docs/sorting/_index.md b/docs/sorting/_index.md index a99eabf1d..422739fb6 100644 --- a/docs/sorting/_index.md +++ b/docs/sorting/_index.md @@ -1,4 +1,4 @@ --- title: Sorting -weight: 6 +weight: 7 --- diff --git a/src/Traits/ComponentUtilities.php b/src/Traits/ComponentUtilities.php index 1e421e0b6..8ec4bd8b6 100644 --- a/src/Traits/ComponentUtilities.php +++ b/src/Traits/ComponentUtilities.php @@ -37,6 +37,14 @@ trait ComponentUtilities protected array $additionalSelects = []; + protected array $extraWiths = []; + + protected array $extraWithCounts = []; + + protected array $extraWithSums = []; + + protected array $extraWithAvgs = []; + /** * Set any configuration options */ diff --git a/src/Traits/Configuration/ComponentConfiguration.php b/src/Traits/Configuration/ComponentConfiguration.php index 90611185a..044f5655e 100644 --- a/src/Traits/Configuration/ComponentConfiguration.php +++ b/src/Traits/Configuration/ComponentConfiguration.php @@ -96,4 +96,60 @@ public function setDataTableFingerprint(string $dataTableFingerprint): self return $this; } + + public function setExtraWiths(array $extraWiths): self + { + $this->extraWiths = $extraWiths; + + return $this; + } + + public function addExtraWith(string $extraWith): self + { + $this->extraWiths[] = $extraWith; + + return $this; + } + + public function addExtraWiths(array $extraWiths): self + { + $this->extraWiths = [...$this->extraWiths, ...$extraWiths]; + + return $this; + } + + public function setExtraWithCounts(array $extraWithCounts): self + { + $this->extraWithCounts = $extraWithCounts; + + return $this; + } + + public function addExtraWithCount(string $extraWithCount): self + { + $this->extraWithCounts[] = $extraWithCount; + + return $this; + } + + public function addExtraWithCounts(array $extraWithCounts): self + { + $this->extraWithCounts = [...$this->extraWithCounts, ...$extraWithCounts]; + + return $this; + } + + public function addExtraWithSum(string $relationship, string $column): self + { + $this->extraWithSums[] = ['table' => $relationship, 'field' => $column]; + + return $this; + } + + public function addExtraWithAvg(string $relationship, string $column): self + { + $this->extraWithAvgs[] = ['table' => $relationship, 'field' => $column]; + + return $this; + } } diff --git a/src/Traits/Helpers/ColumnHelpers.php b/src/Traits/Helpers/ColumnHelpers.php index 009a6f4bd..920d63fe6 100644 --- a/src/Traits/Helpers/ColumnHelpers.php +++ b/src/Traits/Helpers/ColumnHelpers.php @@ -4,6 +4,7 @@ use Illuminate\Support\Collection; use Rappasoft\LaravelLivewireTables\Views\Column; +use Rappasoft\LaravelLivewireTables\Views\Columns\AggregateColumn; trait ColumnHelpers { @@ -18,6 +19,15 @@ public function setColumns(): void ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { @@ -198,6 +208,15 @@ public function getPrependedColumns(): Collection ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { @@ -217,6 +236,15 @@ public function getAppendedColumns(): Collection ->filter(fn ($column) => $column instanceof Column) ->map(function (Column $column) { $column->setComponent($this); + if ($column instanceof AggregateColumn) { + if ($column->getAggregateMethod() == 'count' && $column->hasDataSource()) { + $this->addExtraWithCount($column->getDataSource()); + } elseif ($column->getAggregateMethod() == 'sum' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithSum($column->getDataSource(), $column->getForeignColumn()); + } elseif ($column->getAggregateMethod() == 'avg' && $column->hasDataSource() && $column->hasForeignColumn()) { + $this->addExtraWithAvg($column->getDataSource(), $column->getForeignColumn()); + } + } if ($column->hasField()) { if ($column->isBaseColumn()) { diff --git a/src/Traits/Helpers/ComponentHelpers.php b/src/Traits/Helpers/ComponentHelpers.php index c600200b5..96eae35f8 100644 --- a/src/Traits/Helpers/ComponentHelpers.php +++ b/src/Traits/Helpers/ComponentHelpers.php @@ -149,4 +149,44 @@ public function getAdditionalSelects(): array { return $this->additionalSelects; } + + public function hasExtraWiths(): bool + { + return ! empty($this->extraWiths); + } + + public function getExtraWiths(): array + { + return $this->extraWiths; + } + + public function hasExtraWithCounts(): bool + { + return ! empty($this->extraWithCounts); + } + + public function getExtraWithCounts(): array + { + return $this->extraWithCounts; + } + + public function hasExtraWithSums(): bool + { + return ! empty($this->extraWithSums); + } + + public function getExtraWithSums(): array + { + return $this->extraWithSums; + } + + public function hasExtraWithAvgs(): bool + { + return ! empty($this->extraWithAvgs); + } + + public function getExtraWithAvgs(): array + { + return $this->extraWithAvgs; + } } diff --git a/src/Traits/WithData.php b/src/Traits/WithData.php index 5e1ca3298..23d1055bb 100644 --- a/src/Traits/WithData.php +++ b/src/Traits/WithData.php @@ -56,6 +56,29 @@ protected function baseQuery(): Builder $this->setBuilder($this->applyFilters()); + $builder = $this->getBuilder(); + + if ($this->hasExtraWiths()) { + $builder->with($this->getExtraWiths()); + } + + if ($this->hasExtraWithSums()) { + foreach ($this->getExtraWithSums() as $extraSum) { + $builder->withSum($extraSum['table'], $extraSum['field']); + } + } + if ($this->hasExtraWithAvgs()) { + foreach ($this->getExtraWithAvgs() as $extraAvg) { + $builder->withAvg($extraAvg['table'], $extraAvg['field']); + } + } + + if ($this->hasExtraWithCounts()) { + $builder->withCount($this->getExtraWithCounts()); + } + + $this->setBuilder($builder); + return $this->getBuilder(); } @@ -249,7 +272,8 @@ protected function getTableAlias(?string $currentTableAlias, string $relationPar public function builder(): Builder { if ($this->hasModel()) { - return $this->getModel()::query()->with($this->getRelationships()); + return $this->getModel()::query() + ->with($this->getRelationships()); } // If model does not exist diff --git a/src/Views/Columns/AggregateColumn.php b/src/Views/Columns/AggregateColumn.php new file mode 100644 index 000000000..a284b08ff --- /dev/null +++ b/src/Views/Columns/AggregateColumn.php @@ -0,0 +1,23 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/AvgColumn.php b/src/Views/Columns/AvgColumn.php new file mode 100644 index 000000000..3af197873 --- /dev/null +++ b/src/Views/Columns/AvgColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/CountColumn.php b/src/Views/Columns/CountColumn.php new file mode 100644 index 000000000..4a1ffd812 --- /dev/null +++ b/src/Views/Columns/CountColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Columns/SumColumn.php b/src/Views/Columns/SumColumn.php new file mode 100644 index 000000000..a01822d7b --- /dev/null +++ b/src/Views/Columns/SumColumn.php @@ -0,0 +1,18 @@ +label(fn () => null); + } +} diff --git a/src/Views/Traits/Configuration/AggregateColumnConfiguration.php b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php new file mode 100644 index 000000000..2f181ed2f --- /dev/null +++ b/src/Views/Traits/Configuration/AggregateColumnConfiguration.php @@ -0,0 +1,59 @@ +dataSource = $dataSource; + + if (isset($foreignColumn)) { + $this->setForeignColumn($foreignColumn); + } + + $this->setDefaultLabel(); + + return $this; + } + + public function setAggregateMethod(string $aggregateMethod): self + { + $this->aggregateMethod = $aggregateMethod; + $this->setDefaultLabel(); + + return $this; + } + + public function setForeignColumn(string $foreignColumn): self + { + $this->foreignColumn = $foreignColumn; + $this->setDefaultLabel(); + + return $this; + } + + public function setDefaultLabel(): void + { + $this->label(function ($row, Column $column) { + if ($this->hasForeignColumn()) { + return $row->{$this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn()}; + } + + return $row->{$this->getDataSource().'_'.$this->getAggregateMethod()}; + }); + + } + + public function sortable(?callable $callback = null): self + { + $this->sortable = true; + + $this->sortCallback = ($callback === null) ? ($this->hasForeignColumn() ? fn (Builder $query, string $direction) => $query->orderBy($this->getDataSource().'_'.$this->getAggregateMethod().'_'.$this->getForeignColumn(), $direction) : fn (Builder $query, string $direction) => $query->orderBy($this->dataSource.'_count', $direction)) : $callback; + + return $this; + } +} diff --git a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php index 72188fd79..c0f7657b7 100644 --- a/src/Views/Traits/Configuration/ArrayColumnConfiguration.php +++ b/src/Views/Traits/Configuration/ArrayColumnConfiguration.php @@ -26,4 +26,14 @@ public function outputFormat(callable $callable): self return $this; } + + /** + * Define the Empty Value to use for the Column + */ + public function emptyValue(string $emptyValue): self + { + $this->emptyValue = $emptyValue; + + return $this; + } } diff --git a/src/Views/Traits/Helpers/AggregateColumnHelpers.php b/src/Views/Traits/Helpers/AggregateColumnHelpers.php new file mode 100644 index 000000000..1748b8091 --- /dev/null +++ b/src/Views/Traits/Helpers/AggregateColumnHelpers.php @@ -0,0 +1,46 @@ +dataSource; + } + + public function hasDataSource(): bool + { + return isset($this->dataSource); + } + + public function getAggregateMethod(): string + { + return $this->aggregateMethod; + } + + public function hasForeignColumn(): bool + { + return isset($this->foreignColumn); + } + + public function getForeignColumn(): string + { + return $this->foreignColumn; + } + + public function getContents(Model $row): null|string|\BackedEnum|HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + if (! isset($this->dataSource)) { + throw new DataTableConfigurationException('You must specify a data source'); + } else { + return parent::getContents($row); + } + } +} diff --git a/src/Views/Traits/Helpers/ArrayColumnHelpers.php b/src/Views/Traits/Helpers/ArrayColumnHelpers.php index 7c32f7c70..8289038e4 100644 --- a/src/Views/Traits/Helpers/ArrayColumnHelpers.php +++ b/src/Views/Traits/Helpers/ArrayColumnHelpers.php @@ -48,10 +48,6 @@ public function getContents(Model $row): null|string|\BackedEnum|HtmlString|Data $outputValues = []; $value = $this->getValue($row); - if (! $this->hasSeparator()) { - throw new DataTableConfigurationException('You must set a valid separator on an ArrayColumn'); - } - if (! $this->hasDataCallback()) { throw new DataTableConfigurationException('You must set a data() method on an ArrayColumn'); } diff --git a/src/Views/Traits/IsAggregateColumn.php b/src/Views/Traits/IsAggregateColumn.php new file mode 100644 index 000000000..1824118b9 --- /dev/null +++ b/src/Views/Traits/IsAggregateColumn.php @@ -0,0 +1,14 @@ +setPrimaryKey('id'); + } + + public function columns(): array + { + return [ + Column::make('ID', 'id') + ->sortable() + ->setSortingPillTitle('Key') + ->setSortingPillDirections('0-9', '9-0'), + Column::make('Name') + ->sortable() + ->searchable(), + AvgColumn::make('Average Age') + ->setDataSource('pets', 'age'), + CountColumn::make('Number of Pets') + ->setDataSource('pets'), + SumColumn::make('Total Age') + ->setDataSource('pets', 'age'), + + ]; + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index fdcbd44a6..ab22227bd 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -9,7 +9,7 @@ use Livewire\LivewireServiceProvider; use Orchestra\Testbench\TestCase as Orchestra; use Rappasoft\LaravelLivewireTables\LaravelLivewireTablesServiceProvider; -use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated}; +use Rappasoft\LaravelLivewireTables\Tests\Http\Livewire\{PetsTable,PetsTableUnpaginated,SpeciesTable}; use Rappasoft\LaravelLivewireTables\Tests\Models\Breed; use Rappasoft\LaravelLivewireTables\Tests\Models\Pet; use Rappasoft\LaravelLivewireTables\Tests\Models\Species; @@ -19,6 +19,8 @@ class TestCase extends Orchestra { public PetsTable $basicTable; + public SpeciesTable $speciesTable; + public PetsTableUnpaginated $unpaginatedTable; /** @@ -76,7 +78,7 @@ protected function setUp(): void } $this->setupBasicTable(); $this->setupUnpaginatedTable(); - + $this->setupSpeciesTable(); } protected function setupBasicTable() @@ -95,6 +97,22 @@ protected function setupBasicTable() $this->basicTable->render(); } + protected function setupSpeciesTable() + { + $view = view('livewire-tables::datatable'); + $this->speciesTable = new SpeciesTable(); + $this->speciesTable->boot(); + $this->speciesTable->bootedComponentUtilities(); + $this->speciesTable->bootedWithData(); + $this->speciesTable->bootedWithColumns(); + $this->speciesTable->bootedWithColumnSelect(); + $this->speciesTable->bootedWithSecondaryHeader(); + $this->speciesTable->booted(); + $this->speciesTable->renderingWithData($view, []); + $this->speciesTable->renderingWithPagination($view, []); + $this->speciesTable->render(); + } + protected function setupUnpaginatedTable() { diff --git a/tests/Traits/Configuration/ComponentConfigurationTest.php b/tests/Traits/Configuration/ComponentConfigurationTest.php index 001cea2a0..3bed87443 100644 --- a/tests/Traits/Configuration/ComponentConfigurationTest.php +++ b/tests/Traits/Configuration/ComponentConfigurationTest.php @@ -300,4 +300,106 @@ public function test_can_set_hide_configurable_areas_when_reordering_status(): v $this->basicTable->setHideConfigurableAreasWhenReorderingStatus(true); } + + public function test_no_extra_withs_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_with(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWith('user'); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user'], $this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_withs(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWiths(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths()); + } + + public function test_can_set_extra_withs(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + $this->basicTable->addExtraWith('test'); + $this->assertSame(['test'], $this->basicTable->getExtraWiths()); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->basicTable->setExtraWiths(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWiths()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWiths()); + } + + public function test_no_extra_with_counts_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + } + + public function test_can_add_extra_with_count(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCount('users'); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['users'], $this->basicTable->getExtraWithCounts()); + } + + public function test_can_add_extra_with_counts(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCounts(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts()); + } + + public function test_can_set_extra_with_counts(): void + { + $this->assertFalse($this->basicTable->hasExtraWithCounts()); + $this->assertEmpty($this->basicTable->getExtraWithCounts()); + $this->basicTable->addExtraWithCount('test'); + $this->assertSame(['test'], $this->basicTable->getExtraWithCounts()); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->basicTable->setExtraWithCounts(['user', 'pets']); + $this->assertTrue($this->basicTable->hasExtraWithCounts()); + $this->assertSame(['user', 'pets'], $this->basicTable->getExtraWithCounts()); + } + + public function test_no_extra_with_sums_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWithSums()); + $this->assertEmpty($this->basicTable->getExtraWithSums()); + } + + public function test_can_add_extra_with_sum(): void + { + $this->assertFalse($this->basicTable->hasExtraWithSums()); + $this->assertEmpty($this->basicTable->getExtraWithSums()); + $this->basicTable->addExtraWithSum('users', 'age'); + $this->assertTrue($this->basicTable->hasExtraWithSums()); + $this->assertSame([['table' => 'users', 'field' => 'age']], $this->basicTable->getExtraWithSums()); + } + + public function test_no_extra_with_avgs_by_default(): void + { + $this->assertFalse($this->basicTable->hasExtraWiths()); + $this->assertEmpty($this->basicTable->getExtraWiths()); + } + + public function test_can_add_extra_with_avg(): void + { + $this->assertFalse($this->basicTable->hasExtraWithAvgs()); + $this->assertEmpty($this->basicTable->getExtraWithAvgs()); + $this->basicTable->addExtraWithAvg('user', 'age'); + $this->assertTrue($this->basicTable->hasExtraWithAvgs()); + $this->assertSame([['table' => 'user', 'field' => 'age']], $this->basicTable->getExtraWithAvgs()); + } } diff --git a/tests/Views/Columns/ArrayColumnTest.php b/tests/Views/Columns/ArrayColumnTest.php new file mode 100644 index 000000000..0eff2808b --- /dev/null +++ b/tests/Views/Columns/ArrayColumnTest.php @@ -0,0 +1,87 @@ +assertSame('Array Col', $column->getTitle()); + } + + public function test_can_set_the_separator(): void + { + $column = ArrayColumn::make('Array Col'); + + $this->assertSame('
', $column->getSeparator()); + $column->separator('

'); + $this->assertTrue($column->hasSeparator()); + + $this->assertSame('

', $column->getSeparator()); + } + + public function test_can_set_the_output_format(): void + { + $column = ArrayColumn::make('Array Col'); + + $this->assertNull($column->getOutputFormatCallback()); + $this->assertFalse($column->hasOutputFormatCallback()); + $column->outputFormat(fn ($index, $value) => "".$value->name.''); + $this->assertTrue($column->hasOutputFormatCallback()); + } + + public function test_requires_the_data_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_can_get_the_output_format_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + $this->assertNotNull($column->getDataCallback()); + + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_requires_the_output_format_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + } + + public function test_can_get_empty_value(): void + { + $column = ArrayColumn::make('Average Age') + ->separator('

') + ->data(fn ($value, $row) => ($row->pets)) + ->sortable(); + + $this->assertSame('', $column->getEmptyValue()); + $column->emptyValue('Unknown'); + $this->assertSame('Unknown', $column->getEmptyValue()); + + } +} diff --git a/tests/Views/Columns/AvgColumnTest.php b/tests/Views/Columns/AvgColumnTest.php new file mode 100644 index 000000000..f3657c710 --- /dev/null +++ b/tests/Views/Columns/AvgColumnTest.php @@ -0,0 +1,111 @@ +assertSame('Average Age', $column->getTitle()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + + $this->assertNotEmpty($column); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = AvgColumn::make('Average Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + $column->setForeignColumn('test'); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame('test', $column->getForeignColumn()); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void + { + $column = AvgColumn::make('Average Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertSame('avg', $column->getAggregateMethod()); + $column->setAggregateMethod('test_avg'); + $this->assertSame('test_avg', $column->getAggregateMethod()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_renders_correctly(string $relation_name, string $foreign_field): void + { + $rows = $this->speciesTable->getRows(); + $column = AvgColumn::make('Average Age') + ->setDataSource('pets', 'age'); + $contents = $column->getContents($rows->first()); + $this->assertSame('15', $contents); + $contents = $column->getContents($rows[2]); + $this->assertSame('6', $contents); + } +} diff --git a/tests/Views/Columns/CountColumnTest.php b/tests/Views/Columns/CountColumnTest.php new file mode 100644 index 000000000..6c3d1e772 --- /dev/null +++ b/tests/Views/Columns/CountColumnTest.php @@ -0,0 +1,50 @@ +assertSame('Total Users', $column->getTitle()); + } + + public function test_can_setup_column_correctly(): void + { + $column = CountColumn::make('Total Users') + ->setDataSource('users') + ->sortable(); + + $this->assertNotEmpty($column); + } + + public function test_can_not_skip_set_data_source(): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = CountColumn::make('Average Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + public function test_renders_correctly(): void + { + $rows = $this->speciesTable->getRows(); + $row1 = $rows->first(); + $column = CountColumn::make('Pets') + ->setDataSource('pets'); + $contents = $column->getContents($rows->first()); + $this->assertSame('2', $contents); + $contents = $column->getContents($rows->last()); + $this->assertSame('0', $contents); + } +} diff --git a/tests/Views/Columns/SumColumnTest.php b/tests/Views/Columns/SumColumnTest.php new file mode 100644 index 000000000..9d5cbff51 --- /dev/null +++ b/tests/Views/Columns/SumColumnTest.php @@ -0,0 +1,111 @@ +assertSame('Sum User Age', $column->getTitle()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_setup_column_correctly(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + + $this->assertNotEmpty($column); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_not_skip_set_data_source(string $relation_name, string $foreign_field): void + { + $this->expectException(DataTableConfigurationException::class); + + $column = SumColumn::make('Sum User Age') + ->sortable(); + $contents = $column->getContents(Pet::find(1)); + $this->assertNull($contents); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_set_foreign_column(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + $column->setForeignColumn('test'); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame('test', $column->getForeignColumn()); + + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_foreign_column(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_data_source_fields(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertTrue($column->hasDataSource()); + $this->assertSame($relation_name, $column->getDataSource()); + $this->assertTrue($column->hasForeignColumn()); + $this->assertSame($foreign_field, $column->getForeignColumn()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_can_get_aggregate_method(string $relation_name, string $foreign_field): void + { + $column = SumColumn::make('Sum User Age') + ->setDataSource($relation_name, $foreign_field) + ->sortable(); + $this->assertSame('sum', $column->getAggregateMethod()); + $column->setAggregateMethod('test_sum'); + $this->assertSame('test_sum', $column->getAggregateMethod()); + } + + #[DataProviderExternal(AggregateColumnProvider::class, 'relationshipProvider')] + public function test_renders_correctly(string $relation_name, string $foreign_field): void + { + $rows = $this->speciesTable->getRows(); + $column = SumColumn::make('Total Age') + ->setDataSource('pets', 'age'); + $contents = $column->getContents($rows->first()); + $this->assertSame('30', $contents); + $contents = $column->getContents($rows[2]); + $this->assertSame('12', $contents); + } +}