make:panel-widget
Menghasilkan dashboard widget dalam salah satu dari empat jenis:
stats;table;chart;custom.
Gunakan command ini ketika Panel membutuhkan:
- angka/statistik singkat;
- table kecil;
- grafik;
- atau Vue component khusus pada dashboard maupun Page.
php artisan make:panel-widget UserStats --panel=Admin --type=statsOutput:
INFO Created [app/Panels/Admin/Widgets/UserStats.php]Path discoverWidgets() milik Panel sudah mencakup app/Panels/Admin/Widgets, sehingga widget akan muncul pada dashboard Panel pada request berikutnya.
Signature
make:panel-widget
{name : The widget class name}
{--panel= : The panel it belongs to}
{--type=stats : stats, table, chart, or custom}
{--force}2
3
4
5
| Argument / option | Default | Efek |
|---|---|---|
name | required | Diubah menjadi StudlyCase. |
--panel= | required | Panel tujuan generate dalam StudlyCase. Jika tidak diberikan, command gagal. |
--type= | stats | Salah satu dari stats, table, chart, atau custom. Type lain ditolak dan tidak ada file yang ditulis. |
--force | off | Menimpa file yang sudah ada. |
php artisan make:panel-widget UserStats --panel=Admin --type=stats
php artisan make:panel-widget RecentUsers --panel=Admin --type=table
php artisan make:panel-widget UserGrowth --panel=Admin --type=chart
php artisan make:panel-widget ServerHealth --panel=Admin --type=custom2
3
4
Type yang tidak dikenal ditolak, bukan ditebak:
ERROR Unknown widget type [hologram]. Valid types are: stats, table, chart, custom.File yang Dibuat per Type
--type | Base class | PHP file | Vue file |
|---|---|---|---|
stats | PandaPanel\Widgets\StatsWidget | app/Panels/{Panel}/Widgets/{Class}.php | — |
table | PandaPanel\Widgets\TableWidget | sama | — |
chart | PandaPanel\Widgets\ChartWidget | sama | — |
custom | PandaPanel\Widgets\CustomWidget | sama | resources/js/pages/Panels/{Panel}/Widgets/{Class}.vue |
Hanya custom yang mendapatkan Vue file, dan untuk type tersebut Vue file bukan opsional. Custom Widget tanpa component hanya akan merender fallback.
Tiga type lainnya dirender oleh component yang dipublish package.
Property yang Diwarisi Semua Widget
Semua type mewarisi PandaPanel\Widgets\Widget.
| Property | Type | Default | Arti |
|---|---|---|---|
$sort | int | 0 | Urutan pada dashboard, ascending. |
$columnSpan | int|string|array<string, int|string> | 1; table/chart default lebih lebar | Lebar widget pada grid per breakpoint. |
$lazy | bool | false | Jika true, data dikirim sebagai deferred Inertia prop agar dashboard dapat tampil lebih dulu. |
$heading | ?string | null | Heading di atas widget. |
$description | ?string | null | Deskripsi di bawah heading. |
$pollingInterval | ?int | null | Interval self-refresh dalam detik; null berarti tidak polling. |
Contoh:
use PandaPanel\Widgets\StatsWidget;
final class UserStats extends StatsWidget
{
protected static int $sort = 10;
protected static bool $lazy = true;
protected static ?string $heading = 'Accounts';
protected static ?string $description = 'Everyone who has ever signed up.';
protected static ?int $pollingInterval = 60;
// ...
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
--type=stats
Generated class:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Widgets;
use PandaPanel\Widgets\StatsWidget;
use PandaPanel\Widgets\Support\Stat;
final class UserStats extends StatsWidget
{
protected static int $sort = 0;
/**
* Use aggregates. Hydrating a collection to count it is how a dashboard
* becomes the slowest page in the application.
*
* @return list<Stat>
*/
public function stats(): array
{
return [
Stat::make('Example', 0),
];
}
}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
stats() adalah abstract method utama yang harus diimplementasikan.
Gunakan aggregate query daripada mengambil seluruh collection lalu menghitungnya. Dashboard biasanya dirender sering, sehingga query yang tidak efisien cepat menjadi bottleneck.
Contoh Stat:
use App\Models\User;
use PandaPanel\Widgets\Enums\StatColor;
use PandaPanel\Widgets\Support\Stat;
public function stats(): array
{
return [
Stat::make('Users', User::query()->count())
->description('All time')
->icon('users')
->color(StatColor::Info)
->trend('up', 12.5)
->chart([4, 9, 6, 11, 14])
->format(suffix: ' accounts')
->url('/admin/users'),
];
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
API:
| Method | Signature |
|---|---|
make | static make(string $label, string|int|float $value): self |
description | description(string $description): self |
icon | icon(string $icon): self |
color | color(StatColor $color): self |
trend | trend(string $direction, float $value): self |
chart | chart(array $values): self |
url | url(string $url): self |
format | format(?string $prefix = null, ?string $suffix = null, ?int $decimals = null): self |
--type=table
Generated stub:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Widgets;
use Illuminate\Support\Collection;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\TableSchema;
use PandaPanel\Widgets\TableWidget;
final class RecentUsers extends TableWidget
{
protected static int $sort = 0;
public function table(TableSchema $table): TableSchema
{
return $table->columns([
TextColumn::make('id')->label('ID'),
]);
}
/**
* @return Collection<int, covariant \Illuminate\Database\Eloquent\Model>
*/
public function rows(): Collection
{
return new Collection;
}
}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
Catatan Penting: Stub Ini Saat Ini Tidak Berjalan Apa Adanya
TableWidget mendeklarasikan dua abstract method:
table()
query()2
Tetapi generated stub mengimplementasikan:
table()
rows()2
rows() tidak mengoverride abstract method apa pun.
Akibatnya class hasil generate dapat menghasilkan:
PHP Fatal error: Class App\Panels\Admin\Widgets\RecentUsers contains 1 abstract
method and must therefore be declared abstract or implement the remaining
methods (PandaPanel\Widgets\TableWidget::query)2
3
Ganti rows() menjadi query().
Contoh yang bekerja:
use App\Models\User;
use Illuminate\Database\Eloquent\Builder;
use PandaPanel\Tables\Columns\DateTimeColumn;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\Enums\SortDirection;
use PandaPanel\Tables\TableSchema;
use PandaPanel\Widgets\TableWidget;
final class RecentUsers extends TableWidget
{
protected static int $sort = 20;
protected static string $emptyMessage = 'No one has signed up yet.';
protected static int $perPage = 5;
public function table(TableSchema $table): TableSchema
{
return $table
->columns([
TextColumn::make('name')->searchable()->sortable(),
DateTimeColumn::make('created_at')->label('Joined')->relative()->sortable(),
])
->defaultSort('created_at', SortDirection::Descending);
}
/**
* @return Builder<User>
*/
public function query(): Builder
{
return User::query()->select(['id', 'name', 'created_at']);
}
}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
query() mengembalikan query, bukan Collection, karena table builder membutuhkan query untuk:
- searching;
- sorting;
- pagination.
Table Widget menggunakan TableSchema/TableQuery yang sama seperti Resource index.
Default:
$perPage = 5
$emptyMessage = 'Nothing to show yet.'2
--type=chart
Generated stub:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Widgets;
use PandaPanel\Widgets\ChartWidget;
use PandaPanel\Widgets\Support\ChartSeries;
final class UserGrowth extends ChartWidget
{
protected static int $sort = 0;
/**
* Set to true when the query is slow enough that the dashboard should
* paint before it finishes.
*/
protected static bool $lazy = false;
protected static string $variant = 'bar';
/**
* @return list<string>
*/
public function labels(): array
{
return [];
}
/**
* @return list<ChartSeries>
*/
public function series(): array
{
return [];
}
}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
Catatan Penting: Stub Chart Juga Tidak Berjalan Apa Adanya
Property base:
ChartWidget::$variantmemiliki type:
PandaPanel\Widgets\Enums\ChartVariantGenerated subclass saat ini mendeklarasikan ulang dengan:
protected static string $variant = 'bar';PHP tidak mengizinkan child class mengubah type static property tersebut.
Error:
PHP Fatal error: Type of App\Panels\Admin\Widgets\UserGrowth::$variant must be
PandaPanel\Widgets\Enums\ChartVariant (as in class PandaPanel\Widgets\ChartWidget)2
Ada dua solusi:
- hapus line
$variant, karenaChartVariant::Barsudah menjadi default; atau - gunakan enum yang benar.
use PandaPanel\Widgets\Enums\ChartVariant;
protected static ChartVariant $variant = ChartVariant::Area;2
3
Cases:
| Case | Value |
|---|---|
ChartVariant::Bar | bar |
ChartVariant::Line | line |
ChartVariant::Area | area |
ChartVariant::Doughnut | doughnut |
Contoh chart yang valid:
use App\Models\User;
use PandaPanel\Widgets\ChartWidget;
use PandaPanel\Widgets\Enums\ChartVariant;
use PandaPanel\Widgets\Enums\StatColor;
use PandaPanel\Widgets\Support\ChartOptions;
use PandaPanel\Widgets\Support\ChartSeries;
final class UserGrowth extends ChartWidget
{
protected static ChartVariant $variant = ChartVariant::Area;
protected static int $maxHeight = 200;
public function options(): ChartOptions
{
return ChartOptions::make()->legend(false)->curved()->filled();
}
/**
* @return list<string>
*/
public function labels(): array
{
return ['Jan', 'Feb', 'Mar'];
}
/**
* @return list<ChartSeries>
*/
public function series(): array
{
return [
ChartSeries::make('Sign-ups', [4, 9, 6])->color(StatColor::Info),
];
}
}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
Series API:
ChartSeries::make(string $label, array $values): self
ChartSeries::color(StatColor $color): self2
$maxHeight default adalah:
220 pxBatas tinggi membantu beberapa chart pada satu dashboard tetap dapat dibandingkan tanpa tumbuh mengikuti container secara berlebihan.
--type=custom
php artisan make:panel-widget ServerHealth --panel=Admin --type=customOutput:
INFO Created [app/Panels/Admin/Widgets/ServerHealth.php]
INFO Created [resources/js/pages/Panels/Admin/Widgets/ServerHealth.vue]2
PHP:
<?php
declare(strict_types=1);
namespace App\Panels\Admin\Widgets;
use PandaPanel\Widgets\CustomWidget;
final class ServerHealth extends CustomWidget
{
protected static int $sort = 0;
protected static string $component = 'Panels/Admin/Widgets/ServerHealth';
/**
* @return array<string, mixed>
*/
public function data(): array
{
return [];
}
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Vue:
<script setup lang="ts">
defineProps<{
// Mirror whatever the PHP widget's data() returns.
}>();
</script>
<template>
<div class="flex h-full flex-col gap-3 rounded-lg border p-4">
<h3 class="text-sm font-medium">ServerHealth</h3>
</div>
</template>2
3
4
5
6
7
8
9
10
11
Semua data dari data() diterima sebagai props component:
public function data(): array
{
return [
'queue' => 12,
'failed' => 0,
];
}2
3
4
5
6
7
<script setup lang="ts">
defineProps<{
queue: number;
failed: number;
}>();
</script>2
3
4
5
6
Component name adalah path relatif di bawah:
resources/js/pages/Custom Widget di-resolve melalui build-time glob:
resources/js/pages/Panels/**/Widgets/*.vueComponent di luar bentuk tersebut tidak masuk bundle dan tidak dapat di-resolve walaupun nama component sampai ke frontend.
Custom Stubs
php artisan vendor:publish --tag=panda-panel-stubs| Stub | Digunakan untuk | Placeholder |
|---|---|---|
widget-stats.stub | --type=stats | panel, class, component |
widget-table.stub | --type=table | panel, class, component |
widget-chart.stub | --type=chart | panel, class, component |
widget-custom.stub | --type=custom | panel, class, component |
widget-component.stub | Vue file untuk custom | label |
component hanya digunakan oleh custom stub.
Publishing stubs juga merupakan cara untuk memperbaiki table/chart stub satu kali untuk seluruh project, bukan mengedit setiap generated file.
Exit Codes
| Hasil | Code |
|---|---|
| Minimal satu file dibuat | 0 |
| Semua file sudah ada dan dilewati | 1 |
--panel tidak diberikan | 1, The --panel option is required. |
--type tidak dikenal | 1, tidak ada file ditulis |
Hal yang Perlu Diperhatikan
- Generated table dan chart widget saat ini tidak compile tanpa perbaikan yang dijelaskan di atas.
- Test generator hanya memastikan class mewarisi base yang tepat dan lolos Pint; test tersebut tidak memuat class, sehingga type/abstract-method problem masih dapat lolos.
{{ label }}pada generated Vue file adalah stub placeholder, bukan Vue binding. Value diganti menjadi class name saat generation.- Widget ditemukan melalui discovery. Widget harus berada di path
discoverWidgets(). - Cached manifest menyembunyikan widget baru. Jalankan
php artisan panel:clear. - Discovered widget otomatis masuk Panel dashboard. Untuk memasangnya hanya pada Page tertentu, daftarkan melalui
widgets()Page tersebut. - Custom Widget membutuhkan frontend rebuild.
- Icon pada Stat harus terdaftar. Jalankan
php artisan panel:icons. - Tidak ada option
--lazy. Lazy loading diatur melalui property class. Chart stub hanya menuliskan$lazy = false.