Skip to content

Example: Customer Resource (CRUD)

A complete CRUD resource with sections, lookups, drill-downs, and validation.

php
<?php

namespace App\TwoWee\Resources;

use App\Models\Customer;
use App\Models\CustomerLedgerEntry;
use TwoWee\Laravel\Columns\BooleanColumn;
use TwoWee\Laravel\Columns\DecimalColumn;
use TwoWee\Laravel\Columns\TextColumn;
use TwoWee\Laravel\Enums\Color;
use TwoWee\Laravel\Fields\Boolean;
use TwoWee\Laravel\Fields\Decimal;
use TwoWee\Laravel\Fields\Email;
use TwoWee\Laravel\Fields\Integer;
use TwoWee\Laravel\Fields\Phone;
use TwoWee\Laravel\Fields\Text;
use TwoWee\Laravel\Fields\TextArea;
use TwoWee\Laravel\Resource;
use TwoWee\Laravel\Section;

class CustomerResource extends Resource
{
    protected static string $model = Customer::class;

    protected static ?string $recordKey = 'no';

    protected static string $label = 'Customer';

    protected static ?string $slug = 'customers';

    protected static ?string $navigationGroup = 'Registry';

    protected static int $navigationSort = 1;

    public static function title($model): string
    {
        return 'Customer Card - ' . $model->no . ' ' . $model->name;
    }

    public static function form(): array
    {
        return [
            Section::make('General')
                ->left()
                ->fields([
                    Text::make('no')
                        ->label('No.')
                        ->width(20)
                        ->uppercase()
                        ->required()
                        ->disableOnUpdate()
                        ->blurValidate(),
                    Text::make('name')
                        ->label('Name')
                        ->width(40)
                        ->required()
                        ->minLength(3)
                        ->maxLength(100)
                        ->blurValidate(),
                    Text::make('address')->label('Address')->width(30),
                    Text::make('post_code')->label('Post Code')->width(10)->uppercase(),
                    Text::make('city')->label('City')->width(20),
                    Text::make('country_code')->label('Country')->width(10)->uppercase(),
                ]),

            Section::make('Contact')
                ->right()
                ->fields([
                    Email::make('email')
                        ->label('E-Mail')
                        ->width(40)
                        ->nullable()
                        ->email()
                        ->maxLength(100)
                        ->unique('customers', 'email')
                        ->blurValidate(),
                    Phone::make('phone')
                        ->label('Phone')
                        ->width(20)
                        ->nullable()
                        ->maxLength(30),
                ]),

            Section::make('Invoicing')
                ->left()->rowGroup(1)
                ->fields([
                    Text::make('currency_code')
                        ->label('Currency Code')
                        ->width(10)
                        ->uppercase()
                        ->nullable()
                        ->maxLength(3),
                    Integer::make('payment_terms_days')
                        ->label('Payment Terms (Days)')
                        ->width(10)
                        ->nullable()
                        ->min(0)
                        ->max(365),
                    Decimal::make('credit_limit')
                        ->label('Credit Limit')
                        ->width(15)
                        ->nullable()
                        ->min(0),
                    Boolean::make('blocked')
                        ->label('Blocked')
                        ->width(12)
                        ->trueLabel('Yes')
                        ->falseLabel('No')
                        ->trueColor(Color::Red)
                        ->falseColor(Color::Green),
                ]),

            Section::make('Statistics')
                ->right()->rowGroup(1)
                ->fields([
                    Decimal::make('balance')
                        ->label('Balance')
                        ->width(15)
                        ->disabled()
                        ->drillDown(CustomerLedgerEntry::class)
                        ->color(Color::Yellow)->bold(),
                    Decimal::make('total_invoiced')
                        ->label('Total Invoiced')
                        ->width(15)
                        ->disabled()
                        ->color(Color::Yellow)->bold(),
                ]),

            Section::make('Notes')
                ->left()->rowGroup(2)
                ->fields([
                    TextArea::make('notes')->label('Notes')->width(80),
                ]),
        ];
    }

    public static function table(): array
    {
        return [
            TextColumn::make('no')->label('No.')->width(10),
            TextColumn::make('name')->label('Name')->width('fill'),
            TextColumn::make('city')->label('City')->width(20),
            TextColumn::make('country_code')->label('Country')->width(8),
            TextColumn::make('currency_code')->label('Currency')->width(8),
            DecimalColumn::make('balance')->label('Balance')->width(15)->align('right'),
            DecimalColumn::make('credit_limit')->label('Credit Limit')->width(15)->align('right'),
            BooleanColumn::make('blocked')->label('Blocked')->width(10),
        ];
    }
}

Key Features Demonstrated

  • $recordKey = 'no' — URLs use the customer number, not the database ID
  • ->left() / ->right() — two-column layout; ->rowGroup(1) starts a second row of sections
  • ->disableOnUpdate() — No. is editable on create but locked once saved
  • ->blurValidate() — field validates as soon as the cursor leaves it
  • ->unique('customers', 'email') — server-side uniqueness check on blur
  • ->drillDown(CustomerLedgerEntry::class) — balance field opens a drill-down list
  • ->trueColor() / ->falseColor() — boolean renders in red/green depending on value