OrderResource.php
TLDR
This file, located at app/Filament/Resources/Shop/OrderResource.php
, defines the OrderResource
class, which extends the Resource
class. The OrderResource
class is responsible for managing the "Order" resource in the Filament admin panel. It provides methods for handling forms, tables, relationships, widgets, pages, and navigation badges related to the "Order" resource.
Methods
form
This method returns a Form
instance representing the form for creating or editing an order. It defines the structure and behavior of the form fields.
table
This method returns a Table
instance representing the table for displaying a list of orders. It defines the columns, filters, actions, and groupings of the table.
getRelations
This method returns an array of relation manager classes for managing related models of the order resource.
getWidgets
This method returns an array of widget classes for displaying additional information or functionality related to the order resource.
getPages
This method returns an array of page classes for handling different pages related to the order resource, such as the index, create, and edit pages.
getEloquentQuery
This method overrides the parent Resource
class method to modify the query used to fetch order models from the database.
getGloballySearchableAttributes
This method returns an array of attribute names that can be globally searched for when using the search functionality in the admin panel.
getGlobalSearchResultDetails
This method returns an array of key-value pairs representing details about an order record for the global search functionality.
getGlobalSearchEloquentQuery
This method overrides the parent Resource
class method to modify the query used for global search.
getNavigationBadge
This method returns a string representing the badge count for the navigation icon of the order resource.
getDetailsFormSchema
This method returns an array of form components that define the structure and behavior of the details section of the order form.
getItemsRepeater
This method returns a Repeater
instance representing the repeater for adding and managing order items in the order form.
Classes
Class: OrderResource
This class represents the "Order" resource in the Filament admin panel. It extends the Resource
class and provides methods for handling forms, tables, relationships, widgets, pages, and navigation badges related to the "Order" resource.
<?php
namespace App\Filament\Resources\Shop;
use App\Enums\OrderStatus;
use App\Filament\Resources\Shop\OrderResource\Pages;
use App\Filament\Resources\Shop\OrderResource\RelationManagers;
use App\Filament\Resources\Shop\OrderResource\Widgets\OrderStats;
use App\Forms\Components\AddressForm;
use App\Models\Shop\Order;
use App\Models\Shop\Product;
use Filament\Forms;
use Filament\Forms\Components\Actions\Action;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Resource;
use Filament\Tables;
use Filament\Tables\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
use Illuminate\Support\Carbon;
use Squire\Models\Currency;
class OrderResource extends Resource
{
protected static ?string $model = Order::class;
protected static ?string $slug = 'shop/orders';
protected static ?string $recordTitleAttribute = 'number';
protected static ?string $navigationGroup = 'Shop';
protected static ?string $navigationIcon = 'heroicon-o-shopping-bag';
protected static ?int $navigationSort = 2;
public static function form(Form $form): Form
{
return $form
->schema([
Forms\Components\Group::make()
->schema([
Forms\Components\Section::make()
->schema(static::getDetailsFormSchema())
->columns(2),
Forms\Components\Section::make('Order items')
->headerActions([
Action::make('reset')
->modalHeading('Are you sure?')
->modalDescription('All existing items will be removed from the order.')
->requiresConfirmation()
->color('danger')
->action(fn (Forms\Set $set) => $set('items', [])),
])
->schema([
static::getItemsRepeater(),
]),
])
->columnSpan(['lg' => fn (?Order $record) => $record === null ? 3 : 2]),
Forms\Components\Section::make()
->schema([
Forms\Components\Placeholder::make('created_at')
->label('Created at')
->content(fn (Order $record): ?string => $record->created_at?->diffForHumans()),
Forms\Components\Placeholder::make('updated_at')
->label('Last modified at')
->content(fn (Order $record): ?string => $record->updated_at?->diffForHumans()),
])
->columnSpan(['lg' => 1])
->hidden(fn (?Order $record) => $record === null),
])
->columns(3);
}
public static function table(Table $table): Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('number')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('customer.name')
->searchable()
->sortable()
->toggleable(),
Tables\Columns\TextColumn::make('status')
->badge(),
Tables\Columns\TextColumn::make('currency')
->getStateUsing(fn ($record): ?string => Currency::find($record->currency)?->name ?? null)
->searchable()
->sortable()
->toggleable(),
Tables\Columns\TextColumn::make('total_price')
->searchable()
->sortable()
->summarize([
Tables\Columns\Summarizers\Sum::make()
->money(),
]),
Tables\Columns\TextColumn::make('shipping_price')
->label('Shipping cost')
->searchable()
->sortable()
->toggleable()
->summarize([
Tables\Columns\Summarizers\Sum::make()
->money(),
]),
Tables\Columns\TextColumn::make('created_at')
->label('Order Date')
->date()
->toggleable(),
])
->filters([
Tables\Filters\TrashedFilter::make(),
Tables\Filters\Filter::make('created_at')
->form([
Forms\Components\DatePicker::make('created_from')
->placeholder(fn ($state): string => 'Dec 18, ' . now()->subYear()->format('Y')),
Forms\Components\DatePicker::make('created_until')
->placeholder(fn ($state): string => now()->format('M d, Y')),
])
->query(function (Builder $query, array $data): Builder {
return $query
->when(
$data['created_from'] ?? null,
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '>=', $date),
)
->when(
$data['created_until'] ?? null,
fn (Builder $query, $date): Builder => $query->whereDate('created_at', '<=', $date),
);
})
->indicateUsing(function (array $data): array {
$indicators = [];
if ($data['created_from'] ?? null) {
$indicators['created_from'] = 'Order from ' . Carbon::parse($data['created_from'])->toFormattedDateString();
}
if ($data['created_until'] ?? null) {
$indicators['created_until'] = 'Order until ' . Carbon::parse($data['created_until'])->toFormattedDateString();
}
return $indicators;
}),
])
->actions([
Tables\Actions\EditAction::make(),
])
->groupedBulkActions([
Tables\Actions\DeleteBulkAction::make()
->action(function () {
Notification::make()
->title('Now, now, don\'t be cheeky, leave some records for others to play with!')
->warning()
->send();
}),
])
->groups([
Tables\Grouping\Group::make('created_at')
->label('Order Date')
->date()
->collapsible(),
]);
}
public static function getRelations(): array
{
return [
RelationManagers\PaymentsRelationManager::class,
];
}
public static function getWidgets(): array
{
return [
OrderStats::class,
];
}
public static function getPages(): array
{
return [
'index' => Pages\ListOrders::route('/'),
'create' => Pages\CreateOrder::route('/create'),
'edit' => Pages\EditOrder::route('/{record}/edit'),
];
}
public static function getEloquentQuery(): Builder
{
return parent::getEloquentQuery()->withoutGlobalScope(SoftDeletingScope::class);
}
public static function getGloballySearchableAttributes(): array
{
return ['number', 'customer.name'];
}
public static function getGlobalSearchResultDetails(Model $record): array
{
/** @var Order $record */
return [
'Customer' => optional($record->customer)->name,
];
}
public static function getGlobalSearchEloquentQuery(): Builder
{
return parent::getGlobalSearchEloquentQuery()->with(['customer', 'items']);
}
public static function getNavigationBadge(): ?string
{
return static::$model::where('status', 'new')->count();
}
public static function getDetailsFormSchema(): array
{
return [
Forms\Components\TextInput::make('number')
->default('OR-' . random_int(100000, 999999))
->disabled()
->dehydrated()
->required()
->maxLength(32)
->unique(Order::class, 'number', ignoreRecord: true),
Forms\Components\Select::make('shop_customer_id')
->relationship('customer', 'name')
->searchable()
->required()
->createOptionForm([
Forms\Components\TextInput::make('name')
->required()
->maxLength(255),
Forms\Components\TextInput::make('email')
->label('Email address')
->required()
->email()
->maxLength(255)
->unique(),
Forms\Components\TextInput::make('phone')
->maxLength(255),
Forms\Components\Select::make('gender')
->placeholder('Select gender')
->options([
'male' => 'Male',
'female' => 'Female',
])
->required()
->native(false),
])
->createOptionAction(function (Action $action) {
return $action
->modalHeading('Create customer')
->modalButton('Create customer')
->modalWidth('lg');
}),
Forms\Components\Select::make('status')
->options(OrderStatus::class)
->required()
->native(false),
Forms\Components\Select::make('currency')
->searchable()
->getSearchResultsUsing(fn (string $query) => Currency::where('name', 'like', "%{$query}%")->pluck('name', 'id'))
->getOptionLabelUsing(fn ($value): ?string => Currency::find($value)?->getAttribute('name'))
->required(),
AddressForm::make('address')
->columnSpan('full'),
Forms\Components\MarkdownEditor::make('notes')
->columnSpan('full'),
];
}
public static function getItemsRepeater(): Repeater
{
return Repeater::make('items')
->relationship()
->schema([
Forms\Components\Select::make('shop_product_id')
->label('Product')
->options(Product::query()->pluck('name', 'id'))
->required()
->reactive()
->afterStateUpdated(fn ($state, Forms\Set $set) => $set('unit_price', Product::find($state)?->price ?? 0))
->distinct()
->disableOptionsWhenSelectedInSiblingRepeaterItems()
->columnSpan([
'md' => 5,
])
->searchable(),
Forms\Components\TextInput::make('qty')
->label('Quantity')
->numeric()
->default(1)
->columnSpan([
'md' => 2,
])
->required(),
Forms\Components\TextInput::make('unit_price')
->label('Unit Price')
->disabled()
->dehydrated()
->numeric()
->required()
->columnSpan([
'md' => 3,
]),
])
->extraItemActions([
Action::make('openProduct')
->tooltip('Open product')
->icon('heroicon-m-arrow-top-right-on-square')
->url(function (array $arguments, Repeater $component): ?string {
$itemData = $component->getRawItemState($arguments['item']);
$product = Product::find($itemData['shop_product_id']);
if (! $product) {
return null;
}
return ProductResource::getUrl('edit', ['record' => $product]);
}, shouldOpenInNewTab: true)
->hidden(fn (array $arguments, Repeater $component): bool => blank($component->getRawItemState($arguments['item'])['shop_product_id'])),
])
->orderColumn('sort')
->defaultItems(1)
->hiddenLabel()
->columns([
'md' => 10,
])
->required();
}
}