You describe what you want on the backend. Lodestone draws it with React and shadcn.
class TicketsPage extends Page
{
public function schema(): array
{
return [
Stats::make([
Stat::make('Open', fn () => Ticket::open()->count())->chart(fn () => Ticket::open()->perDay(7)),
Stat::make('Pending', fn () => Ticket::pending()->count())->warning(),
Stat::make('Resolved this week', fn () => Ticket::resolvedThisWeek()->count())->success(),
]),
Table::make('tickets')
->query(Ticket::query()->with('customer')->latest())
->columns([
TextColumn::make('subject')->searchable()->description('customer.name'),
BadgeColumn::make('status')->colors(['info' => 'open', 'warning' => 'pending', 'success' => 'resolved']),
TextColumn::make('created_at')->label('Opened')->relative()->sortable(),
])
->rowButtons([
Button::make('resolve')->icon('check')
->visible(fn (Ticket $ticket) => ! $ticket->isResolved())
->click(function (Ticket $ticket, Ui $ui) {
$ticket->resolve();
$ui->toast("Resolved #{$ticket->id}.");
}),
Button::make('refund')->label('Issue refund')->icon('receipt')
->form(RefundForm::make()->submit(function (Ticket $ticket, array $data, Ui $ui) {
$ticket->customer->refund($data['amount'], $data['reason']);
$ui->toast("Refunded \${$data['amount']}.");
})),
])
->recordModal(TicketPage::class),
];
}
}
TODO