#07 FilamentPHP基礎

カスタムページとウィジェット——ダッシュボードを作る

ウィジェットとカスタムページの概要

Filamentのダッシュボードはウィジェットの集まりです。ウィジェットにはKPIカードを表示する StatsOverviewWidget とグラフを表示する ChartWidget の2種類があります。

ダッシュボード
├── StatsOverviewWidget  → 数値KPIカード
├── ChartWidget          → 折れ線・棒・円グラフ
└── TableWidget          → 一覧テーブル(任意)

また make:filament-page で完全にカスタマイズされたページを作ることもできます。


StatsOverview ウィジェットを作る

php artisan make:filament-widget StatsOverview --stats-overview

生成されるファイル:app/Filament/Widgets/StatsOverview.php

<?php
// 📁 app/Filament/Widgets/StatsOverview.php

namespace App\Filament\Widgets;

use App\Models\Post;
use App\Models\Comment;
use App\Models\User;
use Filament\Widgets\StatsOverviewWidget as BaseWidget;
use Filament\Widgets\StatsOverviewWidget\Stat;

class StatsOverview extends BaseWidget
{
    // 自動更新の間隔(秒)。null で無効
    protected static ?string $pollingInterval = '15s';

    protected function getStats(): array
    {
        return [
            // 基本的なKPIカード
            Stat::make('記事数', Post::count())
                ->description('公開済み: ' . Post::where('is_published', true)->count() . '件')
                ->descriptionIcon('heroicon-o-document-text')
                ->color('primary'),

            // 増減を示すトレンド
            Stat::make('今月のコメント', Comment::whereMonth('created_at', now()->month)->count())
                ->description('先月比 +12%')
                ->descriptionIcon('heroicon-m-arrow-trending-up')
                ->color('success'),

            // チャートデータ付き(過去7日間の推移)
            Stat::make('登録ユーザー数', User::count())
                ->description('今週 +' . User::whereBetween('created_at', [now()->startOfWeek(), now()])->count() . '件')
                ->chart(
                    // 過去7日間の登録数グラフ
                    collect(range(6, 0))->map(
                        fn ($days) => User::whereDate('created_at', now()->subDays($days))->count()
                    )->toArray()
                )
                ->color('warning'),
        ];
    }
}

Stat のオプション

Stat::make('ラベル', '値')
    ->description('補足テキスト')           // 説明文
    ->descriptionIcon('heroicon-o-...')     // 説明横のアイコン
    ->color('success')                      // カードの色
    ->chart([7, 3, 4, 5, 6, 3, 5, 3])     // 小さなスパークラインチャート
    ->url(route('filament.admin.resources.posts.index'))  // クリックで遷移
    ->extraAttributes(['class' => 'cursor-pointer']),     // 追加HTMLクラス

ChartWidget を作る

php artisan make:filament-widget RevenueChart --chart

生成されるファイル:app/Filament/Widgets/RevenueChart.php

<?php
// 📁 app/Filament/Widgets/RevenueChart.php

namespace App\Filament\Widgets;

use App\Models\Post;
use Filament\Widgets\ChartWidget;
use Illuminate\Support\Carbon;

class RevenueChart extends ChartWidget
{
    protected static ?string $heading = '月別投稿数';

    // チャートの種類: line / bar / pie / doughnut / polarArea / radar
    protected static string $color = 'info';

    protected function getType(): string
    {
        return 'bar'; // 棒グラフ
    }

    protected function getData(): array
    {
        // 過去12か月のデータを取得
        $data = collect(range(11, 0))->map(function ($monthsAgo) {
            $date = now()->subMonths($monthsAgo);
            return Post::whereYear('created_at', $date->year)
                       ->whereMonth('created_at', $date->month)
                       ->count();
        });

        $labels = collect(range(11, 0))->map(
            fn ($monthsAgo) => now()->subMonths($monthsAgo)->format('Y/m')
        );

        return [
            'datasets' => [
                [
                    'label'           => '投稿数',
                    'data'            => $data->toArray(),
                    'backgroundColor' => 'rgba(59, 130, 246, 0.5)',
                    'borderColor'     => 'rgb(59, 130, 246)',
                    'borderWidth'     => 1,
                ],
            ],
            'labels' => $labels->toArray(),
        ];
    }
}

折れ線グラフ(LineChart)

protected function getType(): string
{
    return 'line';
}

protected function getData(): array
{
    $labels = collect(range(6, 0))->map(
        fn ($days) => now()->subDays($days)->format('m/d')
    );

    $published = collect(range(6, 0))->map(
        fn ($days) => Post::where('is_published', true)
            ->whereDate('created_at', now()->subDays($days))
            ->count()
    );

    $draft = collect(range(6, 0))->map(
        fn ($days) => Post::where('is_published', false)
            ->whereDate('created_at', now()->subDays($days))
            ->count()
    );

    return [
        'datasets' => [
            [
                'label'       => '公開済み',
                'data'        => $published->toArray(),
                'borderColor' => 'rgb(34, 197, 94)',
                'fill'        => false,
            ],
            [
                'label'       => '下書き',
                'data'        => $draft->toArray(),
                'borderColor' => 'rgb(156, 163, 175)',
                'fill'        => false,
            ],
        ],
        'labels' => $labels->toArray(),
    ];
}

