Menu

Change language

GitHub repo

Record Detail Page

blocklayout

Admin record detail page: breadcrumb header with status badge and Edit / Delete actions, two-column layout with a summary card (avatar, identity, key metrics), Details / Activity tabs (labeled description-list sections and an icon timeline feed), plus a side panel with owner metadata, relative timestamps, tag badges, and a recent-orders mini list. All data is hardcoded inline for you to replace.

Installation

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

Variants

Loading interactive previews…

Schema

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

DataListRow
interface DataListRow {
  label: string
  value: string
}
Section
interface Section {
  title: string
  icon: LucideIconData
  rows: DataListRow[]
}
ActivityEvent
interface ActivityEvent {
  icon: LucideIconData
  sentence: string
  time: string
}
Order
interface Order {
  id: string
  amount: string
  status: 'Paid' | 'Pending' | 'Refunded'
}

Files installed (2)

  • src/app/components/blocks/record-detail-page/record-detail-page.component.ts12 kB
    // Admin record detail page: breadcrumb header with status badge and Edit / Delete
    // actions, two-column layout with a summary card (avatar, identity, key metrics),
    // Details / Activity tabs (labeled description-list sections and an icon timeline feed),
    // plus a side panel with owner metadata, relative timestamps, tag badges, and a
    // recent-orders mini list. All data is hardcoded inline for you to replace.
    // Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import {
      Building2,
      ChevronRight,
      CreditCard,
      FileText,
      LucideAngularModule,
      Mail,
      MessageSquare,
      Pencil,
      Plus,
      ReceiptText,
      Trash2,
      UserPlus,
      type LucideIconData,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } from '@/ui/avatar/avatar.component'
    import { UiBadgeComponent } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTabsComponent,
      UiTabsContentComponent,
      UiTabsListComponent,
      UiTabsTriggerComponent,
    } from '@/ui/tabs/tabs.component'
    
    interface DataListRow {
      label: string
      value: string
    }
    
    interface Section {
      title: string
      icon: LucideIconData
      rows: DataListRow[]
    }
    
    interface ActivityEvent {
      icon: LucideIconData
      sentence: string
      time: string
    }
    
    interface Order {
      id: string
      amount: string
      status: 'Paid' | 'Pending' | 'Refunded'
    }
    
    const summaryRows: DataListRow[] = [
      { label: 'Account ID', value: 'CUS-004182' },
      { label: 'Plan', value: 'Business annual' },
      { label: 'Lifetime value', value: '$48,200' },
    ]
    
    const sections: Section[] = [
      {
        title: 'Contact',
        icon: Mail,
        rows: [
          { label: 'Email', value: '[email protected]' },
          { label: 'Phone', value: '+1 (415) 555-0134' },
          { label: 'Website', value: 'acmecorp.com' },
          { label: 'Address', value: '500 Market St, San Francisco, CA' },
        ],
      },
      {
        title: 'Company',
        icon: Building2,
        rows: [
          { label: 'Industry', value: 'Logistics & supply chain' },
          { label: 'Employees', value: '240–500' },
          { label: 'Tax ID', value: 'US-84-2917365' },
          { label: 'Payment terms', value: 'Net 30' },
        ],
      },
    ]
    
    const activity: ActivityEvent[] = [
      { icon: ReceiptText, sentence: 'Invoice INV-2091 paid — $12,400', time: '2h ago' },
      { icon: MessageSquare, sentence: 'Priya left a note on the renewal deal', time: 'Yesterday' },
      { icon: UserPlus, sentence: 'Dana Whitfield added as billing contact', time: '3d ago' },
      { icon: FileText, sentence: 'Contract Q3-amendment.pdf uploaded', time: '1w ago' },
    ]
    
    const tags = ['enterprise', 'renewal-q4', 'priority-support']
    
    const orders: Order[] = [
      { id: 'ORD-8841', amount: '$12,400.00', status: 'Paid' },
      { id: 'ORD-8790', amount: '$3,180.50', status: 'Pending' },
      { id: 'ORD-8612', amount: '$940.00', status: 'Refunded' },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-record-detail-page, [ui-record-detail-page]',
      standalone: true,
      // The block renders its own root <div>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiSeparatorComponent,
        UiTabsComponent,
        UiTabsContentComponent,
        UiTabsListComponent,
        UiTabsTriggerComponent,
      ],
      template: `
        <div data-slot="record-detail-page" [class]="rootClass">
          <div class="flex flex-wrap items-center justify-between gap-4">
            <div class="flex min-w-0 items-center gap-1.5">
              <button ui-button variant="link" size="sm" class="text-muted-foreground h-auto min-h-6 px-0">
                Customers
              </button>
              <lucide-icon [img]="ChevronRight" class="text-muted-foreground size-4 shrink-0" aria-hidden="true" />
              <span class="truncate text-sm font-semibold">Acme Corp</span>
              <span ui-badge variant="secondary" class="ml-2">Active</span>
            </div>
            <div class="flex items-center gap-2">
              <button ui-button variant="outline" size="sm">
                <lucide-icon [img]="Pencil" class="size-4" aria-hidden="true" />
                Edit
              </button>
              <button ui-button variant="outline" size="sm" class="text-destructive hover:text-destructive">
                <lucide-icon [img]="Trash2" class="size-4" aria-hidden="true" />
                Delete
              </button>
            </div>
          </div>
    
          <div class="mt-6 grid gap-6 lg:grid-cols-3">
            <div class="space-y-6 lg:col-span-2">
              <div ui-card>
                <div ui-card-header class="flex-row items-center gap-4 space-y-0">
                  <ui-avatar size="2xl" class="size-14">
                    <ui-avatar-image src="https://i.pravatar.cc/112?img=32" alt="Acme Corp logo" />
                    <ui-avatar-fallback>AC</ui-avatar-fallback>
                  </ui-avatar>
                  <div class="min-w-0">
                    <h3 ui-card-title class="text-lg">Acme Corp</h3>
                    <p ui-card-description class="truncate">billing&#64;acmecorp.com · Customer since March 2023</p>
                  </div>
                </div>
                <div ui-card-content>
                  <ui-separator class="mb-4" />
                  <dl class="grid gap-x-8 gap-y-3 sm:grid-cols-3">
                    @for (row of summaryRows; track row.label) {
                      <div>
                        <dt class="text-muted-foreground text-xs">{{ row.label }}</dt>
                        <dd class="mt-0.5 text-sm font-medium">{{ row.value }}</dd>
                      </div>
                    }
                  </dl>
                </div>
              </div>
    
              <ui-tabs defaultValue="details">
                <ui-tabs-list>
                  <button ui-tabs-trigger value="details">Details</button>
                  <button ui-tabs-trigger value="activity">Activity</button>
                </ui-tabs-list>
    
                <ui-tabs-content value="details" class="space-y-6 pt-4">
                  @for (section of sections; track section.title) {
                    <section>
                      <h3 class="flex items-center gap-2 text-sm font-semibold">
                        <lucide-icon [img]="section.icon" class="text-muted-foreground size-4" aria-hidden="true" />
                        {{ section.title }}
                      </h3>
                      <dl class="border-border mt-3 divide-y rounded-lg border">
                        @for (row of section.rows; track row.label) {
                          <div class="px-4 py-2.5 sm:flex sm:items-center sm:gap-4">
                            <dt class="text-muted-foreground text-xs sm:w-36 sm:text-sm">{{ row.label }}</dt>
                            <dd class="mt-0.5 text-sm font-medium sm:mt-0">{{ row.value }}</dd>
                          </div>
                        }
                      </dl>
                    </section>
                  }
                </ui-tabs-content>
    
                <ui-tabs-content value="activity" class="pt-4">
                  <ol
                    class="before:bg-border relative space-y-6 before:absolute before:inset-y-1 before:left-[15px] before:w-px"
                  >
                    @for (event of activity; track event.sentence) {
                      <li class="relative flex items-start gap-4 pl-0">
                        <span
                          class="bg-background ring-border relative z-10 flex size-8 shrink-0 items-center justify-center rounded-full ring-1"
                        >
                          <lucide-icon [img]="event.icon" class="text-muted-foreground size-4" aria-hidden="true" />
                        </span>
                        <div class="min-w-0 pt-1">
                          <p class="truncate text-sm">{{ event.sentence }}</p>
                          <time class="text-muted-foreground text-xs">{{ event.time }}</time>
                        </div>
                      </li>
                    }
                  </ol>
                </ui-tabs-content>
              </ui-tabs>
            </div>
    
            <div class="space-y-6">
              <div ui-card>
                <div ui-card-header>
                  <h3 ui-card-title class="text-sm font-medium">Record meta</h3>
                </div>
                <div ui-card-content class="space-y-4">
                  <div class="flex items-center gap-3">
                    <ui-avatar class="size-8">
                      <ui-avatar-image src="https://i.pravatar.cc/64?img=47" alt="Priya Raman" />
                      <ui-avatar-fallback>PR</ui-avatar-fallback>
                    </ui-avatar>
                    <div class="min-w-0">
                      <p class="text-muted-foreground text-xs">Owner</p>
                      <p class="truncate text-sm font-medium">Priya Raman</p>
                    </div>
                  </div>
                  <ui-separator />
                  <dl class="space-y-3">
                    <div class="flex items-center justify-between gap-4">
                      <dt class="text-muted-foreground text-xs">Created</dt>
                      <dd class="text-sm font-medium">Mar 14, 2023</dd>
                    </div>
                    <div class="flex items-center justify-between gap-4">
                      <dt class="text-muted-foreground text-xs">Updated</dt>
                      <dd class="text-sm font-medium">2 hours ago</dd>
                    </div>
                  </dl>
                  <ui-separator />
                  <div>
                    <p class="text-muted-foreground mb-2 text-xs">Tags</p>
                    <div class="flex flex-wrap gap-1.5">
                      @for (tag of tags; track tag) {
                        <span ui-badge variant="secondary">{{ tag }}</span>
                      }
                    </div>
                  </div>
                </div>
              </div>
    
              <div ui-card>
                <div ui-card-header class="flex-row items-center justify-between space-y-0">
                  <h3 ui-card-title class="text-sm font-medium">Recent orders</h3>
                  <button ui-button variant="ghost" size="icon" aria-label="View all orders">
                    <lucide-icon [img]="CreditCard" class="size-4" aria-hidden="true" />
                  </button>
                </div>
                <div ui-card-content>
                  <ul class="divide-y">
                    @for (order of orders; track order.id) {
                      <li class="flex items-center gap-3 py-2.5 first:pt-0 last:pb-0">
                        <span class="font-mono text-xs">{{ order.id }}</span>
                        <span class="text-muted-foreground ml-auto text-sm tabular-nums">{{ order.amount }}</span>
                        <span
                          ui-badge
                          [variant]="
                            order.status === 'Paid' ? 'default' : order.status === 'Pending' ? 'secondary' : 'outline'
                          "
                          class="min-w-[72px] justify-center"
                        >
                          {{ order.status }}
                        </span>
                      </li>
                    }
                  </ul>
                  <button ui-button variant="outline" size="sm" class="mt-4 w-full">
                    <lucide-icon [img]="Plus" class="size-4" aria-hidden="true" />
                    New order
                  </button>
                </div>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiRecordDetailPageComponent {
      protected readonly ChevronRight = ChevronRight
      protected readonly CreditCard = CreditCard
      protected readonly Pencil = Pencil
      protected readonly Plus = Plus
      protected readonly Trash2 = Trash2
      protected readonly summaryRows = summaryRows
      protected readonly sections = sections
      protected readonly activity = activity
      protected readonly tags = tags
      protected readonly orders = orders
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('bg-background text-foreground w-full', this.className)
      }
    }
    
  • src/app/components/blocks/record-detail-page/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/record-detail-page.json