Sales Operations - Datatables for Laravel                  [               Datatables for Laravel  ](/) [ 1.0.5](https://laravel-datatables.com/release-notes "Click to view release notes")

  Toggle main menu         [Docs](https://laravel-datatables.com/docs) [Demos](/demos) [Blog](https://laravel-datatables.com/blog) [Pricing](/pricing)        Search  ⌘ K   [ Log in ](/login) 

  [Docs](https://laravel-datatables.com/docs) [Demos](/demos) [Blog](https://laravel-datatables.com/blog) [Pricing](/pricing) 

 Search docs and demos  

Type at least 2 characters to search.

    Demos     

### Use Cases

- [ Simple Datatable ](https://laravel-datatables.com/demos/use-case-simple)
- [ Products Datatable ](https://laravel-datatables.com/demos/use-case-products)
- [ Sales Operations ](https://laravel-datatables.com/demos/use-case-sales)

### Features

- [ Accessibility ](https://laravel-datatables.com/demos/features-accessibility)
- [ Advanced Filtering ](https://laravel-datatables.com/demos/features-advanced-filtering)
- [ Aggregate Totals ](https://laravel-datatables.com/demos/features-aggregate-totals)
- [ Column Reordering ](https://laravel-datatables.com/demos/features-column-reordering)
- [ Custom Controls ](https://laravel-datatables.com/demos/features-custom-controls)
- [ Directionality (LTR / RTL) ](https://laravel-datatables.com/demos/features-directionality)
- [ Export ](https://laravel-datatables.com/demos/features-export)
- [ Frozen Columns ](https://laravel-datatables.com/demos/features-frozen-columns)
- [ Frozen Header ](https://laravel-datatables.com/demos/features-frozen)
- [ Frozen Header &amp; Columns ](https://laravel-datatables.com/demos/features-frozen-both)
- [ Pagination ](https://laravel-datatables.com/demos/features-pagination)
- [ Responsive Mobile ](https://laravel-datatables.com/demos/features-responsive-mobile)
- [ Row Grouping ](https://laravel-datatables.com/demos/features-row-grouping)
- [ Row Selection &amp; Bulk Actions ](https://laravel-datatables.com/demos/features-row-selection-bulk-actions)

### Cell Renderer

- [ Action Buttons ](https://laravel-datatables.com/demos/cell-renderer-action-buttons)
- [ Avatar ](https://laravel-datatables.com/demos/cell-renderer-avatar)
- [ Checkmark ](https://laravel-datatables.com/demos/cell-renderer-checkmark)
- [ Color Sample ](https://laravel-datatables.com/demos/cell-renderer-color-sample)
- [ Email Link ](https://laravel-datatables.com/demos/cell-renderer-email)
- [ External Link ](https://laravel-datatables.com/demos/cell-renderer-external)
- [ Friendly Timestamp ](https://laravel-datatables.com/demos/cell-renderer-friendly-timestamp)
- [ Image ](https://laravel-datatables.com/demos/cell-renderer-image)
- [ Long Text ](https://laravel-datatables.com/demos/cell-renderer-long-text)
- [ Phone Link ](https://laravel-datatables.com/demos/cell-renderer-phone-link)
- [ Progress Bar ](https://laravel-datatables.com/demos/cell-renderer-progress-bar)
- [ Status Badge ](https://laravel-datatables.com/demos/cell-renderer-status-badge)
- [ URL Link ](https://laravel-datatables.com/demos/cell-renderer-url)
- [ Wire Link ](https://laravel-datatables.com/demos/cell-renderer-wire)

### Value Formatter

- [ Compact Number ](https://laravel-datatables.com/demos/value-formatter-compact-number)
- [ Currency ](https://laravel-datatables.com/demos/value-formatter-currency)
- [ Duration ](https://laravel-datatables.com/demos/value-formatter-duration)
- [ Friendly Filesize ](https://laravel-datatables.com/demos/value-formatter-friendly-filesize)
- [ Friendly Timestamp ](https://laravel-datatables.com/demos/value-formatter-friendly-timestamp)
- [ Locale Timestamp ](https://laravel-datatables.com/demos/value-formatter-locale-timestamp)
- [ Number ](https://laravel-datatables.com/demos/value-formatter-number)
- [ Percent ](https://laravel-datatables.com/demos/value-formatter-percent)

Sales Operations
================

The Sales operations datatable is a more advanced use case showcasing various features like exporting, filtering, row selection and multi-row actions as well as a few more.

As this is just a datatable with demo data the multi-row delete action is simulated here by tracking deleted row IDs in the `$hiddenRows` variable of the datatable. This allows us also to see a dataSource() method that adds additional query constraints to the Eloquent query such as adding a new data element 'units\_sold' on the fly and hiding any "deleted" rows.

The columns() method has also increased in complexity showcasing a custom formatter callback using an anonymous function to format the Status column values and rendering it using a [ StatusBadge ](https://laravel-datatables.com/docs/renderer-status-badge).

This use case also gives a simple example of how to implement custom bulk actions by defining the bulk actions as an array called `$bulkActions` and executing the selected bulk action in the onBulkAction() method, like exporting the selected data.

Note that although row actions have been enabled for view, edit and delete, their respective row action handling has not been implemented for this simple demo. If you want to learn more about custom (dynamic) action buttons and row action handling, check out the [ Actions and Forms ](https://laravel-datatables.com/docs/actions-and-forms) and [ Cell Renderer: Action Buttons ](https://laravel-datatables.com/docs/renderer-action-buttons) documentation.

          Items per page        Filters      Export        Manage Columns   

    10  Current    25    50    100    

     CSV   XLSX   PDF   

 ​

### Filter options

   Product Name      

  Category    All  Audio &amp; Headphones Automotive Parts &amp; Accessories Baby Gear &amp; Nursery Beauty &amp; Personal Care Bedding &amp; Bath Beverages &amp; Coffee Board Games &amp; Puzzles Books &amp; E-Books Cameras &amp; Photography Cleaning Supplies Collectibles &amp; Art Computers &amp; Laptops Crafts &amp; Sewing Cycling &amp; Bicycles Electronics Event &amp; Party Supplies Fitness &amp; Gym Equipment Footwear Furniture Groceries &amp; Gourmet Food Hair Care Handbags &amp; Luggage Health &amp; Wellness Home Appliances Home Decor Industrial &amp; Scientific Jewelry &amp; Watches Kids &amp; Baby Apparel Kitchen Appliances Lawn &amp; Garden Lighting &amp; Ceiling Fans Men's Apparel Movies &amp; Music Musical Instruments Outdoor &amp; Camping Pet Supplies Safety &amp; Security Products Skincare Smart Home Devices Smartphones &amp; Accessories Sports Goods Stationery &amp; Office Supplies Tools &amp; Home Improvement Toys &amp; Games Travel &amp; Adventure Gear Video Games &amp; Consoles Vitamins &amp; Supplements Wearable Technology Wine, Beer &amp; Spirits Women's Apparel     

 Clear all Done 

 ​

###  Manage Columns 

  Apply   Cancel  

        Loading... 

             ID                Product Name                Category                Status                Units Sold                Unit Price               Actions                        1       Cumque et ullam #1       Sports Goods       Active       915       $487.94       View Edit Delete

                  2       Nesciunt doloribus voluptatum #2       Home Appliances       Active       18       $375.07       View Edit Delete

                  3       Et ullam dignissimos #3       Crafts &amp; Sewing       Active       710       A$485.74       View Edit Delete

                  4       Exercitationem sapiente nihil #4       Groceries &amp; Gourmet Food       Active       836       CA$108.78       View Edit Delete

                  5       Vel tempore dolores #5       Toys &amp; Games       Active       140       CA$211.49       View Edit Delete

                  6       Qui consequatur sed #6       Wine, Beer &amp; Spirits       Active       1,000       £422.27       View Edit Delete

                  7       Sit quae occaecati #7       Outdoor &amp; Camping       Active       134       $374.05       View Edit Delete

                  8       Esse nihil quam #8       Event &amp; Party Supplies       Active       1,000       £437.97       View Edit Delete

                  9       Corporis dolorum est #9       Vitamins &amp; Supplements       Inactive       12       £144.12       View Edit Delete

                  10       Ut explicabo quia #10       Crafts &amp; Sewing       Inactive       307       A$364.24       View Edit Delete

        « Previous       Next »    

 1 - 10 of 1000

                    1       2       3       4       5       6       7       8       9       10        ...          99       100                 

     `namespace App\Livewire\Datatables;use App\Models\Product;use MorrowIt\LaravelDatatable\Http\Livewire\Datatable;use MorrowIt\LaravelDatatable\Contracts\DataSourceInterface;use MorrowIt\LaravelDatatable\DataSources\EloquentDataSource;use MorrowIt\LaravelDatatable\Config\Column;use MorrowIt\LaravelDatatable\Renderers\StatusBadge;use Symfony\Component\HttpFoundation\Response;class UseCaseSalesOperationsDatatable extends Datatable{    public string $tableId = 'use-case-sales-operations-table';    public bool $persistTableState = true;    public bool $enableColumnReordering = true;    public bool $freezeHeader = true;    public bool $enableRowSelection = true;    public bool $enableExports = true;    public bool $persistSelectedRows = false;    public int $freezeColumns = 1;    public bool $enableAdvancedFilters = true;    public string $actionButtonsVariant = 'verbal';    public array $bulkActions = [        'export-selected-rows' => 'Export selected rows',        'change-status' => 'Change status',        'delete-selected-rows' => 'Delete selected rows',    ];    public array $hiddenRows = [];    public array $statusOverrides = [];    public function dataSource(): DataSourceInterface    {        $query = Product::query()->withSum('sales as units_sold', 'units');        if (!empty($this->hiddenRows)) {            $query->whereNotIn('id', $this->hiddenRows);        }        return new EloquentDataSource($query);    }    public function columns(): array    {        $categories = Product::query()            ->select('category')            ->whereNotNull('category')            ->where('category', '!=', '')            ->distinct()            ->orderBy('category')            ->pluck('category', 'category')            ->toArray();        return [            Column::make('id', 'ID')                ->sortable(),            Column::make('name', 'Product Name')                ->searchable()                ->sortable()                ->filterable('text'),            Column::make('category', 'Category')                ->searchable()                ->sortable()                ->filterable('select', options: $categories),            Column::make('active', 'Status')                ->sortable()                ->format(function ($value, $row) {                    $id = (string) data_get($row, 'id');                    if (array_key_exists($id, $this->statusOverrides)) {                        return $this->statusOverrides[$id] ? 'Active' : 'Inactive';                    }                    return $value ? 'Active' : 'Inactive';                })                ->renderer(new StatusBadge()),            Column::make('units_sold', 'Units Sold')                ->sortable()                ->headerClasses('text-right')                ->cellClasses('text-right')                ->format(new \MorrowIt\LaravelDatatable\Formatters\CompactNumber()),            Column::make('price', 'Unit Price')                ->sortable()                ->headerClasses('text-right')                ->cellClasses('text-right')                ->format(new \MorrowIt\LaravelDatatable\Formatters\Currency(currencyCol: 'currency')),            Column::make('actions', 'Actions')                ->renderer(new ActionButtons(variant: $this->actionButtonsVariant))                ->toggleable(false),        ];    }    protected function onBulkAction(string $action, array $selectedRows): ?Response    {        $ids = array_values(array_map('intval', $selectedRows));        switch ($action) {            case 'export-selected-rows':                return $this->export('csv');                break;            case 'change-status':                foreach ($ids as $id) {                    $key = (string) $id;                    $current = $this->statusOverrides[$key] ?? (bool) Product::query()->whereKey($id)->value('active');                    $this->statusOverrides[$key] = !$current;                }                break;            case 'delete-selected-rows':                $this->hiddenRows = array_values(array_unique(array_merge($this->hiddenRows, $ids)));                break;        }        return null;    }}` 

```
namespace App\Livewire\Datatables;

use App\Models\Product;
use MorrowIt\LaravelDatatable\Http\Livewire\Datatable;
use MorrowIt\LaravelDatatable\Contracts\DataSourceInterface;
use MorrowIt\LaravelDatatable\DataSources\EloquentDataSource;
use MorrowIt\LaravelDatatable\Config\Column;
use MorrowIt\LaravelDatatable\Renderers\StatusBadge;
use Symfony\Component\HttpFoundation\Response;

class UseCaseSalesOperationsDatatable extends Datatable
{
    public string $tableId = 'use-case-sales-operations-table';
    public bool $persistTableState = true;
    public bool $enableColumnReordering = true;
    public bool $freezeHeader = true;
    public bool $enableRowSelection = true;
    public bool $enableExports = true;
    public bool $persistSelectedRows = false;
    public int $freezeColumns = 1;
    public bool $enableAdvancedFilters = true;
    public string $actionButtonsVariant = 'verbal';

    public array $bulkActions = [
        'export-selected-rows' => 'Export selected rows',
        'change-status' => 'Change status',
        'delete-selected-rows' => 'Delete selected rows',
    ];

    public array $hiddenRows = [];
    public array $statusOverrides = [];

    public function dataSource(): DataSourceInterface
    {
        $query = Product::query()->withSum('sales as units_sold', 'units');
        if (!empty($this->hiddenRows)) {
            $query->whereNotIn('id', $this->hiddenRows);
        }

        return new EloquentDataSource($query);
    }

    public function columns(): array
    {
        $categories = Product::query()
            ->select('category')
            ->whereNotNull('category')
            ->where('category', '!=', '')
            ->distinct()
            ->orderBy('category')
            ->pluck('category', 'category')
            ->toArray();

        return [
            Column::make('id', 'ID')
                ->sortable(),
            Column::make('name', 'Product Name')
                ->searchable()
                ->sortable()
                ->filterable('text'),
            Column::make('category', 'Category')
                ->searchable()
                ->sortable()
                ->filterable('select', options: $categories),
            Column::make('active', 'Status')
                ->sortable()
                ->format(function ($value, $row) {
                    $id = (string) data_get($row, 'id');

                    if (array_key_exists($id, $this->statusOverrides)) {
                        return $this->statusOverrides[$id] ? 'Active' : 'Inactive';
                    }

                    return $value ? 'Active' : 'Inactive';
                })
                ->renderer(new StatusBadge()),
            Column::make('units_sold', 'Units Sold')
                ->sortable()
                ->headerClasses('text-right')
                ->cellClasses('text-right')
                ->format(new \MorrowIt\LaravelDatatable\Formatters\CompactNumber()),
            Column::make('price', 'Unit Price')
                ->sortable()
                ->headerClasses('text-right')
                ->cellClasses('text-right')
                ->format(new \MorrowIt\LaravelDatatable\Formatters\Currency(currencyCol: 'currency')),
            Column::make('actions', 'Actions')
                ->renderer(new ActionButtons(variant: $this->actionButtonsVariant))
                ->toggleable(false),
        ];
    }

    protected function onBulkAction(string $action, array $selectedRows): ?Response
    {
        $ids = array_values(array_map('intval', $selectedRows));

        switch ($action) {
            case 'export-selected-rows':
                return $this->export('csv');
                break;

            case 'change-status':
                foreach ($ids as $id) {
                    $key = (string) $id;
                    $current = $this->statusOverrides[$key] ?? (bool) Product::query()->whereKey($id)->value('active');
                    $this->statusOverrides[$key] = !$current;
                }
                break;

            case 'delete-selected-rows':
                $this->hiddenRows = array_values(array_unique(array_merge($this->hiddenRows, $ids)));
                break;
        }

        return null;
    }
}
```

### Related Documentation

 [Creating a Table

Basics

Create your very first basic Datatables for Laravel datatable.](https://laravel-datatables.com/docs/basic-creating-a-table) [Displaying Columns

Basics

Display datatable columns and configure behavior, rendering, formatting and other properties.](https://laravel-datatables.com/docs/basic-displaying-columns) 

### Related Demos

 [Simple Datatable

Use Cases

Showcase of a Contact table that queries data using an Eloquent data source and a Contact model.](https://laravel-datatables.com/demos/use-case-simple) [Products Datatable

Use Cases

Showcase of a Products table that queries data using an Eloquent data source and a Product model.](https://laravel-datatables.com/demos/use-case-products) 

   © 2026 Copyright (C)  [ Morrow IT Consulting &amp; Services ](https://morrow-it.services)  

 [Terms of Service](https://laravel-datatables.com/legal/terms) [Privacy](https://laravel-datatables.com/legal/privacy) [Legal Disclosure](https://laravel-datatables.com/legal/disclosure)
