Skip to main content

Data display

Billing History code

Copy the implementation or inspect every file that belongs to this component unit.

resources/views/components/ui/billing-history.blade.php

@props([
    'title' => 'Billing history',
    'description' => null,
    'invoices' => [],
    'currency' => '$',
    'emptyMessage' => 'No invoices yet. Your billing history will appear here.',
])

@php
    /*
    | Billing history table — a list of past invoices with a number, date, amount,
    | status badge and a download link. Reuses the same status palette as
    | <x-ui.invoice>. Numeric amounts are formatted with the `currency` symbol;
    | pre-formatted strings pass through. Use the `actions` slot for a header
    | action (e.g. "Download all").
    |
    | Each invoice: [
    |   'number' => ?, 'date' => ?, 'description' => ?, 'amount' => ?,
    |   'status' => 'paid'|'due'|'overdue'|'refunded'|'draft'|'void',
    |   'href' => ?(download/view link),
    | ]
    |
    | Usage:
    |   <x-ui.billing-history :invoices="[
    |       ['number' => 'INV-0142', 'date' => 'Jun 1, 2026', 'amount' => 55.08, 'status' => 'paid', 'href' => '#'],
    |       ['number' => 'INV-0141', 'date' => 'May 1, 2026', 'amount' => 55.08, 'status' => 'paid', 'href' => '#'],
    |   ]" />
    */
    $money = function ($value) use ($currency): ?string {
        if ($value === null || $value === '') {
            return null;
        }

        return is_numeric($value) ? $currency.number_format((float) $value, 2) : (string) $value;
    };

    $statuses = [
        'paid' => ['variant' => 'green', 'icon' => 'check-circle', 'label' => 'Paid'],
        'due' => ['variant' => 'amber', 'icon' => 'clock', 'label' => 'Due'],
        'overdue' => ['variant' => 'red', 'icon' => 'warning-circle', 'label' => 'Overdue'],
        'draft' => ['variant' => 'gray', 'icon' => 'pencil-simple', 'label' => 'Draft'],
        'refunded' => ['variant' => 'blue', 'icon' => 'arrow-counter-clockwise', 'label' => 'Refunded'],
        'void' => ['variant' => 'gray', 'icon' => 'prohibit', 'label' => 'Void'],
    ];

    $rows = collect($invoices)->map(fn (array $invoice): array => [
        'number' => $invoice['number'] ?? '—',
        'date' => $invoice['date'] ?? null,
        'description' => $invoice['description'] ?? null,
        'amount' => $invoice['amount'] ?? null,
        'status' => $invoice['status'] ?? 'paid',
        'href' => $invoice['href'] ?? null,
    ])->values();
@endphp

<div {{ $attributes->merge(['class' => 'overflow-hidden rounded-2xl border border-gray-200 bg-white dark:border-gray-800 dark:bg-gray-900']) }}>
    {{-- Header --}}
    <div class="flex flex-col gap-4 border-b border-gray-200 p-5 dark:border-gray-800 sm:flex-row sm:items-center sm:justify-between">
        <div class="min-w-0">
            <h3 class="text-base font-semibold text-gray-900 dark:text-white">{{ $title }}</h3>
            @if ($description)
                <p class="mt-0.5 text-sm text-gray-500 dark:text-gray-400">{{ $description }}</p>
            @endif
        </div>
        @isset($actions)
            <div class="shrink-0">{{ $actions }}</div>
        @endisset
    </div>

    @if ($rows->isNotEmpty())
        <table class="w-full text-sm">
            <thead>
                <tr class="border-b border-gray-200 text-left text-xs font-semibold uppercase tracking-wide text-gray-400 dark:border-gray-800 dark:text-gray-500">
                    <th class="px-5 py-3 font-semibold">Invoice</th>
                    <th class="hidden px-5 py-3 font-semibold sm:table-cell">Date</th>
                    <th class="px-5 py-3 text-right font-semibold">Amount</th>
                    <th class="hidden px-5 py-3 text-center font-semibold sm:table-cell">Status</th>
                    <th class="px-5 py-3 text-right font-semibold"><span class="sr-only">Download</span></th>
                </tr>
            </thead>
            <tbody class="divide-y divide-gray-100 dark:divide-gray-800">
                @foreach ($rows as $row)
                    @php $meta = $statuses[$row['status']] ?? $statuses['paid']; @endphp
                    <tr class="transition hover:bg-gray-50/70 dark:hover:bg-gray-800/40">
                        <td class="px-5 py-3.5">
                            <p class="font-medium text-gray-900 dark:text-white">{{ $row['number'] }}</p>
                            <p class="mt-0.5 text-xs text-gray-500 dark:text-gray-400 sm:hidden">
                                @if ($row['date']){{ $row['date'] }}@endif
                            </p>
                            @if ($row['description'])
                                <p class="mt-0.5 hidden text-xs text-gray-500 dark:text-gray-400 sm:block">{{ $row['description'] }}</p>
                            @endif
                        </td>
                        <td class="hidden px-5 py-3.5 text-gray-600 dark:text-gray-300 sm:table-cell">{{ $row['date'] ?? '—' }}</td>
                        <td class="px-5 py-3.5 text-right font-medium tabular-nums text-gray-900 dark:text-white">{{ $money($row['amount']) ?? '—' }}</td>
                        <td class="hidden px-5 py-3.5 text-center sm:table-cell">
                            <x-ui.badge :variant="$meta['variant']" size="sm" :icon="$meta['icon']">{{ $meta['label'] }}</x-ui.badge>
                        </td>
                        <td class="px-5 py-3.5 text-right">
                            @if ($row['href'])
                                <x-ui.icon-button :href="$row['href']" icon="download-simple" size="sm" variant="ghost" label="Download invoice {{ $row['number'] }}" />
                            @else
                                <span class="text-gray-300 dark:text-gray-600">—</span>
                            @endif
                        </td>
                    </tr>
                @endforeach
            </tbody>
        </table>
    @else
        <div class="flex flex-col items-center justify-center gap-3 px-6 py-12 text-center">
            <span class="flex h-11 w-11 items-center justify-center rounded-full bg-gray-100 text-gray-400 dark:bg-gray-800 dark:text-gray-500">
                <x-ui.icon name="receipt" weight="duotone" size="lg" />
            </span>
            <p class="max-w-xs text-sm text-gray-500 dark:text-gray-400">{{ $emptyMessage }}</p>
        </div>
    @endif
</div>