Extensões e Customizações
Aprenda a estender o Filament Core Starter Kit para atender suas necessidades específicas.
Adicionar Novos Módulos
Criar Módulo Customizado
Exemplo: Sistema de Tarefas (Tasks)
1. Criar Model e Migration
bash
php artisan make:model Task -mfsphp
// database/migrations/xxxx_create_tasks_table.php
Schema::create('tasks', function (Blueprint $table) {
$table->id();
$table->string('title');
$table->text('description')->nullable();
$table->foreignId('person_id')->constrained()->cascadeOnDelete();
$table->foreignId('assigned_to')->nullable()->constrained('users');
$table->enum('status', ['pending', 'in_progress', 'completed', 'cancelled']);
$table->enum('priority', ['low', 'medium', 'high', 'urgent']);
$table->timestamp('due_date')->nullable();
$table->timestamps();
$table->softDeletes();
});php
// app/Models/Task.php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
class Task extends Model
{
use SoftDeletes;
protected $fillable = [
'title',
'description',
'person_id',
'assigned_to',
'status',
'priority',
'due_date',
];
protected $casts = [
'due_date' => 'datetime',
];
public function person(): BelongsTo
{
return $this->belongsTo(Person::class);
}
public function assignedUser(): BelongsTo
{
return $this->belongsTo(User::class, 'assigned_to');
}
}2. Criar Filament Resource
bash
php artisan make:filament-resource Taskphp
// app/Filament/Resources/TaskResource.php
namespace App\Filament\Resources;
use Filament\Forms;
use Filament\Tables;
use Filament\Resources\Resource;
use App\Models\Task;
class TaskResource extends Resource
{
protected static ?string $model = Task::class;
protected static ?string $navigationIcon = 'heroicon-o-clipboard-document-list';
protected static ?string $navigationGroup = 'Gestão';
public static function form(Forms\Form $form): Forms\Form
{
return $form
->schema([
Forms\Components\Section::make('Informações da Tarefa')
->schema([
Forms\Components\TextInput::make('title')
->label('Título')
->required()
->maxLength(255),
Forms\Components\Textarea::make('description')
->label('Descrição')
->rows(4)
->columnSpanFull(),
Forms\Components\Select::make('person_id')
->label('Cliente')
->relationship('person', 'name')
->searchable()
->required(),
Forms\Components\Select::make('assigned_to')
->label('Atribuído a')
->relationship('assignedUser', 'name')
->searchable(),
Forms\Components\Select::make('priority')
->label('Prioridade')
->options([
'low' => 'Baixa',
'medium' => 'Média',
'high' => 'Alta',
'urgent' => 'Urgente',
])
->required()
->default('medium'),
Forms\Components\Select::make('status')
->label('Status')
->options([
'pending' => 'Pendente',
'in_progress' => 'Em Progresso',
'completed' => 'Concluída',
'cancelled' => 'Cancelada',
])
->required()
->default('pending'),
Forms\Components\DateTimePicker::make('due_date')
->label('Data de Vencimento')
->native(false),
])
->columns(2),
]);
}
public static function table(Tables\Table $table): Tables\Table
{
return $table
->columns([
Tables\Columns\TextColumn::make('title')
->label('Título')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('person.name')
->label('Cliente')
->searchable()
->sortable(),
Tables\Columns\TextColumn::make('assignedUser.name')
->label('Atribuído a')
->searchable(),
Tables\Columns\BadgeColumn::make('priority')
->label('Prioridade')
->colors([
'success' => 'low',
'warning' => 'medium',
'danger' => 'high',
'danger' => 'urgent',
]),
Tables\Columns\BadgeColumn::make('status')
->label('Status')
->colors([
'gray' => 'pending',
'warning' => 'in_progress',
'success' => 'completed',
'danger' => 'cancelled',
]),
Tables\Columns\TextColumn::make('due_date')
->label('Vencimento')
->dateTime('d/m/Y H:i')
->sortable(),
])
->filters([
Tables\Filters\SelectFilter::make('status')
->label('Status')
->options([
'pending' => 'Pendente',
'in_progress' => 'Em Progresso',
'completed' => 'Concluída',
'cancelled' => 'Cancelada',
]),
Tables\Filters\SelectFilter::make('priority')
->label('Prioridade')
->options([
'low' => 'Baixa',
'medium' => 'Média',
'high' => 'Alta',
'urgent' => 'Urgente',
]),
])
->actions([
Tables\Actions\EditAction::make(),
Tables\Actions\DeleteAction::make(),
])
->bulkActions([
Tables\Actions\BulkActionGroup::make([
Tables\Actions\DeleteBulkAction::make(),
]),
]);
}
public static function getPages(): array
{
return [
'index' => Pages\ListTasks::route('/'),
'create' => Pages\CreateTask::route('/create'),
'edit' => Pages\EditTask::route('/{record}/edit'),
];
}
}3. Criar Widget para Dashboard
bash
php artisan make:filament-widget TasksOverviewphp
// app/Filament/Widgets/TasksOverview.php
namespace App\Filament\Widgets;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
use App\Models\Task;
class TasksOverview extends BaseWidget
{
protected function getStats(): array
{
return [
Stat::make('Tarefas Pendentes', Task::where('status', 'pending')->count())
->description('Aguardando início')
->descriptionIcon('heroicon-m-clock')
->color('warning'),
Stat::make('Em Progresso', Task::where('status', 'in_progress')->count())
->description('Sendo executadas')
->descriptionIcon('heroicon-m-arrow-path')
->color('info'),
Stat::make('Concluídas', Task::where('status', 'completed')->count())
->description('Finalizadas')
->descriptionIcon('heroicon-m-check-circle')
->color('success'),
];
}
}4. Criar Eventos
php
// app/Events/TaskCreated.php
namespace App\Events;
use App\Models\Task;
use Illuminate\Foundation\Events\Dispatchable;
class TaskCreated
{
use Dispatchable;
public function __construct(
public readonly Task $task
) {}
}php
// app/Listeners/NotifyAssignedUser.php
namespace App\Listeners;
use App\Events\TaskCreated;
use Filament\Notifications\Notification;
class NotifyAssignedUser
{
public function handle(TaskCreated $event): void
{
if ($event->task->assignedUser) {
Notification::make()
->title('Nova tarefa atribuída')
->body("Tarefa: {$event->task->title}")
->sendToDatabase($event->task->assignedUser);
}
}
}Registrar no EventServiceProvider:
php
protected $listen = [
TaskCreated::class => [
NotifyAssignedUser::class,
],
];Customizar Módulos Existentes
Adicionar Campo ao People Resource
php
// app/Filament/Resources/PersonResource.php
public static function form(Forms\Form $form): Forms\Form
{
return $form
->schema([
// Campos existentes...
// Novo campo customizado
Forms\Components\Select::make('customer_type')
->label('Tipo de Cliente')
->options([
'vip' => 'VIP',
'regular' => 'Regular',
'premium' => 'Premium',
])
->default('regular'),
Forms\Components\TextInput::make('tax_id')
->label('Inscrição Estadual')
->mask('999.999.999.999')
->maxLength(15),
]);
}Não esqueça de adicionar a migration:
bash
php artisan make:migration add_custom_fields_to_people_tablephp
Schema::table('people', function (Blueprint $table) {
$table->string('customer_type')->default('regular');
$table->string('tax_id')->nullable();
});Sobrescrever Lógica de Invoice
php
// app/Models/Invoice.php
class Invoice extends BaseInvoice
{
// Sobrescrever cálculo de impostos
public function calculateTaxes(): array
{
$taxes = parent::calculateTaxes();
// Adicionar ISS para serviços
if ($this->type === 'service') {
$taxes['iss'] = [
'rate' => 5.00,
'value' => $this->subtotal * 0.05,
];
}
return $taxes;
}
}Integrar APIs Externas
Exemplo: Integração com API de CEP
bash
composer require eduardokum/laravel-correiosphp
// app/Services/AddressService.php
namespace App\Services;
use Eduardokum\LaravelCorreios\Consultas\CEP;
class AddressService
{
public function findByCep(string $cep): ?array
{
try {
$result = CEP::consulta($cep);
return [
'street' => $result->getLogradouro(),
'neighborhood' => $result->getBairro(),
'city' => $result->getCidade(),
'state' => $result->getUf(),
'zipcode' => $result->getCep(),
];
} catch (\Exception $e) {
return null;
}
}
}Usar no Form:
php
Forms\Components\TextInput::make('zipcode')
->label('CEP')
->mask('99999-999')
->reactive()
->afterStateUpdated(function ($state, callable $set) {
if (strlen($state) === 9) {
$service = new AddressService();
$address = $service->findByCep($state);
if ($address) {
$set('street', $address['street']);
$set('neighborhood', $address['neighborhood']);
$set('city', $address['city']);
$set('state', $address['state']);
}
}
}),Exemplo: Integração com Gateway de Pagamento
bash
composer require stripe/stripe-phpphp
// app/Services/PaymentService.php
namespace App\Services;
use Stripe\StripeClient;
use App\Models\Invoice;
class PaymentService
{
protected StripeClient $stripe;
public function __construct()
{
$this->stripe = new StripeClient(config('services.stripe.secret'));
}
public function createPaymentIntent(Invoice $invoice): string
{
$intent = $this->stripe->paymentIntents->create([
'amount' => $invoice->total * 100, // Centavos
'currency' => 'brl',
'metadata' => [
'invoice_id' => $invoice->id,
],
]);
return $intent->client_secret;
}
public function handleWebhook(array $payload): void
{
if ($payload['type'] === 'payment_intent.succeeded') {
$invoiceId = $payload['data']['object']['metadata']['invoice_id'];
$invoice = Invoice::find($invoiceId);
$invoice->markAsPaid();
}
}
}Criar Actions Customizadas
Action para Duplicar Fatura
php
// app/Filament/Resources/InvoiceResource.php
Tables\Actions\Action::make('duplicate')
->label('Duplicar')
->icon('heroicon-o-document-duplicate')
->action(function (Invoice $record) {
$newInvoice = $record->replicate();
$newInvoice->number = Invoice::generateNumber();
$newInvoice->status = 'draft';
$newInvoice->save();
// Duplicar itens
foreach ($record->items as $item) {
$newItem = $item->replicate();
$newItem->invoice_id = $newInvoice->id;
$newItem->save();
}
Notification::make()
->title('Fatura duplicada com sucesso')
->success()
->send();
return redirect()->route('filament.resources.invoices.edit', $newInvoice);
})
->requiresConfirmation(),Bulk Action Customizada
php
Tables\Actions\BulkAction::make('markAsPaid')
->label('Marcar como Pago')
->icon('heroicon-o-check-circle')
->action(function (Collection $records) {
$records->each->markAsPaid();
Notification::make()
->title("{$records->count()} faturas marcadas como pagas")
->success()
->send();
})
->requiresConfirmation()
->color('success'),Adicionar Middleware Customizado
bash
php artisan make:middleware CheckSubscriptionphp
// app/Http/Middleware/CheckSubscription.php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
class CheckSubscription
{
public function handle(Request $request, Closure $next)
{
$user = $request->user();
if (!$user->hasActiveSubscription()) {
return redirect()->route('subscription.expired');
}
return $next($request);
}
}Registrar no Kernel.php:
php
protected $routeMiddleware = [
// ...
'subscription' => \App\Http\Middleware\CheckSubscription::class,
];Aplicar ao Filament:
php
// app/Providers/FilamentServiceProvider.php
Filament::serving(function () {
Filament::registerMiddleware([
\App\Http\Middleware\CheckSubscription::class,
]);
});Criar Command Artisan Customizado
bash
php artisan make:command SendInvoiceRemindersphp
// app/Console/Commands/SendInvoiceReminders.php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use App\Models\Invoice;
use App\Models\Communication;
class SendInvoiceReminders extends Command
{
protected $signature = 'invoices:send-reminders';
protected $description = 'Enviar lembretes para faturas vencendo em 3 dias';
public function handle(): int
{
$invoices = Invoice::where('status', 'pending')
->whereBetween('due_date', [now(), now()->addDays(3)])
->get();
$this->info("Encontradas {$invoices->count()} faturas para lembrete");
foreach ($invoices as $invoice) {
Communication::create([
'person_id' => $invoice->person_id,
'channel' => 'email',
'subject' => 'Lembrete: Fatura vencendo',
'message' => "Sua fatura {$invoice->number} vence em 3 dias.",
])->send();
$this->info("Lembrete enviado para: {$invoice->person->name}");
}
return Command::SUCCESS;
}
}Agendar no Kernel.php:
php
protected function schedule(Schedule $schedule): void
{
$schedule->command('invoices:send-reminders')
->daily()
->at('09:00');
}Customizar Tema do Filament
Personalizar Cores
php
// config/filament.php
'theme' => [
'colors' => [
'primary' => '#6366f1', // Indigo
'secondary' => '#64748b', // Slate
'success' => '#22c55e', // Green
'warning' => '#f59e0b', // Amber
'danger' => '#ef4444', // Red
'info' => '#3b82f6', // Blue
],
],CSS Customizado
css
/* resources/css/filament.css */
:root {
--sidebar-width: 280px;
}
.filament-sidebar {
background: linear-gradient(180deg, #1e293b 0%, #0f172a 100%);
}
.filament-brand {
font-size: 1.5rem;
font-weight: 700;
background: linear-gradient(90deg, #6366f1, #8b5cf6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}Compilar:
bash
npm run buildPróximos Passos
- 🏗️ Revise a Estrutura do Projeto
- ⚙️ Configure Variáveis de Ambiente
- 🚀 Explore Funcionalidades Principais
- 🚢 Prepare o Deploy em Produção