From 3a260dc55eb779dadd7b25b5b057c6b91093d47c Mon Sep 17 00:00:00 2001 From: Joe <104938042+lrljoe@users.noreply.github.com> Date: Thu, 11 Jul 2024 01:48:46 +0100 Subject: [PATCH] Development to Master (3.3.0) (#1764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Adjust for HTML Columns * Update ChangeLog and SP * fix: Apply cursor pointer only on clickable columns when using Bootst… (#1742) * Ensure HTML Columns Return HTML Correctly (#1737) * Adjust for HTML Columns * fix: Apply cursor pointer only on clickable columns when using Bootstrap --------- Co-authored-by: Joe <104938042+lrljoe@users.noreply.github.com> * Fix styling * Fix hide bulk actions when empty not reflecting in frontend (#1747) * Fix issue with Hide Bulk Actions When Empty not reflecting in frontend * Fix styling * Add development branch into tests --------- Co-authored-by: lrljoe * Change Return Type for attributes() to static (#1749) * Switch to using Composer\InstalledVersions for AboutCommand to reduce necessity to update ServiceProvider with each update (#1748) * Two improvements to improve typehinting, migrate to larastan/larastan, cleanup of test (#1750) * Add ArrayColumn (BETA) (#1751) * Add ArrayColumn * Fix styling --------- Co-authored-by: lrljoe * Always hide bulk actions option (#1752) * Add option to "Always Hide Bulk Actions" * Fix styling * Fix test function name clash --------- Co-authored-by: lrljoe * Optionally disable count for simple pagination (#1755) * Add option for setShouldRetrieveTotalItemCountStatus * Fix styling --------- Co-authored-by: lrljoe * Update ChangeLog For 3.2.8 Release (#1754) * Update ChangeLog for 3.2.8 * Add release date * Fix phpstan unescaped | * Fix missing typehints (#1757) * Add additional typehints * Fix styling * Add filterCollection typehint * Fix styling * trUrlCallback fixes * Use Collection rather than collect() helper * Fix styling * Add ignore for "Unable to resolve the template type" for Illuminate Collection, add typehint for empty * Add ignore for $model has no defined type (allows for non Eloquent Model to be used longer term) * Adjust concurrency * Adjust Test * Adjust Again * Adjust PHPStan * Add Max Parallel * Use v4 of checkout/cache * Run one at a time * Add Clear Cache Workflow * Fix * Migrate to v4 and adjust workflows * Adjust workflow run rules * Adjust Run-Tests to separate L10 and L11 jobs * Adjust run-tests * Adjust Test * Add Laravel matrix * Adjust Concurrency * Adjust * Adjust Pull Jobs to Match Push jobs --------- Co-authored-by: lrljoe * Add CountColumn, simpler adding of WithCounts, With (#1761) * Initial Commit * Adjust CountColumn * Add ExtraWiths * Add AggregateColumn * Add SumColumn * Update Docs - Add Column Types Section * Add exceptions for empty data source, add standard tests * Ensure pcov runs on push to master/development/develop * Update to use codecov v4 --------- Co-authored-by: lrljoe * Add Option to Retain Selected when Searching/Filtering (#1762) * Initial Commit for Retaining Selected * Update Test for Search/Filter --------- Co-authored-by: lrljoe * Add WireLink Column (#1763) * Add WireLinkColumn * Add Tests for WireLinkColumn --------- Co-authored-by: lrljoe * Fix styling --------- Co-authored-by: Matt Pickering Co-authored-by: lrljoe --- .github/workflows/run-tests-pcov-pull.yml | 9 +- .gitignore | 4 +- CHANGELOG.md | 4 + coverage.xml | 2894 ----------------- docs/bulk-actions/_index.md | 2 +- docs/bulk-actions/available-methods.md | 72 + docs/column-types/_index.md | 4 + docs/column-types/array_column.md | 22 + docs/column-types/avg_column.md | 14 + docs/column-types/boolean_columns.md | 81 + docs/column-types/button_group_column.md | 34 + docs/column-types/color_columns.md | 41 + docs/column-types/component_column.md | 28 + docs/column-types/count_column.md | 14 + docs/column-types/date_columns.md | 28 + docs/column-types/image_columns.md | 26 + docs/column-types/link_columns.md | 24 + .../column-types/livewire_component_column.md | 5 + docs/column-types/sum_column.md | 14 + docs/column-types/wire_link_column.md | 37 + docs/columns/other-column-types.md | 241 +- docs/examples/_index.md | 2 +- docs/filter-types/_index.md | 2 +- docs/filters/_index.md | 2 +- docs/footer/_index.md | 2 +- docs/misc/_index.md | 2 +- docs/pagination/_index.md | 2 +- docs/reordering/_index.md | 2 +- docs/rows/_index.md | 2 +- docs/search/_index.md | 2 +- docs/secondary-header/_index.md | 2 +- docs/sorting/_index.md | 2 +- .../includes/columns/wire-link.blade.php | 9 + src/Traits/ComponentUtilities.php | 8 + .../BulkActionsConfiguration.php | 42 + .../Configuration/ComponentConfiguration.php | 56 + src/Traits/Helpers/BulkActionsHelpers.php | 10 + src/Traits/Helpers/ColumnHelpers.php | 28 + src/Traits/Helpers/ComponentHelpers.php | 40 + src/Traits/WithBulkActions.php | 4 + src/Traits/WithData.php | 26 +- src/Traits/WithFilters.php | 8 +- src/Traits/WithSearch.php | 8 +- src/Views/Columns/AggregateColumn.php | 23 + src/Views/Columns/AvgColumn.php | 18 + src/Views/Columns/CountColumn.php | 18 + src/Views/Columns/SumColumn.php | 18 + src/Views/Columns/WireLinkColumn.php | 45 + .../AggregateColumnConfiguration.php | 59 + .../ArrayColumnConfiguration.php | 10 + .../WireLinkColumnConfiguration.php | 5 + src/Views/Traits/Core/HasActionCallback.php | 28 + src/Views/Traits/Core/HasConfirmation.php | 28 + .../Traits/Helpers/AggregateColumnHelpers.php | 46 + .../Traits/Helpers/ArrayColumnHelpers.php | 4 - .../Traits/Helpers/WireLinkColumnHelpers.php | 5 + src/Views/Traits/IsAggregateColumn.php | 14 + tests/Attributes/AggregateColumnProvider.php | 13 + tests/Http/Livewire/SpeciesTable.php | 38 + tests/TestCase.php | 22 +- .../ComponentConfigurationTest.php | 102 + .../Traits/Helpers/BulkActionsHelpersTest.php | 70 + tests/Views/Columns/ArrayColumnTest.php | 87 + tests/Views/Columns/AvgColumnTest.php | 111 + tests/Views/Columns/CountColumnTest.php | 50 + tests/Views/Columns/SumColumnTest.php | 111 + tests/Views/Columns/WireLinkColumnTest.php | 66 + 67 files changed, 1691 insertions(+), 3159 deletions(-) delete mode 100644 coverage.xml create mode 100644 docs/column-types/_index.md create mode 100644 docs/column-types/array_column.md create mode 100644 docs/column-types/avg_column.md create mode 100644 docs/column-types/boolean_columns.md create mode 100644 docs/column-types/button_group_column.md create mode 100644 docs/column-types/color_columns.md create mode 100644 docs/column-types/component_column.md create mode 100644 docs/column-types/count_column.md create mode 100644 docs/column-types/date_columns.md create mode 100644 docs/column-types/image_columns.md create mode 100644 docs/column-types/link_columns.md create mode 100644 docs/column-types/livewire_component_column.md create mode 100644 docs/column-types/sum_column.md create mode 100644 docs/column-types/wire_link_column.md create mode 100644 resources/views/includes/columns/wire-link.blade.php create mode 100644 src/Views/Columns/AggregateColumn.php create mode 100644 src/Views/Columns/AvgColumn.php create mode 100644 src/Views/Columns/CountColumn.php create mode 100644 src/Views/Columns/SumColumn.php create mode 100644 src/Views/Columns/WireLinkColumn.php create mode 100644 src/Views/Traits/Configuration/AggregateColumnConfiguration.php create mode 100644 src/Views/Traits/Configuration/WireLinkColumnConfiguration.php create mode 100644 src/Views/Traits/Core/HasActionCallback.php create mode 100644 src/Views/Traits/Core/HasConfirmation.php create mode 100644 src/Views/Traits/Helpers/AggregateColumnHelpers.php create mode 100644 src/Views/Traits/Helpers/WireLinkColumnHelpers.php create mode 100644 src/Views/Traits/IsAggregateColumn.php create mode 100644 tests/Attributes/AggregateColumnProvider.php create mode 100644 tests/Http/Livewire/SpeciesTable.php create mode 100644 tests/Views/Columns/ArrayColumnTest.php create mode 100644 tests/Views/Columns/AvgColumnTest.php create mode 100644 tests/Views/Columns/CountColumnTest.php create mode 100644 tests/Views/Columns/SumColumnTest.php create mode 100644 tests/Views/Columns/WireLinkColumnTest.php 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..940323134 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to `laravel-livewire-tables` will be documented in this file +## [v3.3.0] - 2024-07-12 +### 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/bulk-actions/available-methods.md b/docs/bulk-actions/available-methods.md index ad98e1999..512394c23 100644 --- a/docs/bulk-actions/available-methods.md +++ b/docs/bulk-actions/available-methods.md @@ -274,3 +274,75 @@ public function configure(): void $this->setShouldAlwaysHideBulkActionsDropdownOptionDisabled(); } ``` + + +## setClearSelectedOnSearch + +By default, any selected items for Bulk Actions are cleared upon searching. You may configure this behaviour here. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearch(true); +} +``` + + +## setClearSelectedOnSearchEnabled + +By default, any selected items for Bulk Actions are cleared upon searching. This enables this behaviour. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearchEnabled(); +} +``` + + +## setClearSelectedOnSearchDisabled + +By default, any selected items for Bulk Actions are cleared upon searching. This disables this behaviour, ensuring that selected items are retained after searching. + +```php +public function configure(): void +{ + $this->setClearSelectedOnSearchDisabled(); +} +``` + + +## setClearSelectedOnFilter + +By default, any selected items for Bulk Actions are cleared upon filtering. You may configure this behaviour here. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilter(true); +} +``` + + +## setClearSelectedOnFilterEnabled + +By default, any selected items for Bulk Actions are cleared upon filtering. This enables this behaviour. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilterEnabled(); +} +``` + + +## setClearSelectedOnFilterDisabled + +By default, any selected items for Bulk Actions are cleared upon filtering. This disables this behaviour, ensuring that selected items are retained after filtering. + +```php +public function configure(): void +{ + $this->setClearSelectedOnFilterDisabled(); +} +``` 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/column-types/wire_link_column.md b/docs/column-types/wire_link_column.md new file mode 100644 index 000000000..3577ad95b --- /dev/null +++ b/docs/column-types/wire_link_column.md @@ -0,0 +1,37 @@ +--- +title: Wire Link Column (beta) +weight: 13 +--- + +WireLink columns provide a way to display Wired Links in your table without having to use `format()` or partial views, with or without a Confirmation Message + +WireLinkColumn requires title, and an "action", which must be a valid LiveWire method in the current class, or a global method + +Without a Confirmation Message +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->action(fn($row) => 'delete("'.$row->id.'")'), +``` + +You may also pass a string to "confirmMessage", which will utilise LiveWire 3's "wire:confirm" approach to display a confirmation modal. + +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->confirmMessage('Are you sure you want to delete this item?') + ->action(fn($row) => 'delete("'.$row->id.'")') + ->attributes(fn($row) => [ + 'class' => 'btn btn-danger', + ]), +``` + +And you may also pass an array of attributes, which will be applied to the "button" element used within the Column +```php + WireLinkColumn::make("Delete Item") + ->title(fn($row) => 'Delete Item') + ->action(fn($row) => 'delete("'.$row->id.'")') + ->attributes(fn($row) => [ + 'class' => 'btn btn-danger', + ]), +``` 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/resources/views/includes/columns/wire-link.blade.php b/resources/views/includes/columns/wire-link.blade.php new file mode 100644 index 000000000..191ba5f1d --- /dev/null +++ b/resources/views/includes/columns/wire-link.blade.php @@ -0,0 +1,9 @@ + 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/BulkActionsConfiguration.php b/src/Traits/Configuration/BulkActionsConfiguration.php index d0d5e17f3..6af318f0e 100644 --- a/src/Traits/Configuration/BulkActionsConfiguration.php +++ b/src/Traits/Configuration/BulkActionsConfiguration.php @@ -171,4 +171,46 @@ public function setShouldAlwaysHideBulkActionsDropdownOptionDisabled(): self return $this; } + + public function setClearSelectedOnSearch(bool $status): self + { + $this->clearSelectedOnSearch = $status; + + return $this; + } + + public function setClearSelectedOnSearchEnabled(): self + { + $this->setClearSelectedOnSearch(true); + + return $this; + } + + public function setClearSelectedOnSearchDisabled(): self + { + $this->setClearSelectedOnSearch(false); + + return $this; + } + + public function setClearSelectedOnFilter(bool $status): self + { + $this->clearSelectedOnFilter = $status; + + return $this; + } + + public function setClearSelectedOnFilterEnabled(): self + { + $this->setClearSelectedOnFilter(true); + + return $this; + } + + public function setClearSelectedOnFilterDisabled(): self + { + $this->setClearSelectedOnFilter(false); + + return $this; + } } 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/BulkActionsHelpers.php b/src/Traits/Helpers/BulkActionsHelpers.php index 70ea427a5..77a263635 100644 --- a/src/Traits/Helpers/BulkActionsHelpers.php +++ b/src/Traits/Helpers/BulkActionsHelpers.php @@ -220,4 +220,14 @@ public function shouldAlwaysHideBulkActionsDropdownOption(): bool { return $this->alwaysHideBulkActionsDropdownOption ?? false; } + + public function getClearSelectedOnSearch(): bool + { + return $this->clearSelectedOnSearch ?? true; + } + + public function getClearSelectedOnFilter(): bool + { + return $this->clearSelectedOnFilter ?? true; + } } 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/WithBulkActions.php b/src/Traits/WithBulkActions.php index 08f69197b..37a5cd111 100644 --- a/src/Traits/WithBulkActions.php +++ b/src/Traits/WithBulkActions.php @@ -36,6 +36,10 @@ trait WithBulkActions protected bool $alwaysHideBulkActionsDropdownOption = false; + public bool $clearSelectedOnSearch = true; + + public bool $clearSelectedOnFilter = true; + public function bulkActions(): array { return property_exists($this, 'bulkActions') ? $this->bulkActions : []; 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/Traits/WithFilters.php b/src/Traits/WithFilters.php index 878c660ce..981d0e855 100644 --- a/src/Traits/WithFilters.php +++ b/src/Traits/WithFilters.php @@ -74,9 +74,11 @@ public function updatedFilterComponents(string|array|null $value, string $filter { $this->resetComputedPage(); - // Clear bulk actions on filter - $this->clearSelected(); - $this->setSelectAllDisabled(); + // Clear bulk actions on filter - if enabled + if ($this->getClearSelectedOnFilter()) { + $this->clearSelected(); + $this->setSelectAllDisabled(); + } // Clear filters on empty value $filter = $this->getFilterByKey($filterName); diff --git a/src/Traits/WithSearch.php b/src/Traits/WithSearch.php index 0ef402573..5d90a991f 100644 --- a/src/Traits/WithSearch.php +++ b/src/Traits/WithSearch.php @@ -70,9 +70,11 @@ public function updatedSearch(string|array|null $value): void { $this->resetComputedPage(); - // Clear bulk actions on search - $this->clearSelected(); - $this->setSelectAllDisabled(); + // Clear bulk actions on search - if enabled + if ($this->getClearSelectedOnSearch()) { + $this->clearSelected(); + $this->setSelectAllDisabled(); + } if (is_null($value) || $value === '') { $this->clearSearch(); 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/Columns/WireLinkColumn.php b/src/Views/Columns/WireLinkColumn.php new file mode 100644 index 000000000..343f28ef9 --- /dev/null +++ b/src/Views/Columns/WireLinkColumn.php @@ -0,0 +1,45 @@ +label(fn () => null); + } + + public function getContents(Model $row): null|string|\Illuminate\Support\HtmlString|DataTableConfigurationException|\Illuminate\Contracts\Foundation\Application|\Illuminate\Contracts\View\Factory|\Illuminate\Contracts\View\View + { + if (! $this->hasTitleCallback()) { + throw new DataTableConfigurationException('You must specify a title callback for a WireLink column.'); + } + + if (! $this->hasActionCallback()) { + throw new DataTableConfigurationException('You must specify an action callback for a WireLink column.'); + } + + return view($this->getView()) + ->withColumn($this) + ->withTitle(app()->call($this->getTitleCallback(), ['row' => $row])) + ->withPath(app()->call($this->getActionCallback(), ['row' => $row])) + ->withAttributes($this->hasAttributesCallback() ? app()->call($this->getAttributesCallback(), ['row' => $row]) : []); + } +} 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/Configuration/WireLinkColumnConfiguration.php b/src/Views/Traits/Configuration/WireLinkColumnConfiguration.php new file mode 100644 index 000000000..238b36fbf --- /dev/null +++ b/src/Views/Traits/Configuration/WireLinkColumnConfiguration.php @@ -0,0 +1,5 @@ +actionCallback = $callback; + + return $this; + } + + public function getActionCallback(): ?callable + { + return $this->actionCallback; + } + + public function hasActionCallback(): bool + { + return $this->actionCallback !== null; + } +} diff --git a/src/Views/Traits/Core/HasConfirmation.php b/src/Views/Traits/Core/HasConfirmation.php new file mode 100644 index 000000000..9a8597947 --- /dev/null +++ b/src/Views/Traits/Core/HasConfirmation.php @@ -0,0 +1,28 @@ +confirmMessage = $confirmMessage; + + return $this; + } + + public function hasConfirmMessage(): bool + { + return isset($this->confirmMessage); + } + + public function getConfirmMessage(): string + { + return $this->confirmMessage; + } +} 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/Helpers/WireLinkColumnHelpers.php b/src/Views/Traits/Helpers/WireLinkColumnHelpers.php new file mode 100644 index 000000000..f24c7e460 --- /dev/null +++ b/src/Views/Traits/Helpers/WireLinkColumnHelpers.php @@ -0,0 +1,5 @@ +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/Traits/Helpers/BulkActionsHelpersTest.php b/tests/Traits/Helpers/BulkActionsHelpersTest.php index bf4cc9e6e..5506fcb4b 100644 --- a/tests/Traits/Helpers/BulkActionsHelpersTest.php +++ b/tests/Traits/Helpers/BulkActionsHelpersTest.php @@ -207,4 +207,74 @@ public function test_bulk_actions_th_checkbox_attributes_returns_default_true_if { $this->assertSame(['default' => true], $this->basicTable->getBulkActionsThCheckboxAttributes()); } + + public function test_select_clears_by_default(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_not_clear_when_disabled(): void + { + $this->basicTable->setClearSelectedOnSearchDisabled(); + + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + } + + public function test_select_does_clear_when_enabled(): void + { + $this->basicTable->setClearSelectedOnSearchEnabled(); + + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setSearch('Anthony'); + $this->basicTable->updatedSearch('Anthony'); + + $this->assertSame([], $this->basicTable->getSelected()); + + } + + public function test_select_clears_by_default_when_filtering(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_clear_when_filtering_when_enabled(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + $this->basicTable->setClearSelectedOnFilterEnabled(); + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + $this->assertSame([], $this->basicTable->getSelected()); + } + + public function test_select_does_not_clear_when_filtering_when_disabled(): void + { + $this->basicTable->setSelected([1, 2, 3, 4, 5]); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + $this->basicTable->setClearSelectedOnFilterDisabled(); + $this->basicTable->setFilter('breed_id_filter', '2'); + $this->basicTable->updatedFilterComponents('2', 'breed_id_filter'); + $this->assertSame([1, 2, 3, 4, 5], $this->basicTable->getSelected()); + } } 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); + } +} diff --git a/tests/Views/Columns/WireLinkColumnTest.php b/tests/Views/Columns/WireLinkColumnTest.php new file mode 100644 index 000000000..ebe15d32b --- /dev/null +++ b/tests/Views/Columns/WireLinkColumnTest.php @@ -0,0 +1,66 @@ +assertSame('Name', $column->getTitle()); + } + + public function test_can_not_infer_field_name_from_title_if_no_from(): void + { + $column = WireLinkColumn::make('My Title'); + + $this->assertNull($column->getField()); + } + + public function test_can_not_render_field_if_no_title_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + + WireLinkColumn::make('Name')->getContents(Pet::find(1)); + } + + public function test_can_not_render_field_if_no_action_callback(): void + { + $this->expectException(DataTableConfigurationException::class); + + WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->getContents(Pet::find(1)); + } + + public function test_can_render_field_if_title_and_action_callback(): void + { + $column = WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->action(fn ($row) => 'delete("'.$row->id.'")')->getContents(Pet::find(1)); + + $this->assertNotEmpty($column); + } + + public function test_can_render_field_if_confirm_set(): void + { + $column = WireLinkColumn::make('Name')->title(fn ($row) => 'Edit')->action(fn ($row) => 'delete("'.$row->id.'")')->confirmMessage('Test')->getContents(Pet::find(1)); + + $this->assertNotEmpty($column); + } + + public function test_can_add_confirm_message(): void + { + $column = WireLinkColumn::make('Name', 'name'); + + $this->assertFalse($column->hasConfirmMessage()); + + $column->confirmMessage('Test'); + + $this->assertTrue($column->hasConfirmMessage()); + + $this->assertSame('Test', $column->getConfirmMessage()); + } +}