بنقرة واحدة
widgets
Create FilamentPHP v4 dashboard widgets - stats overviews, charts, and custom components
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
القائمة
Create FilamentPHP v4 dashboard widgets - stats overviews, charts, and custom components
التثبيت باستخدام Codex أو Claude انسخ هذا Prompt والصقه في Codex أو Claude أو مساعد آخر ليراجع صفحة Skill ويثبّتها لك.
استنادا إلى تصنيف SOC المهني
Analyze, test, and prepare apps for production with Pest and Playwright testing
This skill should be used when encountering errors during development, when the user mentions an error, when debugging issues, or when asked to "fix an error", "debug this", "why is this failing", "solve this error". Provides intelligent error recognition, solution lookup from past errors, and error logging for future reference.
This skill should be used when the user asks to "manage notes", "update vault", "add to obsidian", "document in vault", "search my notes", "find related notes", or when working with Obsidian vault files. Also triggers when discussing knowledge management, note organization, or when Claude Code auto-captures commits, tasks, or component creation.
This skill should be used when conducting PRD interviews, creating product requirements documents, planning new features, documenting bug fixes, or when using commands like "/prd-builder:prd", "/prd-builder:feature", "/prd-builder:bugfix", or "/prd-builder:refine". Provides comprehensive interview frameworks and question templates for building thorough PRDs.
Execute complete user flow testing with Playwright MCP, testing end-to-end journeys through the application
Systematically test all pages for errors, functionality, and proper rendering using Playwright MCP
| name | widgets |
| description | Create FilamentPHP v4 dashboard widgets - stats overviews, charts, and custom components |
This skill generates FilamentPHP v4 dashboard widgets including stats overview widgets, chart widgets, table widgets, and custom widgets.
CRITICAL: Before generating widgets, read:
/home/mwguerra/projects/mwguerra/claude-code-plugins/filament-specialist/skills/docs/references/widgets/# Basic widget
php artisan make:filament-widget StatsOverview
# Stats overview widget
php artisan make:filament-widget StatsOverview --stats-overview
# Chart widget
php artisan make:filament-widget RevenueChart --chart
# Table widget
php artisan make:filament-widget LatestOrders --table
# Resource widget
php artisan make:filament-widget PostStats --resource=PostResource
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;
class StatsOverview extends BaseWidget
{
protected static ?int $sort = 1;
protected function getStats(): array
{
return [
// Basic stat
Stat::make('Total Users', User::count())
->description('All registered users')
->descriptionIcon('heroicon-o-users')
->color('primary'),
// Stat with trend
Stat::make('New Users', User::whereMonth('created_at', now()->month)->count())
->description('32% increase')
->descriptionIcon('heroicon-m-arrow-trending-up')
->color('success')
->chart([7, 3, 4, 5, 6, 3, 5, 8]) // Sparkline data
->chartColor('success'),
// Stat with decrease trend
Stat::make('Bounce Rate', '21%')
->description('7% decrease')
->descriptionIcon('heroicon-m-arrow-trending-down')
->color('danger'),
// Revenue stat with formatting
Stat::make('Revenue', '$' . number_format(Order::sum('total'), 2))
->description('This month')
->descriptionIcon('heroicon-o-currency-dollar')
->color('success')
->chart([1200, 1400, 1100, 1800, 2200, 1900, 2400])
->chartColor('success'),
// Stat with extra info
Stat::make('Pending Orders', Order::where('status', 'pending')->count())
->description('Requires attention')
->descriptionIcon('heroicon-o-clock')
->color('warning')
->extraAttributes([
'class' => 'cursor-pointer',
'wire:click' => 'goToOrders',
]),
];
}
// Optional: Make stats live
protected static ?string $pollingInterval = '15s';
// Optional: Column span
protected int | string | array $columnSpan = 'full';
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Carbon;
class RevenueChart extends ChartWidget
{
protected static ?string $heading = 'Revenue';
protected static ?int $sort = 2;
protected int | string | array $columnSpan = 'full';
protected function getData(): array
{
$data = collect(range(1, 12))->map(function ($month) {
return Order::whereMonth('created_at', $month)
->whereYear('created_at', now()->year)
->sum('total');
});
return [
'datasets' => [
[
'label' => 'Revenue',
'data' => $data->values()->toArray(),
'borderColor' => '#10b981',
'backgroundColor' => 'rgba(16, 185, 129, 0.1)',
'fill' => true,
],
],
'labels' => ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],
];
}
protected function getType(): string
{
return 'line';
}
protected function getOptions(): array
{
return [
'plugins' => [
'legend' => [
'display' => false,
],
],
'scales' => [
'y' => [
'beginAtZero' => true,
'ticks' => [
'callback' => '(value) => "$" + value.toLocaleString()',
],
],
],
];
}
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Widgets\ChartWidget;
class OrdersPerCategory extends ChartWidget
{
protected static ?string $heading = 'Orders by Category';
protected static ?int $sort = 3;
protected function getData(): array
{
$categories = \App\Models\Category::withCount('orders')->get();
return [
'datasets' => [
[
'label' => 'Orders',
'data' => $categories->pluck('orders_count')->toArray(),
'backgroundColor' => [
'#3b82f6',
'#10b981',
'#f59e0b',
'#ef4444',
'#8b5cf6',
],
],
],
'labels' => $categories->pluck('name')->toArray(),
];
}
protected function getType(): string
{
return 'bar';
}
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Widgets\ChartWidget;
class OrderStatusChart extends ChartWidget
{
protected static ?string $heading = 'Order Status Distribution';
protected static ?int $sort = 4;
protected function getData(): array
{
$statuses = Order::selectRaw('status, COUNT(*) as count')
->groupBy('status')
->pluck('count', 'status');
return [
'datasets' => [
[
'data' => $statuses->values()->toArray(),
'backgroundColor' => [
'#f59e0b', // pending - warning
'#3b82f6', // processing - primary
'#10b981', // completed - success
'#ef4444', // cancelled - danger
],
],
],
'labels' => $statuses->keys()->map(fn ($s) => ucfirst($s))->toArray(),
];
}
protected function getType(): string
{
return 'doughnut'; // or 'pie'
}
protected function getOptions(): array
{
return [
'plugins' => [
'legend' => [
'position' => 'bottom',
],
],
];
}
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Forms\Components\DatePicker;
use Filament\Forms\Components\Select;
use Filament\Widgets\ChartWidget;
class FilterableRevenueChart extends ChartWidget
{
protected static ?string $heading = 'Revenue Over Time';
public ?string $filter = 'week';
protected function getFilters(): ?array
{
return [
'today' => 'Today',
'week' => 'Last 7 days',
'month' => 'This month',
'year' => 'This year',
];
}
protected function getData(): array
{
$data = match ($this->filter) {
'today' => $this->getTodayData(),
'week' => $this->getWeekData(),
'month' => $this->getMonthData(),
'year' => $this->getYearData(),
};
return [
'datasets' => [
[
'label' => 'Revenue',
'data' => $data['values'],
'borderColor' => '#3b82f6',
],
],
'labels' => $data['labels'],
];
}
protected function getType(): string
{
return 'line';
}
private function getTodayData(): array
{
// Implementation
}
private function getWeekData(): array
{
// Implementation
}
private function getMonthData(): array
{
// Implementation
}
private function getYearData(): array
{
// Implementation
}
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Order;
use Filament\Tables;
use Filament\Tables\Table;
use Filament\Widgets\TableWidget as BaseWidget;
class LatestOrders extends BaseWidget
{
protected static ?string $heading = 'Latest Orders';
protected static ?int $sort = 5;
protected int | string | array $columnSpan = 'full';
public function table(Table $table): Table
{
return $table
->query(
Order::query()
->latest()
->limit(10)
)
->columns([
Tables\Columns\TextColumn::make('number')
->searchable(),
Tables\Columns\TextColumn::make('customer.name')
->label('Customer')
->searchable(),
Tables\Columns\BadgeColumn::make('status')
->colors([
'warning' => 'pending',
'primary' => 'processing',
'success' => 'completed',
'danger' => 'cancelled',
]),
Tables\Columns\TextColumn::make('total')
->money('usd')
->sortable(),
Tables\Columns\TextColumn::make('created_at')
->dateTime()
->sortable(),
])
->actions([
Tables\Actions\Action::make('view')
->url(fn (Order $record): string => route('filament.admin.resources.orders.view', $record))
->icon('heroicon-o-eye'),
])
->paginated(false);
}
}
<?php
declare(strict_types=1);
namespace App\Filament\Widgets;
use App\Models\Task;
use Filament\Widgets\Widget;
class TasksWidget extends Widget
{
protected static string $view = 'filament.widgets.tasks-widget';
protected static ?int $sort = 6;
protected int | string | array $columnSpan = 1;
public array $tasks = [];
public function mount(): void
{
$this->tasks = Task::where('user_id', auth()->id())
->whereNull('completed_at')
->orderBy('due_date')
->limit(5)
->get()
->toArray();
}
public function completeTask(int $taskId): void
{
Task::find($taskId)->update(['completed_at' => now()]);
$this->mount(); // Refresh tasks
}
}
Blade view (resources/views/filament/widgets/tasks-widget.blade.php):
<x-filament-widgets::widget>
<x-filament::section>
<x-slot name="heading">
My Tasks
</x-slot>
<ul class="divide-y divide-gray-200 dark:divide-gray-700">
@forelse ($tasks as $task)
<li class="py-3 flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-900 dark:text-white">
{{ $task['title'] }}
</p>
<p class="text-xs text-gray-500">
Due: {{ \Carbon\Carbon::parse($task['due_date'])->format('M j, Y') }}
</p>
</div>
<x-filament::icon-button
icon="heroicon-o-check"
wire:click="completeTask({{ $task['id'] }})"
color="success"
/>
</li>
@empty
<li class="py-3 text-sm text-gray-500">
No pending tasks
</li>
@endforelse
</ul>
</x-filament::section>
</x-filament-widgets::widget>
// In AdminPanelProvider.php
->widgets([
Widgets\AccountWidget::class, // Default
Widgets\FilamentInfoWidget::class, // Default
\App\Filament\Widgets\StatsOverview::class,
\App\Filament\Widgets\RevenueChart::class,
\App\Filament\Widgets\LatestOrders::class,
])
// In resource class
public static function getWidgets(): array
{
return [
Widgets\PostStatsOverview::class,
];
}
// In ListRecords page
protected function getHeaderWidgets(): array
{
return [
Widgets\PostStatsOverview::class,
];
}
protected function getFooterWidgets(): array
{
return [
Widgets\RecentPosts::class,
];
}
class MyWidget extends Widget
{
// Sort order
protected static ?int $sort = 1;
// Column span (1, 2, 'full', or responsive array)
protected int | string | array $columnSpan = [
'md' => 2,
'xl' => 3,
];
// Polling interval
protected static ?string $pollingInterval = '10s';
// Visibility
public static function canView(): bool
{
return auth()->user()->isAdmin();
}
// Lazy loading
protected static bool $isLazy = true;
}
Generated widgets include: