In Filament, adding a filter to dashboard widgets is pretty straightforward. But what if you want to have a filter that would update MULTIPLE widgets at once, similar to Google Analytics? We need to use events and Livewire properties for this. Let's take a look.
First, let's create the widgets. We will need three widgets:
php artisan make:filament-widget Filtersphp artisan make:filament-widget OrdersChart --chartphp artisan make:filament-widget UsersChart --chart
For the charts, I will be using the bar type.
Now, let's show date inputs in the Filters
widget with Filament Forms. Also, the Filters
widget needs to be full width and have the $sort = 1
to be on top.
app/Filament/Widgets/Filters.php:
use Filament\Forms\Form;use Filament\Forms\Components\Grid;use Filament\Forms\Contracts\HasForms;use Filament\Forms\Components\DatePicker;use Filament\Forms\Concerns\InteractsWithForms; class Filters extends Widget implements HasForms{ use InteractsWithForms; protected static string $view = 'filament.widgets.filters'; protected int | string | array $columnSpan = 'full'; protected static ?int $sort = 1; public ?array $data = []; public function form(Form $form): Form { return $form ->statePath('data') ->schema([ Grid::make() ->schema([ DatePicker::make('from'), DatePicker::make('to'), ]), ]); }}
resources/views/filament/widgets/filters.blade.php:
<x-filament-widgets::widget> <x-filament::section> {{ $this->form }} </x-filament::section></x-filament-widgets::widget>
In the dashboard, we now have a widget with two date inputs.
For the other two widgets, we will show a chart of two models, User
and Order
. These charts will show how many new records are created daily.
For passing data to the chart, we will use the package recommended by Filament flowframe/laravel-trend
.
composer require flowframe/laravel-trend
And for the charts.
app/Filament/Widgets/OrdersChart.php:
use App\Models\Order;use Flowframe\Trend\Trend;use Flowframe\Trend\TrendValue;use Filament\Widgets\ChartWidget; class OrdersChart extends ChartWidget{ protected static ?string $heading = 'Orders Chart'; protected static ?int $sort = 2; protected function getData(): array { $data = Trend::model(Order::class) ->between( start: now()->subWeek(), end: now(), ) ->perDay() ->count(); return [ 'datasets' => [ [ 'label' => 'Orders', 'data' => $data->map(fn (TrendValue $value) => $value->aggregate), ], ], 'labels' => $data->map(fn (TrendValue $value) => $value->date), ]; } protected function getType(): string { return 'bar'; }}
app/Filament/Widgets/UsersChart.php:
use App\Models\User;use Flowframe\Trend\Trend;use Flowframe\Trend\TrendValue;use Filament\Widgets\ChartWidget; class UsersChart extends ChartWidget{ protected static ?string $heading = 'Users Chart'; protected static ?int $sort = 3; protected function getData(): array { $data = Trend::model(User::class) ->between( start: now()->subWeek(), end: now(), ) ->perDay() ->count(); return [ 'datasets' => [ [ 'label' => 'Users', 'data' => $data->map(fn (TrendValue $value) => $value->aggregate), ], ], 'labels' => $data->map(fn (TrendValue $value) => $value->date), ]; } protected function getType(): string { return 'bar'; }}
We also have two charts in the dashboard showing data from the week to now.
So now, let's update the charts when a filter is selected. To achieve this, we will need to dispatch a Livewire event after the date is selected and listen for that event in both charts widgets.
app/Filament/Widgets/Filters.php:
class Filters extends Widget implements HasForms{ // ... public function form(Form $form): Form { return $form ->statePath('data') ->schema([ Grid::make() ->schema([ DatePicker::make('from') ->live() ->afterStateUpdated(fn (?string $state) => $this->dispatch('updateFromDate', from: $state)), DatePicker::make('to') ->live() ->afterStateUpdated(fn (?string $state) => $this->dispatch('updateToDate', to: $state)), ]), ]); }}
Now we need to listen for these two events in chart widgets and set its value to a public property.
app/Filament/Widgets/OrdersChart.php:
use Livewire\Attributes\On;use Illuminate\Support\Carbon; class OrdersChart extends ChartWidget{ protected static ?string $heading = 'Orders Chart'; protected static ?int $sort = 2; public Carbon $fromDate; public Carbon $toDate; // ... #[On('updateFromDate')] public function updateFromDate(string $from): void { $this->fromDate = Carbon::make($from); $this->updateChartData(); } #[On('updateToDate')] public function updateToDate(string $to): void { $this->toDate = Carbon::make($to); $this->updateChartData(); } }
app/Filament/Widgets/UsersChart.php:
use Livewire\Attributes\On;use Illuminate\Support\Carbon; class UsersChart extends ChartWidget{ protected static ?string $heading = 'Users Chart'; protected static ?int $sort = 3; public Carbon $fromDate; public Carbon $toDate; // ... #[On('updateFromDate')] public function updateFromDate(string $from): void { $this->fromDate = Carbon::make($from); $this->updateChartData(); } #[On('updateToDate')] public function updateToDate(string $to): void { $this->toDate = Carbon::make($to); $this->updateChartData(); } }
Because flowframe/laravel-trend
package accepts Carbon as a parameter and from the event date is sent as a string, we make it to a Carbon. And then, using the updateChartData
method, the chart is updated with the new data.
All that is left is to use these two properties in the getData
method.
app/Filament/Widgets/OrdersChart.php:
class OrdersChart extends ChartWidget{ // ... protected function getData(): array { $fromDate = $this->fromDate ??= now()->subWeek(); $toDate = $this->toDate ??= now(); $data = Trend::model(Order::class) ->between( start: now()->subWeek(), end: now(), start: $fromDate, end: $toDate, ) ->perDay() ->count(); return [ 'datasets' => [ [ 'label' => 'Orders', 'data' => $data->map(fn (TrendValue $value) => $value->aggregate), ], ], 'labels' => $data->map(fn (TrendValue $value) => $value->date), ]; } // ...}
app/Filament/Widgets/UsersChart.php:
class UsersChart extends ChartWidget{ // ... protected function getData(): array { $fromDate = $this->fromDate ??= now()->subWeek(); $toDate = $this->toDate ??= now(); $data = Trend::model(User::class) ->between( start: now()->subWeek(), end: now(), start: $fromDate, end: $toDate, ) ->perDay() ->count(); return [ 'datasets' => [ [ 'label' => 'Users', 'data' => $data->map(fn (TrendValue $value) => $value->aggregate), ], ], 'labels' => $data->map(fn (TrendValue $value) => $value->date), ]; } // ...}
Here, we just set a variable to the public property or a default value.
If you want more Filament examples, you can find more real-life projects on our FilamentExamples.com.