Courses

Creating CRM with Filament 3: Step-By-Step

Customer View Page with Infolist

Summary of this lesson:
- Creating InfoList view page for customers
- Customizing information display with sections
- Adding pipeline stage history component
- Converting table row clicks to view instead of edit

Next, we want to build an info list (View) page for our Customers. This page has to display all the information we have on our Customer along with a list of Pipeline Status logs:

In this lesson, we will do the following:

  • Create an InfoList View
  • Update Table row click to point to the View page
  • Create a custom component to display the Pipeline Status logs

Creating InfoList Page

Let's get to work and create a new file for our View:

php artisan make:filament-page ViewCustomer --resource=CustomerResource --type=ViewRecord

This should create the following file:

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

namespace App\Filament\Resources\CustomerResource\Pages;
 
use App\Filament\Resources\CustomerResource;
use Filament\Resources\Pages\CreateRecord;
use Filament\Resources\Pages\ViewRecord;
 
class ViewCustomer extends ViewRecord
{
protected static string $resource = CustomerResource::class;
}

Once this is done, we can go ahead and create links to the View page:

app/Filament/Resources/CustomerResource.php

// ...
 
public static function table(Table $table): Table
{
return $table
->columns([
// ...
])
->filters([
//
])
->actions([
// ...
])
->recordUrl(function ($record) {
if ($record->trashed()) {
return null;
}
 
return Pages\EditCustomer::getUrl([$record->id]);
return Pages\ViewCustomer::getUrl([$record->id]);
})
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
 
public static function getPages(): array
{
return [
'index' => Pages\ListCustomers::route('/'),
'create' => Pages\CreateCustomer::route('/create'),
'edit' => Pages\EditCustomer::route('/{record}/edit'),
'view' => Pages\ViewCustomer::route('/{record}'),
];
}

Now you should be able to open the table and click on a row to view the Customer's details:

All of these fields were auto-guessed by Filament and displayed as is from the Form definition. We can customize them by using the infoList() method. Let's do that now.


Customizing the View Page

While the auto-guessed fields are great, we can customize them to our liking by simply defining the structure just like we do with Forms:

app/Filament/Resources/CustomerResource.php

use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\Section;
use Filament\Infolists\Components\TextEntry;
use Filament\Infolists\Components\ViewEntry;
use Filament\Infolists\Infolist;
use Filament\Support\Colors\Color;
 
// ...
 
public static function infoList(Infolist $infolist): Infolist
{
return $infolist
->schema([
Section::make('Personal Information')
->schema([
TextEntry::make('first_name'),
TextEntry::make('last_name'),
])
->columns(),
Section::make('Contact Information')
->schema([
TextEntry::make('email'),
TextEntry::make('phone_number'),
])
->columns(),
Section::make('Additional Details')
->schema([
TextEntry::make('description'),
]),
Section::make('Lead and Stage Information')
->schema([
TextEntry::make('leadSource.name'),
TextEntry::make('pipelineStage.name'),
])
->columns(),
Section::make('Pipeline Stage History and Notes')
->schema([
ViewEntry::make('pipelineStageLogs')
->label('')
->view('infolists.components.pipeline-stage-history-list')
])
->collapsible()
]);
}
 
// ...

If you try to load the page - you will see that we have an error:

This is due to us using ViewEntry and not having a defined view. Let's create one now:

resources/views/infolists/components/pipeline-stage-history-list.blade.php

<x-dynamic-component :component="$getEntryWrapperView()" :entry="$entry"
class="grid grid-cols-[--cols-default] fi-in-component-ctn gap-6">
@foreach($getState() as $pipelineLog)
<div class="mb-4">
<div class="">
<span class="font-bold">{{ $pipelineLog->user?->name ?? 'System' }}</span>, <span x-data="{}" x-tooltip="{
content: '{{ $pipelineLog->created_at }}',
theme: $store.theme,
}">{{ $pipelineLog->created_at->diffForHumans() }}</span>
</div>
<div class="">
<span class="font-bold">Pipeline Stage:</span> {{ $pipelineLog->pipelineStage->name }}
</div>
@if($pipelineLog->notes)
<div class="">
<span class="font-bold">Note:</span> {{ $pipelineLog->notes }}
</div>
@endif
</div>
@endforeach
</x-dynamic-component>

Now, if we reload the page, we should see a nicer design for our View plus Pipeline Stage History:

That's it! We have our View page ready.


We will create a Document Resource for our Customers in the next lesson.

Previous: SoftDeletes: Archive and Restore Customers
avatar

Hello there, i got this

foreach() argument must be of type array|object, null given

C:\laragon\www\crm\resources\views\infolists\components\pipeline-stage-history-list.blade.php: 2

avatar

Hi, this seems like a missed step somewhere. Could you add a bit more code on what you did or flare report share?

The error message says that there are no pipeline stages on your Customer, which is due to bad relationship name or a typo somewhere (could be missed code piece or anything else like that)

avatar
You can use Markdown
avatar

Having a problem if the customer has not been moved up from a lead and you click on that client in the list then you get the

foreach() argument must be of type array|object, null given Error

But if the customer has been moved up to say Contact Made you no longer get the error and the customer data is displayed

What can we do to fix this problem?

Also gitting a N+1 Error message but if you click on the "ok" button it puls up the page

I see that this problem is fixed in your github repository however.

avatar

The problem that you are describing is from misconfiguration. You have to create a customer with at least 1 pipeline stage and observer has to catch that it was there. This will create 1 entry and prevent this error.

What you might have done differently from this tutorial - skipped the fresh migration run after implementing the observer. Try to run php artisan migrate:fresh --seed and see if you can open customers view. If not, then simply add @if(count($getState()) > 0) around the foreach.

As for N+1 - could you expand where that happens? And does it still exist on the last lesson that we have?

avatar

hello for me It works with both modifications, the relationship in tables was wrong, which is solved with the refresh and correct functioning is ensured by testing if getState > 0

avatar
You can use Markdown
avatar
You can use Markdown