Dashboard
Dashboard adalah page pertama yang dibuka ketika user masuk ke panel. Dashboard merupakan PandaPanel\Pages\Page biasa, tetapi daftar widget-nya berasal dari widget registry milik panel, bukan dari daftar statis pada dirinya sendiri. Karena itu metadata, breadcrumbs, header actions, authorization, filter, dan lazy loading bekerja sama seperti pada page lain. Satu panel dapat memiliki lebih dari satu dashboard.
Default dashboard
Setiap panel mendapatkan PandaPanel\Pages\Dashboard pada path panel tanpa konfigurasi tambahan:
$panel->path('admin'); // GET /admin renders PandaPanel\Pages\Dashboardroute('panel.admin.dashboard', absolute: false); // '/admin'Dashboard menampilkan seluruh widget yang terdaftar pada panel dan boleh dilihat current user:
$panel->discoverWidgets(app_path('Panels/Admin/Widgets'));Mengganti dashboard
/** @param class-string<Page> $page */
public function dashboard(string $page): self
/** @return class-string<Page> */
public function getDashboard(): string2
3
4
5
use App\Panels\Admin\Pages\Overview;
$panel->dashboard(Overview::class);2
3
Yang diberikan adalah class Page, bukan component name, agar landing page mendapatkan metadata, authorization, dan widget handling yang sama seperti page lain. PanelDashboardController hanya menginstansiasi class tersebut lalu memanggil render().
Custom dashboard paling sederhana meng-extend dashboard bawaan dan menentukan widget miliknya sendiri:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Pages;
use App\Panels\Admin\Widgets\RecentUsers;
use App\Panels\Admin\Widgets\UserStats;
use PandaPanel\Pages\Dashboard;
use PandaPanel\Widgets\Widget;
final class Overview extends Dashboard
{
protected static ?string $title = 'Overview';
/** @return list<class-string<Widget>> */
public function widgets(): array
{
return [UserStats::class, RecentUsers::class];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Jika meng-extend Dashboard tetapi tidak meng-override widgets(), behavior registry tetap digunakan:
use PandaPanel\Core\PanelManager;
public function widgets(): array
{
return app(PanelManager::class)->widgets($this->panel())->all();
}2
3
4
5
6
Lebih dari satu dashboard
/** @param array<array-key, class-string<Page>> $pages */
public function dashboards(array $pages): self
/** @return list<class-string<Page>> */
public function getExtraDashboards(): array2
3
4
5
use App\Panels\Admin\Pages\AccountsDashboard;
use PandaPanel\Pages\Dashboard;
$panel->dashboards([
Dashboard::class, // the panel root, /admin
AccountsDashboard::class, // its own route, /admin/accounts
]);2
3
4
5
6
7
Entry pertama menjadi root panel — dashboards() mengatur dashboard() menggunakan entry tersebut — sedangkan sisanya didaftarkan sebagai page biasa di bawah prefix panel menggunakan path yang dihasilkan slug masing-masing. Array kosong diabaikan.
Semua dashboard tetap merupakan Page biasa. Masing-masing memiliki authorization, navigation entry, dan filter sendiri. Operations dashboard dan finance dashboard adalah dua page widget yang berbeda, bukan satu page dengan dropdown, karena keduanya menjawab pertanyaan berbeda dan dapat dibaca oleh kelompok user yang berbeda.
Extra dashboard dideduplikasi berdasarkan class karena dashboard yang juga berada di discovered path dapat ditemukan dua kali tetapi tetap harus menjadi satu page.
Contoh dashboard kedua secara lengkap
Berikut examples/app/Panels/Admin/Pages/AccountsDashboard.php:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Pages;
use App\Panels\Admin\Widgets\RecentUsers;
use App\Panels\Admin\Widgets\UserGrowth;
use App\Panels\Admin\Widgets\UserStats;
use BackedEnum;
use PandaPanel\Forms\Components\Select;
use PandaPanel\Forms\FormSchema;
use PandaPanel\Pages\Dashboard;
use PandaPanel\Widgets\Widget;
final class AccountsDashboard extends Dashboard
{
protected static ?string $title = 'Accounts';
protected static ?string $slug = 'accounts';
protected static ?string $navigationIcon = 'users';
protected static string|BackedEnum|null $navigationGroup = 'User Management';
protected static ?string $subheading = 'Sign-ups and verification at a glance.';
public function filterSchema(): FormSchema
{
return FormSchema::make()->schema([
Select::make('period')
->label('Period')
->options([
'month' => 'This month',
'quarter' => 'This quarter',
'year' => 'This year',
])
->default('month'),
]);
}
/** @return list<class-string<Widget>> */
public function widgets(): array
{
return [UserStats::class, UserGrowth::class, RecentUsers::class];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
Page tersebut tersedia di /admin/accounts dengan route name panel.admin.pages.accounts.
Fitur yang diberikan Dashboard
| Member | Value | Tujuan |
|---|---|---|
$title | 'Dashboard' | Page title dan heading. |
$slug | 'dashboard' | Suffix route name dan registry key. |
$component | 'panel/Dashboard' | Inertia component. |
$navigationIcon | 'layout-grid' | Icon sidebar. |
$shouldRegisterNavigation | true | Menentukan apakah dashboard ingin navigation entry. |
widgets() | widget registry milik panel | Dapat di-override. |
breadcrumbs() | [Breadcrumb::make('Dashboard')->current()] | Satu breadcrumb yang ditandai current. |
Fitur lain berasal dari Page: props(), headerActions(), filterSchema(), canAccess(), middleware(), dan routePath().
use PandaPanel\Support\Breadcrumb;
public function breadcrumbs(): array
{
return [
Breadcrumb::make('Dashboard')->url($this->dashboardUrl()),
Breadcrumb::make('Accounts')->current(),
];
}2
3
4
5
6
7
8
9
Urutan dan authorization widget
Daftar widget dashboard di-resolve oleh WidgetCollection::for(), yang:
- membuang widget ketika
canView()bernilai false — sebelumdata()dipanggil, sehingga widget yang tidak boleh dilihat tidak pernah menjalankan query; - mengurutkan sisanya berdasarkan
[Widget::sort(), Widget::id()]; - menserialisasi definition setiap widget, memasukkan
data()langsung untuk eager widget dan menundanya untuk lazy widget.
use PandaPanel\Widgets\StatsWidget;
final class UserStats extends StatsWidget
{
protected static int $sort = 0;
protected static int|string|array $columnSpan = 2;
protected static bool $lazy = false;
protected static ?int $pollingInterval = 60;
public static function canView(): bool
{
return auth()->user()?->is_admin === true;
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Registry panel (WidgetRegistry::all()) mengembalikan class name secara alfabetis; $sort yang benar-benar menentukan urutan di page, dengan widget id sebagai tiebreaker. Widget id harus unik di dalam satu panel — dua class dengan kebab-cased basename yang sama melempar exception saat registration.
Filter
Dashboard dapat mendeklarasikan satu form filter yang dibaca seluruh widget di dalamnya:
public function filterSchema(): ?FormSchemaValue berasal dari query string, kemudian di-narrow oleh schema yang mendeklarasikannya, dan dipersist per page di bawah key panel.{panelId}.page.{slug}. Dua dashboard dengan filter berbeda adalah dua pertanyaan berbeda; memulihkan state satu dashboard ke dashboard lain akan menghasilkan jawaban yang salah untuk keduanya.
Widget juga dapat memiliki filter sendiri di atas filter dashboard, dan Widget::filtersInModal() menentukan apakah filter tersebut dibuka di dialog. Lihat Widget Filters.
Props yang dirender dashboard:
| Prop | Isi |
|---|---|
page | title, heading, subheading, breadcrumbs, header actions, render-hook scope, cluster |
widgets | definition widget secara berurutan, dengan inline data untuk eager widget |
widgetData | satu deferred prop berisi seluruh payload lazy widget, atau tidak ada jika tidak ada lazy widget |
filters | null jika page tidak memiliki filter schema, atau serialized form jika ada |
Akses
Dashboard adalah page, sehingga authorization-nya sama:
final class FinanceDashboard extends Dashboard
{
public static function canAccess(): bool
{
return auth()->user()?->can('view-finance') === true;
}
}2
3
4
5
6
7
canAccess() diterapkan pada route, bukan hanya navigation. Root dashboard panel sedikit berbeda karena dicapai melalui PanelDashboardController, yang merender nominated page. Namun Page::render() sendiri menjalankan abort_unless(static::canAccess(), 403), sehingga dashboard yang menolak user tetap menghasilkan 403 pada root panel, bukan empty shell.
Catatan
- Root dashboard panel berada langsung di panel path, sehingga tidak didaftarkan sebagai page dan tidak memiliki sidebar entry. Tambahkan
->pages([Dashboard::class])jika menginginkannya; dashboard kemudian juga tersedia di/admin/dashboard. - Extra dashboard adalah page biasa selain cara registration-nya, sehingga tidak membutuhkan
discoverPages()untuk mendapatkan route. Dashboard yang berada di discovery path sekaligus disebut didashboards()tetap didaftarkan satu kali. dashboards([])tidak melakukan apa pun dan bukan berarti menghapus dashboard. Panel tanpa landing page tidak memiliki root route, sehingga empty array sengaja diabaikan.- Widget data tidak pernah disimpan di panel manifest. Hanya class name yang di-cache; count, row, dan chart dihitung per request.
- Widget pada resource page melalui
headerWidgets()ataufooterWidgets()mendapatkanPageContext; widget pada dashboard tidak. Memanggilcontext()pada dashboard widget melempar exception dengan pesan yang menjelaskan kondisi tersebut.