Kolom
Kolom mewakili satu informasi yang ditampilkan tabel dari sebuah record: bagaimana kolom dideskripsikan ke frontend, bagaimana nilai record diubah menjadi sel yang dapat diserialisasi, serta metadata per row seperti tooltip, link, dan attribute tambahan. Pilih jenis kolom berdasarkan jenis nilai yang ditampilkan, karena type() menjadi discriminator yang digunakan renderer Vue untuk memilih komponen yang tepat.
Semua jenis kolom mewarisi PandaPanel\Tables\Columns\Column, sehingga seluruh kemampuan pada base class tersedia untuk setiap jenis kolom.
Contoh kumpulan kolom minimal
use PandaPanel\Tables\Columns\BadgeColumn;
use PandaPanel\Tables\Columns\DateTimeColumn;
use PandaPanel\Tables\Columns\NumberColumn;
use PandaPanel\Tables\Columns\TextColumn;
use PandaPanel\Tables\Enums\BadgeColor;
use PandaPanel\Tables\TableSchema;
return $table->columns([
TextColumn::make('reference')->searchable()->sortable()->toggleable(false),
TextColumn::make('customer.name')->label('Customer')->searchable(),
BadgeColumn::make('status')
->labels(['open' => 'Open', 'done' => 'Done'])
->colors(['open' => BadgeColor::Info, 'done' => BadgeColor::Success])
->sortable(),
NumberColumn::make('total')->prefix('$')->decimals(2)->sortable(),
DateTimeColumn::make('created_at')->label('Placed')->sortable(),
]);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
make() menerima nama atribut. Nilainya dibaca dengan data_get(), sehingga customer.name dapat menelusuri relation. Secara default, label dibentuk dengan Str::headline() dari nama kolom.
Jenis kolom
| Class | type() | Bentuk sel |
|---|---|---|
TextColumn | text | string|null |
NumberColumn | number | {display: string, raw: int|float}|null |
BadgeColumn | badge | {value, label, color}|null |
BooleanColumn | boolean | {value: bool, label: string} |
DateColumn | date | {display, iso}|null |
DateTimeColumn | datetime | {display, iso}|null |
ImageColumn | image | {url, fallback, alt} |
IconColumn | icon | {icon, color, label}|null |
ColorColumn | color | {color, label}|null |
CustomColumn | custom | apa pun yang dikembalikan state() |
ToggleColumn, CheckboxColumn, TextInputColumn, SelectColumn | editable | lihat kolom yang dapat diedit |
Nilai type() di atas adalah case dari PandaPanel\Tables\Enums\ColumnType.
TextColumn
use PandaPanel\Tables\Columns\TextColumn;
TextColumn::make('excerpt')
->limit(80) // truncates on the server, so the payload stays small
->wrap(); // let the cell wrap instead of clipping to one line2
3
4
5
| Method | Signature | Default |
|---|---|---|
limit() | limit(int $characters): self | tanpa batas |
wrap() | wrap(bool $wrap = true): self | false |
Nilai non-scalar di-encode sebagai JSON. String kosong dianggap tidak memiliki nilai sehingga placeholder dapat ditampilkan.
NumberColumn
use PandaPanel\Tables\Columns\NumberColumn;
NumberColumn::make('total')->prefix('$')->decimals(2)->suffix(' USD');2
3
| Method | Signature | Default |
|---|---|---|
decimals() | decimals(int $decimals): self | 0 |
prefix() | prefix(string $prefix): self | null |
suffix() | suffix(string $suffix): self | null |
Alignment default adalah end. Formatting dilakukan di server menggunakan number_format(), dan sel membawa string display yang sudah siap ditampilkan bersama angka raw. Nilai yang bukan numeric dirender sebagai tidak memiliki nilai.
BadgeColumn
use PandaPanel\Tables\Columns\BadgeColumn;
use PandaPanel\Tables\Enums\BadgeColor;
BadgeColumn::make('email_verified_at')
->label('Status')
->formatUsing(static fn (mixed $value): string => $value === null ? 'unverified' : 'verified')
->labels(['verified' => 'Verified', 'unverified' => 'Unverified'])
->colors(['verified' => BadgeColor::Success, 'unverified' => BadgeColor::Warning]);2
3
4
5
6
7
8
| Method | Signature |
|---|---|
colors() | colors(array $colors): self — di-key berdasarkan value, menggunakan BadgeColor atau string value-nya |
labels() | labels(array $labels): self — di-key berdasarkan value |
BadgeColor adalah closed set: Neutral, Success, Warning, Danger, dan Info. Nilai yang tidak memiliki mapping tetap dirender sebagai badge neutral dengan label dari Str::headline(). Dengan demikian, penambahan enum case baru tetap menghasilkan badge yang terbaca, bukan elemen tanpa style. Boolean menggunakan key 'true' dan 'false', sedangkan backed enum menggunakan value miliknya.
BooleanColumn
use PandaPanel\Tables\Columns\BooleanColumn;
BooleanColumn::make('is_active')->labels('Active', 'Suspended');2
3
labels(string $true, string $false): self memiliki default Yes dan No. Kolom menggunakan alignment center. Nilai di-cast dengan (bool), sehingga jenis kolom ini selalu menghasilkan sebuah sel.
DateColumn dan DateTimeColumn
use PandaPanel\Tables\Columns\DateColumn;
use PandaPanel\Tables\Columns\DateTimeColumn;
DateColumn::make('due_on')->format('d/m/Y');
DateTimeColumn::make('created_at')->relative();2
3
4
5
| Method | Signature | Default |
|---|---|---|
format() | format(string $format): static | M j, Y (M j, Y H:i untuk DateTimeColumn) |
relative() | relative(bool $relative = true): static | false |
Sel membawa display dan iso, sehingga frontend dapat menampilkan timestamp presisi pada hover tanpa request kedua. Atribut harus di-resolve menjadi Carbon\CarbonInterface; lakukan cast pada model, atau sel akan dirender kosong.
ImageColumn
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use PandaPanel\Tables\Columns\ImageColumn;
ImageColumn::make('avatar')
->label('')
->circular()
->size(40)
->fallbackUsing(static fn (Model $record): string => Str::upper(Str::substr($record->email, 0, 2)));2
3
4
5
6
7
8
9
| Method | Signature | Default |
|---|---|---|
circular() | circular(bool $circular = true): self | false |
size() | size(int $pixels): self | 32 |
fallbackUsing() | fallbackUsing(Closure $callback): self | inisial dari atribut name pada record |
Nilai digunakan sebagai url jika berupa string yang tidak kosong. Nilai alt menggunakan atribut name milik record sebagai fallback pertama, lalu label kolom.
IconColumn
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Tables\Columns\IconColumn;
use PandaPanel\Tables\Enums\BadgeColor;
IconColumn::make('status')
->icons(['published' => 'check', 'draft' => 'pencil'])
->colors(['published' => BadgeColor::Success, 'draft' => BadgeColor::Warning]);
IconColumn::make('two_factor_confirmed_at')->boolean(trueIcon: 'shield', falseIcon: 'shield-off');
IconColumn::make('score')->iconUsing(
static fn (mixed $value, Model $record): ?string => $value > 80 ? 'trending-up' : 'trending-down',
);2
3
4
5
6
7
8
9
10
11
12
13
| Method | Signature |
|---|---|
icons() | icons(array $icons): self — mapping value ke key icon registry |
colors() | colors(array $colors): self — mapping value ke BadgeColor |
iconUsing() | iconUsing(Closure $callback): self — fn (mixed $value, Model $record): ?string |
boolean() | boolean(string $trueIcon = 'check', string $falseIcon = 'x'): self |
Alignment default adalah center. Closure mengembalikan key registry, bukan markup atau path. Dengan cara ini schema tabel tidak dapat meminta browser memuat icon yang tidak pernah dikompilasi, dan panel:icons dapat menemukan nama icon dengan membaca source. boolean() menggunakan warna Success/Danger serta label Yes/No. Nilai yang tidak memiliki mapping tidak menampilkan icon.
ColorColumn
use PandaPanel\Tables\Columns\ColorColumn;
ColorColumn::make('brand_color')->copyable();2
3
copyable(bool $copyable = true): self menampilkan value sebagai teks di samping color swatch.
Nilai warna divalidasi di server sebagai hex (#rgb, #rgba, #rrggbb, #rrggbbaa), rgb()/rgba(), atau hsl()/hsla(). Nilai lain tidak dirender. Whitelist ini penting karena nilai akhirnya digunakan pada inline background-color; membiarkan string tidak tervalidasi masuk ke sana sama saja memberikan CSS arbitrer dari data record.
CustomColumn
use Illuminate\Database\Eloquent\Model;
use PandaPanel\Tables\Columns\CustomColumn;
CustomColumn::make('trend')
->component('Panels/Admin/Columns/Sparkline')
->state(static fn (Model $record): array => ['points' => $record->view_counts]);2
3
4
5
6
| Method | Signature |
|---|---|
component() | component(string $component): self |
state() | state(Closure $callback): self — fn (Model $record): mixed |
Nama component merupakan key build-time registry di bawah resources/js/pages/Panels/{Panel}/Columns/ yang di-resolve melalui Vite glob. Tanpa state(), sel menggunakan resolved value biasa milik kolom. Nilai apa pun yang dikembalikan state() harus dapat diserialisasi menjadi scalar dan array seperti sel lainnya.
Kemampuan yang tersedia pada setiap kolom
Nama dan label
TextColumn::make('created_at')->label('Registered');Gunakan getName() dan getLabel() untuk membaca nilainya. Nama kosong melempar PanelSchemaException::emptyName('column').
Pengurutan
use Illuminate\Database\Eloquent\Builder;
use PandaPanel\Tables\Enums\SortDirection;
TextColumn::make('name')->sortable();
TextColumn::make('display_name')->sortable(column: 'name'); // order by a different column
TextColumn::make('status')->sortUsing(
static fn (Builder $query, SortDirection $direction) => $query
->orderByRaw("field(status, 'urgent', 'open', 'closed') {$direction->value}"),
);2
3
4
5
6
7
8
9
| Method | Signature |
|---|---|
sortable() | sortable(bool $sortable = true, ?string $column = null): static |
sortUsing() | sortUsing(Closure $callback): static — juga membuat kolom sortable |
sortableByRelation() | sortableByRelation(string $relation, string $column): static |
sortable() adalah deklarasi, bukan implementasi perilaku. TableQuery membacanya sebagai whitelist. Lihat pengurutan.
Pencarian
TextColumn::make('name')->searchable();
TextColumn::make('name')->searchable(columns: ['first_name', 'last_name']);
TextColumn::make('reference')->searchable(individually: true);
TextColumn::make('author.name')->searchable(); // routed to whereHas2
3
4
Signature-nya adalah searchable(bool $searchable = true, ?array $columns = null, bool $individually = false): static. Nama dengan dot diarahkan ke whereHas, bukan LIKE terhadap kolom yang tidak tersedia di tabel saat ini. Lihat pencarian.
Visibility
TextColumn::make('id')->toggleable(false); // the user can never hide it
TextColumn::make('notes')->visible(false); // hidden until the user shows it2
| Method | Signature | Default |
|---|---|---|
visible() | visible(bool $visible = true): static | true |
toggleable() | toggleable(bool $toggleable = true): static | true |
Lihat column manager.
Nilai kosong
TextColumn::make('title')->placeholder('Untitled');
TextColumn::make('title')->default('—');2
Keduanya tidak sama. default() menggantikan nilai null sebelum sel diformat, sehingga nilainya tetap melewati formatUsing(). placeholder() adalah presentasi khusus ketika nilai tidak ada dan dirender sebagai pengganti sel. Placeholder tersedia di base class agar kolom tanggal kosong dan kolom teks kosong dapat menggunakan perilaku yang konsisten.
Formatting
use Illuminate\Database\Eloquent\Model;
TextColumn::make('status')->formatUsing(
static fn (mixed $value, Model $record): string => ucfirst((string) $value),
);2
3
4
5
formatUsing(Closure $callback): static dijalankan di server. Hanya hasil akhirnya yang diserialisasi.
Layout
use PandaPanel\Tables\Enums\Alignment;
NumberColumn::make('total')
->alignment(Alignment::End)
->headerAlignment(Alignment::Start)
->width('12rem')
->wrapHeader()
->headerTooltip('Including tax');2
3
4
5
6
7
8
| Method | Signature | Default |
|---|---|---|
alignment() | alignment(Alignment|string $alignment): static | Start (End pada NumberColumn, Center pada BooleanColumn, IconColumn, ToggleColumn, CheckboxColumn) |
headerAlignment() | headerAlignment(Alignment|string $alignment): static | mengikuti alignment sel |
width() | width(string $width): static | null |
wrapHeader() | wrapHeader(bool $wrap = true): static | false |
headerTooltip() | headerTooltip(string $tooltip): static | null |
Alignment bersifat logis — Start, Center, End, Justify — sehingga locale right-to-left dapat membalik arah tanpa menulis ulang setiap tabel. 'left' dan 'right' tetap diterima dengan arti literalnya; nilai yang tidak dikenali fallback ke Start. width() menerima CSS length yang digunakan inline, bukan Tailwind class. Class seperti w-${n} membutuhkan interpolasi dan class hasil interpolasi belum tentu tersedia di bundle.
Metadata per row
use App\Panels\Admin\Resources\Posts\PostResource;
use Illuminate\Database\Eloquent\Model;
TextColumn::make('title')
->tooltip(static fn (Model $record): string => $record->slug)
->url(static fn (Model $record): string => PostResource::url('view', $record))
->extraAttributes(static fn (Model $record): array => ['data-status' => $record->status]);2
3
4
5
6
7
| Method | Signature |
|---|---|
tooltip() | tooltip(Closure|string $tooltip): static |
url() | url(Closure $callback): static — fn (Model $record): ?string |
extraAttributes() | extraAttributes(Closure|array $attributes): static |
action() | action(Action $action): static |
Metadata tersebut dikirim melalui cellMeta di samping cells, bukan dicampur ke value sel. Dengan begitu bentuk value tetap sesuai type guard renderer dan tabel yang tidak menggunakan metadata tambahan cukup mengirim map kosong. toCellMeta() mengembalikan null ketika tidak ada metadata. Nilai url() hanya dipertahankan jika relatif atau menggunakan scheme http, https, mailto, atau tel; scheme yang tidak aman diserialisasi menjadi null dan juga diabaikan renderer Vue.
extraAttributes di-spread ke elemen DOM, sehingga hanya scalar yang diterima dan key yang diawali on ditolak. Event handler melalui attribute akan menjadi jalan untuk memasukkan executable content dari schema.
Cell action
use PandaPanel\Actions\Action;
use Illuminate\Database\Eloquent\Model;
TextColumn::make('reference')->action(
Action::make('approve')->action(static fn (Model $record) => $record->approve()),
);2
3
4
5
6
Action di-resolve per record. Sel yang tidak boleh menjalankan action dirender sebagai nilai biasa, bukan tombol yang akan menghasilkan 403. TableSchema::getRecordAction() mencari column action bersama row action karena keduanya sama-sama mengidentifikasi record, mengotorisasi operasi, lalu mengubahnya. Berikan sebuah kolom action atau url(), bukan keduanya; sel yang sekaligus bernavigasi dan menjalankan operasi menghasilkan perilaku ambigu.
Freezing
use PandaPanel\Tables\Enums\ColumnPin;
TextColumn::make('reference')->frozen();
NumberColumn::make('balance')->frozen(ColumnPin::End);
TextColumn::make('notes')->frozen(false);2
3
4
5
frozen(ColumnPin|bool $pin = true): static. Nilai true berarti ColumnPin::Start, sedangkan false menghapus pin. Lihat frozen dan pinned column.
Summary
use PandaPanel\Tables\Summaries\Average;
use PandaPanel\Tables\Summaries\Sum;
NumberColumn::make('total')->summarize([Sum::make(), Average::make()]);2
3
4
Gunakan summarize(array $summarizers): static. Lihat summary.
State relation
NumberColumn::make('posts_count')->counts('posts')->sortable();
BooleanColumn::make('posts_exists')->exists('posts');
NumberColumn::make('orders_sum_total')->sum('orders', 'total');
TextColumn::make('author_name')->sortableByRelation('author', 'name');2
3
4
counts(), exists(), sum(), avg(), min(), dan max() dihitung di SELECT oleh TableSchema::applyColumnQueries(), sehingga biayanya satu query untuk seluruh halaman, bukan query per row. Hasilnya ditempatkan pada atribut yang dihasilkan Eloquent dan sel membaca atribut tersebut, bukan nama kolom aslinya. Lihat kolom relation.
Hal yang perlu diperhatikan
sortable()me-reset sort column yang diset sebelumnya. Parameter kedua default kenulldan selalu di-assign, sehingga->counts('posts')->sortable()dapat menghapus alias aggregate yang diset olehcounts(). Beri nama kolom sesuai atribut hasil Eloquent sepertiposts_count, atau tentukansortable(column: 'posts_count')secara eksplisit.default()melewatiformatUsing(), sedangkanplaceholder()tidak. Gunakan placeholder untuk mengatakan “tidak ada nilai” dan default untuk menyediakan nilai pengganti yang diperlakukan seperti nilai sebenarnya.DateColumndengan atribut yang belum di-cast dirender kosong. Sel membutuhkanCarbonInterface; raw string bukanCarbonInterface.ColorColumntidak merender apa pun untuk value yang tidak dikenali. Ini disengaja; memperbaiki string secara otomatis lalu memasukkannya ke style attribute lebih berisiko daripada tidak menampilkan warna.- Nama kolom duplikat melempar exception pada setter
columns(), sehingga stack trace menunjuk ke deklarasi resource yang bermasalah. - Setiap kolom
toggleable()secara default. Tandai kolom identitas utama dengantoggleable(false)agar pengguna tidak dapat menyembunyikan satu-satunya informasi yang menjelaskan row tersebut.