Skip to content

HeaderLines Layout

HeaderLines is for document-style screens with a header (sections with fields) and an editable grid of line items — like sales orders, purchase orders, or invoices.

Setup

php
class SalesOrderResource extends Resource
{
    protected static string $model = \App\Models\SalesOrder::class;
    protected static string $label = 'Sales Order';

    public static function layout(): string
    {
        return 'HeaderLines';
    }

    public static function linesRelation(): ?string
    {
        return 'lines';   // Eloquent relationship name
    }

    public static function linesOverlayPct(): int
    {
        return 65;   // Grid overlay size (0-100)
    }

    public static function form(): array
    {
        return [
            Section::make('General')
                ->column(0)->rowGroup(0)
                ->fields([
                    Text::make('no')->label('No.')->width(20)->uppercase()->disabled(),
                    Text::make('customer_no')->label('Customer No.')->width(20)
                        ->uppercase()
                        ->lookup('customer_no', 'customer_name', validate: true),
                    Text::make('customer_name')->label('Customer Name')
                        ->width(30)->disabled(),
                ]),
        ];
    }

    public static function table(): array
    {
        return [
            TextColumn::make('no')->label('No.')->width(10),
            TextColumn::make('customer_name')->label('Customer')->width('fill'),
            DecimalColumn::make('total')->label('Total')->decimals(2)->width(15)->align('right'),
        ];
    }

    public static function lineColumns(): array
    {
        return [
            OptionColumn::make('type')->label('Type')
                ->options(['Item', 'Resource', 'Text'])
                ->editable()->width(10),
            TextColumn::make('no')->label('No.')
                ->editable()->width(15)
                ->lookup('line_item', 'no')
                ->autofill(['description']),
            TextColumn::make('description')->label('Description')
                ->editable()->width('fill'),
            DecimalColumn::make('quantity')->label('Qty')
                ->editable()->decimals(2)->width(10)->align('right'),
            DecimalColumn::make('unit_price')->label('Unit Price')
                ->editable()->decimals(2)->width(12)->align('right'),
            DecimalColumn::make('discount_pct')->label('Disc. %')
                ->editable()->decimals(1)->width(8)->align('right'),
            DecimalColumn::make('amount')->label('Amount')
                ->decimals(2)->width(15)->align('right'),
        ];
    }

    public static function totals(?\Illuminate\Database\Eloquent\Model $model = null): array
    {
        $total = $model?->lines?->sum('amount') ?? 0;

        return [
            ['label' => 'Total', 'value' => number_format($total, 2, '.', ''), 'source_column' => 'amount'],
        ];
    }
}

Overlay

The linesOverlayPct() method controls how much of the screen the grid takes. The default is 50. Override it when you want a different split:

php
public static function linesOverlayPct(): int
{
    return 65; // grid takes 65% of the screen
}
  • 0 = grid hidden, full header view
  • 50 = split view (default — omit the method to use this)
  • 100 = grid fills entire screen

The user toggles the overlay with Ctrl+L.

By default the overlay starts closed. Override linesOpen() to open it automatically when the screen loads:

php
public static function linesOpen(): bool
{
    return true;
}

Empty Trailing Row

The server always includes at least one empty trailing row in the grid, allowing the user to add new line items.

Saving

The client sends both changes (header fields) and lines (2D grid data) in the SaveChangeset. The server:

  1. Updates the header model with changes
  2. Deletes all existing line items
  3. Creates new line items from lines (skipping empty rows)
  4. Returns the refreshed ScreenContract