円グラフ(PieChart)

protected function getType(): string
{
    return 'pie';
}

protected function getData(): array
{
    $published = Post::where('is_published', true)->count();
    $draft     = Post::where('is_published', false)->count();

    return [
        'datasets' => [
            [
                'data'            => [$published, $draft],
                'backgroundColor' => [
                    'rgb(34, 197, 94)',
                    'rgb(156, 163, 175)',
                ],
            ],
        ],
        'labels' => ['公開済み', '下書き'],
    ];
}

ウィジェットを表示順と幅を設定する

PanelProvider でウィジェットを登録

AdminPanelProvider.php->widgets() に追加します(discoverWidgets() を使っている場合は自動検出されます)。

// 📁 app/Providers/Filament/AdminPanelProvider.php
->widgets([
    Widgets\AccountWidget::class,
    Widgets\FilamentInfoWidget::class,
])

ウィジェット側でソート順を指定

class StatsOverview extends BaseWidget
{
    // 数値が小さいほど上に表示
    protected static ?int $sort = 1;
}

class RevenueChart extends ChartWidget
{
    protected static ?int $sort = 2;
}

ウィジェットの横幅

// full(全幅)/ half(半幅)/ デフォルトは自動
protected int | string | array $columnSpan = 'full';

// レスポンシブ設定
protected int | string | array $columnSpan = [
    'md' => 2,  // md以上で2カラム幅
    'xl' => 3,  // xl以上で3カラム幅
];

カスタムページを作る

完全にカスタマイズされたページを作成します。

php artisan make:filament-page Dashboard

生成されるファイル:

app/Filament/Pages/Dashboard.php
resources/views/filament/pages/dashboard.blade.php
<?php
// 📁 app/Filament/Pages/Dashboard.php

namespace App\Filament\Pages;

use App\Filament\Widgets\RevenueChart;
use App\Filament\Widgets\StatsOverview;
use Filament\Pages\Page;

class Dashboard extends Page
{
    protected static ?string $navigationIcon = 'heroicon-o-home';
    protected static ?string $navigationLabel = 'ダッシュボード';
    protected static ?string $title = 'ダッシュボード';
    protected static ?int $navigationSort = -2; // サイドバーの一番上

    // このページで表示するウィジェットクラス
    protected function getHeaderWidgets(): array
    {
        return [
            StatsOverview::class,
        ];
    }

    protected function getFooterWidgets(): array
    {
        return [
            RevenueChart::class,
        ];
    }
}

Bladeファイルは最小限でOKです。

{{-- 📁 resources/views/filament/pages/dashboard.blade.php --}}

<x-filament-panels::page>
    <p class="text-gray-500">
        管理画面へようこそ。最新の状況は上のウィジェットで確認できます。
    </p>
</x-filament-panels::page>

デフォルトのDashboardページをカスタマイズ

Filamentが提供するデフォルトのDashboardをカスタマイズする場合は、Filament\Pages\Dashboard を継承します。

<?php
// 📁 app/Filament/Pages/Dashboard.php

namespace App\Filament\Pages;

class Dashboard extends \Filament\Pages\Dashboard
{
    // ウィジェットの幅(カラム数)
    public function getColumns(): int | string | array
    {
        return 2;
    }

    // 表示するウィジェットを指定
    public function getWidgets(): array
    {
        return [
            \App\Filament\Widgets\StatsOverview::class,
            \App\Filament\Widgets\RevenueChart::class,
        ];
    }
}

ナビゲーションへのカスタムページ追加

$navigationIcon$navigationLabel を設定するだけで、サイドバーに自動追加されます。

class Reports extends Page
{
    protected static ?string $navigationIcon  = 'heroicon-o-chart-bar';
    protected static ?string $navigationLabel = 'レポート';
    protected static ?string $navigationGroup = '分析';  // グループに入れる
    protected static ?int    $navigationSort  = 1;
}

アクセス制限が必要な場合:

public static function canAccess(): bool
{
    return auth()->user()?->is_admin ?? false;
}

まとめ

  • php artisan make:filament-widget StatsOverview --stats-overview でKPIカードウィジェットを生成
  • Stat::make('ラベル', 値) でカードを追加、 ->chart() でスパークライン表示
  • php artisan make:filament-widget RevenueChart --chart でグラフウィジェットを生成
  • getType()line / bar / pie を指定
  • getData() でChart.js形式のデータを返す
  • protected static ?int $sort でウィジェットの表示順を制御
  • php artisan make:filament-page Dashboard でカスタムページを生成
  • getHeaderWidgets() / getFooterWidgets() でページ内のウィジェットを指定

次回は認証とアクセス制御を学びます。