Menu

Change language

GitHub repo

Invoices List

blockfinance

Invoice history page: header with a New invoice action, a three-card summary strip (total outstanding, paid this month, overdue), and an invoice table pairing monospace invoice numbers with avatar clients, issue/due dates, tabular-nums amounts, color-coded status badges, and a per-row View/Download/Mark-as-paid dropdown, closed by a pagination hint. Data is stubbed inline; swap for your source.

Installation

$npx uipkge-ng@latest add invoices-list
Manifest:https://uipkge.dev/r/angular/invoices-list.jsonInstalls to:src/app/components/blocks/invoices-list/

Variants

Loading interactive previews…

Schema

Type aliases exported from this item's source. Use these to shape the data you pass in.

Invoice
interface Invoice {
  id: string
  number: string
  client: string
  issued: string
  due: string
  amount: string
  status: InvoiceStatus
}

Files installed (2)

  • src/app/components/blocks/invoices-list/invoices-list.component.ts9.3 kB
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import { Check, Download, Eye, LucideAngularModule, MoreHorizontal, Plus } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import { UiCardComponent, UiCardContentComponent } from '@/ui/card/card.component'
    import {
      UiDropdownMenuComponent,
      UiDropdownMenuContentComponent,
      UiDropdownMenuItemComponent,
      UiDropdownMenuSeparatorComponent,
      UiDropdownMenuTriggerComponent,
    } from '@/ui/dropdown-menu/dropdown-menu.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export type InvoiceStatus = 'paid' | 'pending' | 'overdue' | 'draft'
    
    export interface Invoice {
      id: string
      number: string
      client: string
      issued: string
      due: string
      amount: string
      status: InvoiceStatus
    }
    
    const summary = [
      { label: 'Total outstanding', value: '$12,480.00', note: '5 unpaid invoices', tone: '' },
      { label: 'Paid this month', value: '$28,930.00', note: '+12% vs last month', tone: '' },
      { label: 'Overdue', value: '$3,120.00', note: '2 invoices past due', tone: 'text-destructive' },
    ]
    
    const statusClass: Record<InvoiceStatus, string> = {
      paid: 'bg-success/10 text-success',
      pending: 'bg-warning/10 text-warning',
      overdue: 'bg-destructive/10 text-destructive',
      draft: '',
    }
    
    const invoices: Invoice[] = [
      {
        id: 'i1',
        number: 'INV-2026-0142',
        client: 'Acme Corp',
        issued: 'Aug 18, 2026',
        due: 'Sep 1, 2026',
        amount: '$4,800.00',
        status: 'pending',
      },
      {
        id: 'i2',
        number: 'INV-2026-0141',
        client: 'Northwind Traders',
        issued: 'Aug 15, 2026',
        due: 'Aug 30, 2026',
        amount: '$2,350.00',
        status: 'overdue',
      },
      {
        id: 'i3',
        number: 'INV-2026-0140',
        client: 'Globex Industries',
        issued: 'Aug 12, 2026',
        due: 'Sep 10, 2026',
        amount: '$1,200.00',
        status: 'draft',
      },
      {
        id: 'i4',
        number: 'INV-2026-0139',
        client: 'Helios Labs',
        issued: 'Aug 8, 2026',
        due: 'Aug 22, 2026',
        amount: '$6,750.00',
        status: 'paid',
      },
      {
        id: 'i5',
        number: 'INV-2026-0138',
        client: 'Acme Corp',
        issued: 'Aug 5, 2026',
        due: 'Aug 19, 2026',
        amount: '$770.00',
        status: 'paid',
      },
      {
        id: 'i6',
        number: 'INV-2026-0137',
        client: 'Vertex Media',
        issued: 'Jul 30, 2026',
        due: 'Aug 13, 2026',
        amount: '$3,120.00',
        status: 'overdue',
      },
      {
        id: 'i7',
        number: 'INV-2026-0136',
        client: 'Initech Systems',
        issued: 'Jul 24, 2026',
        due: 'Aug 7, 2026',
        amount: '$5,400.00',
        status: 'paid',
      },
    ]
    
    /**
     * InvoicesList -- the React block, 1:1: header with a New invoice action, a three-card
     * summary strip (total outstanding, paid this month, overdue), and an invoice table pairing
     * monospace invoice numbers with avatar clients, issue/due dates, tabular-nums amounts,
     * color-coded status badges, and a per-row View/Download/Mark-as-paid dropdown, closed by
     * a pagination hint. Data is stubbed inline; swap for your source.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-invoices-list, [ui-invoices-list]',
      standalone: true,
      // React renders the root <div> itself: the host stays out of layout and `class` goes to the root.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiDropdownMenuComponent,
        UiDropdownMenuContentComponent,
        UiDropdownMenuItemComponent,
        UiDropdownMenuSeparatorComponent,
        UiDropdownMenuTriggerComponent,
        UiTableComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="invoices-list" [class]="rootClass">
          <div class="flex flex-wrap items-center justify-between gap-4">
            <div>
              <h2 class="text-2xl font-bold tracking-tight">Invoices</h2>
              <p class="text-muted-foreground mt-1 text-sm">Manage billing for all of your clients.</p>
            </div>
            <button ui-button>
              <lucide-icon [img]="Plus" />
              New invoice
            </button>
          </div>
    
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-3">
            @for (item of summary; track item.label) {
              <div ui-card class="shadow-xs">
                <div ui-card-content class="space-y-1">
                  <p class="text-muted-foreground text-sm">{{ item.label }}</p>
                  <p class="text-2xl font-bold tracking-tight tabular-nums" [class]="item.tone">{{ item.value }}</p>
                  <p class="text-muted-foreground text-xs">{{ item.note }}</p>
                </div>
              </div>
            }
          </div>
    
          <div class="bg-card overflow-x-auto rounded-lg border shadow-xs">
            <table ui-table>
              <thead ui-table-header>
                <tr ui-table-row>
                  <th ui-table-head>Invoice</th>
                  <th ui-table-head>Client</th>
                  <th ui-table-head>Issued</th>
                  <th ui-table-head>Due</th>
                  <th ui-table-head class="text-right">Amount</th>
                  <th ui-table-head>Status</th>
                  <th ui-table-head class="w-12"><span class="sr-only">Actions</span></th>
                </tr>
              </thead>
              <tbody ui-table-body>
                @for (invoice of invoices; track invoice.id) {
                  <tr ui-table-row>
                    <td ui-table-cell class="font-mono text-xs font-medium">{{ invoice.number }}</td>
                    <td ui-table-cell>
                      <div class="flex items-center gap-2">
                        <ui-avatar class="size-7">
                          <ui-avatar-fallback class="text-xs">{{ initials(invoice.client) }}</ui-avatar-fallback>
                        </ui-avatar>
                        <span class="truncate text-sm font-medium">{{ invoice.client }}</span>
                      </div>
                    </td>
                    <td ui-table-cell class="text-muted-foreground text-sm">{{ invoice.issued }}</td>
                    <td ui-table-cell class="text-muted-foreground text-sm">{{ invoice.due }}</td>
                    <td ui-table-cell class="text-right font-medium tabular-nums">{{ invoice.amount }}</td>
                    <td ui-table-cell>
                      <span
                        ui-badge
                        [variant]="invoice.status === 'draft' ? 'secondary' : 'default'"
                        [class]="badgeClass(invoice.status)"
                      >
                        {{ invoice.status }}
                      </span>
                    </td>
                    <td ui-table-cell>
                      <ui-dropdown-menu>
                        <button
                          ui-button
                          ui-dropdown-menu-trigger
                          variant="ghost"
                          size="icon-sm"
                          class="text-muted-foreground"
                        >
                          <lucide-icon [img]="MoreHorizontal" />
                          <span class="sr-only">Open actions for {{ invoice.number }}</span>
                        </button>
                        <ui-dropdown-menu-content align="end">
                          <ui-dropdown-menu-item>
                            <lucide-icon [img]="Eye" />
                            <span>View</span>
                          </ui-dropdown-menu-item>
                          <ui-dropdown-menu-item>
                            <lucide-icon [img]="Download" />
                            <span>Download</span>
                          </ui-dropdown-menu-item>
                          <ui-dropdown-menu-separator />
                          <ui-dropdown-menu-item [disabled]="invoice.status === 'paid' || invoice.status === 'draft'">
                            <lucide-icon [img]="Check" />
                            <span>Mark as paid</span>
                          </ui-dropdown-menu-item>
                        </ui-dropdown-menu-content>
                      </ui-dropdown-menu>
                    </td>
                  </tr>
                }
              </tbody>
            </table>
          </div>
    
          <div class="flex items-center justify-between">
            <p class="text-muted-foreground text-sm">Showing {{ invoices.length }} of 42 invoices</p>
            <div class="flex items-center gap-2">
              <button ui-button variant="outline" size="sm" disabled>Previous</button>
              <button ui-button variant="outline" size="sm">Next</button>
            </div>
          </div>
        </div>
      `,
    })
    export class UiInvoicesListComponent {
      protected readonly Check = Check
      protected readonly Download = Download
      protected readonly Eye = Eye
      protected readonly MoreHorizontal = MoreHorizontal
      protected readonly Plus = Plus
    
      protected readonly summary = summary
      protected readonly invoices = invoices
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      badgeClass(status: InvoiceStatus): string {
        return cn('capitalize', statusClass[status])
      }
    
      initials(name: string): string {
        return name
          .split(' ')
          .map((part) => part[0])
          .slice(0, 2)
          .join('')
          .toUpperCase()
      }
    }
    
  • src/app/components/blocks/invoices-list/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/invoices-list.json