Courses

Creating CRM with Filament 3: Step-By-Step

Customers by Stage: Tabs with Numbers

Summary of this lesson:
- Creating dynamic tabs for each pipeline stage
- Adding customer count badges to tabs
- Implementing "All Customers" tab
- Adding filtering by pipeline stage

Since our Customer table can have thousands of entries - we need a way to filter them by something. In our case, we will create tabs to group them by their Pipeline Stage like this:

In this lesson, we will do the following:

  • Dynamically create tabs for each Pipeline Stage
  • Create a new tab called All to show all Customers
  • Add counters to each tab to show how many Customers are in each group

Let's get started!


Creating the Tabs

To make tabs, we will modify our List file:

app/Filament/Resources/CustomerResource/Pages/ListCustomers.php

use App\Models\Customer;
use App\Models\PipelineStage;
use Filament\Resources\Components\Tab;
 
// ...
 
class ListCustomers extends ListRecords
{
// ...
 
public function getTabs(): array
{
$tabs = [];
 
$tabs['all'] = Tab::make('All Customers')
->badge(Customer::count());
 
$pipelineStages = PipelineStage::orderBy('position')->withCount('customers')->get();
 
foreach ($pipelineStages as $pipelineStage) {
$tabs[str($pipelineStage->name)->slug()->toString()] = Tab::make($pipelineStage->name)
->badge($pipelineStage->customers_count)
->modifyQueryUsing(function ($query) use ($pipelineStage) {
return $query->where('pipeline_stage_id', $pipelineStage->id);
});
}
 
return $tabs;
}
}

Once this code is done, we should see tabs appearing above our table:

But what did we do here? Let's look at the code again with some comments:

public function getTabs(): array
{
$tabs = [];
 
// Adding `all` as our first tab
$tabs['all'] = Tab::make('All Customers')
// We will add a badge to show how many customers are in this tab
->badge(Customer::count());
 
// Load all Pipeline Stages
$pipelineStages = PipelineStage::orderBy('position')->withCount('customers')->get();
 
// Loop through each Pipeline Stage
foreach ($pipelineStages as $pipelineStage) {
// Add a tab for each Pipeline Stage
// Array index is going to be used in the URL as a slug, so we transform the name into a slug
$tabs[str($pipelineStage->name)->slug()->toString()] = Tab::make($pipelineStage->name)
// We will add a badge to show how many customers are in this tab
->badge($pipelineStage->customers_count)
// We will modify the query to only show customers in this Pipeline Stage
->modifyQueryUsing(function ($query) use ($pipelineStage) {
return $query->where('pipeline_stage_id', $pipelineStage->id);
});
}
 
return $tabs;
}

That's it! This is all we had to do for the tabs to work.


In the next lesson, we will add an ability to view archived Customers and restore them.

Previous: Moving Customers through Pipeline Stages
avatar

Thank you for the very useful tutorials. I have a question. How to control the visibility of a table column depending on the active tab?

avatar

I found a solution ->visible(fn ($livewire) => $livewire->activeTab !== 'all')

avatar

Get only the pipelines that have clients:

$pipelineStages = PipelineStage::whereHas('customers')
        ->orderBy('position')
        ->withCount('customers')
        ->get();
avatar
You can use Markdown
avatar

I found this redundant :

->modifyQueryUsing(function ($query) use ($pipelineStage) {
                    return $query->where('pipeline_stage_id', $pipelineStage->id);
                })
avatar

Why?

This applies the pipeline_stage_id filter on the selected tab :) Without it - it wouldn't filter the records once you click, no?

avatar

I think I've not understood what it does then because

$tabs[str($pipelineStage->name)->slug()->toString()] = Tab::make($pipelineStage->name)
                ->badge($pipelineStage->customers_count)

is returning the correct customer count for each pipeline stage anyway.

avatar

So there's a few functions that this does. Especially if we are talking about tags:

  1. The ->badge() just displays the amount of people with that status
  2. The ->modifyQueryUsing() allows you to click on the tab and filter records based on the filter

That way, you don't just get plain information, but also an additional filter on the table

avatar

Oh I see - this was new to me - thanks for the clarification.

avatar
You can use Markdown
avatar
You can use Markdown