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