DEV3 Technologies home

Filament order detail infolist with line items

A read-only order view for Filament 4 using an infolist: status badge, customer block, repeatable item lines and a bold money total.

FilamentLayoutsfilamentinfolistordersview-page

Code

<?php

namespace App\Filament\Resources\Orders\Schemas;

use Filament\Infolists\Components\RepeatableEntry;
use Filament\Infolists\Components\TextEntry;
use Filament\Schemas\Components\Grid;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Enums\FontWeight;
use Filament\Support\Enums\TextSize;

/** Call from the resource: public static function infolist(Schema $schema): Schema { return OrderInfolist::configure($schema); } */
class OrderInfolist
{
    public static function configure(Schema $schema): Schema
    {
        return $schema->components([
            Grid::make(3)->schema([
                Section::make('Order')
                    ->columnSpan(2)
                    ->columns(3)
                    ->schema([
                        TextEntry::make('number')->label('Order #')->copyable()->weight(FontWeight::SemiBold),
                        TextEntry::make('status')->badge()->color(fn (string $state): string => match ($state) {
                            'delivered' => 'success',
                            'cancelled', 'refunded' => 'danger',
                            'shipped' => 'info',
                            default => 'warning',
                        }),
                        TextEntry::make('placed_at')->dateTime('M j, Y H:i')->since()->tooltip(fn ($record) => $record->placed_at),
                    ]),

                Section::make('Customer')
                    ->schema([
                        TextEntry::make('customer.name'),
                        TextEntry::make('shipping_address')
                            ->state(fn ($record) => collect([$record->ship_line1, $record->ship_city, $record->ship_country])->filter()->implode(', '))
                            ->placeholder('No address'),
                    ]),
            ]),

            Section::make('Items')->schema([
                RepeatableEntry::make('items')
                    ->hiddenLabel()
                    ->columns(4)
                    ->schema([
                        TextEntry::make('product.name')->columnSpan(2),
                        TextEntry::make('quantity')->numeric(),
                        TextEntry::make('unit_price')->money('usd', divideBy: 100)->alignEnd(),
                    ]),
            ]),

            Section::make('Totals')->columns(3)->schema([
                TextEntry::make('subtotal')->money('usd', divideBy: 100),
                TextEntry::make('shipping_total')->label('Shipping')->money('usd', divideBy: 100),
                TextEntry::make('total')->money('usd', divideBy: 100)->size(TextSize::Large)->weight(FontWeight::Bold),
            ]),
        ]);
    }
}

Needs: filament/filament@^4.0

How it works

What it solves

Order screens are usually read much more than edited. A form-shaped view with disabled inputs looks wrong and wastes space; an infolist presents values as text, badges and tables, and can be reused for both a View page and a modal.

How it works

OrderInfolist::configure() builds a Schema of display components. A three-column Grid puts the order facts in a section that spans two columns and the customer block beside it. Entries read attributes and relations with dot notation, so customer.name and product.name need no custom code.

The status entry is a badge() whose colour comes from a match, so the meaning of colours lives next to the display. The timestamp shows a relative time with since() and the exact moment in a tooltip. The address is computed with state() from several columns, with a placeholder() for missing data.

RepeatableEntry loops over the items relation and renders each line using nested entries. Money is stored in cents and formatted with money('usd', divideBy: 100), and the grand total gets a larger size and bold weight.

Adapting it

Keep the class as a helper and call it from infolist() on the resource. Add actions in section headers, or relation managers below for payments and notes. Replace the currency string with a per-order currency column through a closure.

Pitfalls

  • Eager load items.product and customer in the resource's getEloquentQuery(), otherwise the repeatable entry issues one query per line.
  • Infolist entries are not editable. If you need inline edits, use a relation manager.
  • copyable() needs a secure context (HTTPS) in browsers.

How to use it

  1. Create a resource with a view page: php artisan make:filament-resource Order --view.
  2. Put OrderInfolist.php in the resource's Schemas folder and call it from the resource infolist() method.
  3. Make sure the model has customer() and items() relations, with product() on each item.
  4. Eager load them in getEloquentQuery().
  5. Adjust the field names and the currency to your schema.

Licence: DEV3 Snippet Licence. Free to use, change and ship in personal and commercial projects. Attribution is appreciated, not required. Please do not redistribute snippets as a collection.

Related snippets