Menu

Change language

GitHub repo

Vendor Supplier Portal

blockdashboard

Supplier and vendor relationship workbench with SLA scorecard metrics, purchase order status, and contracted pricing catalog.

Installation

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

Variants

Loading interactive previews…

Schema

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

VendorPO
interface VendorPO {
  id: string
  poNumber: string
  issueDate: string
  deliveryDeadline: string
  linesCount: number
  totalAmount: number
  status: 'acknowledged' | 'in_production' | 'shipped' | 'pending_ack'
}
ContractedCatalogItem
interface ContractedCatalogItem {
  id: string
  sku: string
  name: string
  moq: number
  contractedPrice: number
  leadTimeDays: number
  validUntil: string
}

npm dependencies

Files installed (2)

  • src/app/components/blocks/vendor-supplier-portal/vendor-supplier-portal.component.ts12.7 kB
    import { Component, Input, signal, ChangeDetectionStrategy } from '@angular/core'
    import { Download, FileSpreadsheet, LucideAngularModule, ShieldCheck } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      UiCardFooterComponent,
      UiCardHeaderComponent,
      UiCardTitleComponent,
    } from '@/ui/card/card.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export interface VendorPO {
      id: string
      poNumber: string
      issueDate: string
      deliveryDeadline: string
      linesCount: number
      totalAmount: number
      status: 'acknowledged' | 'in_production' | 'shipped' | 'pending_ack'
    }
    
    export interface ContractedCatalogItem {
      id: string
      sku: string
      name: string
      moq: number
      contractedPrice: number
      leadTimeDays: number
      validUntil: string
    }
    
    export interface VendorPortalProps {
      vendorName?: string
      vendorId?: string
      tier?: string
      className?: string
    }
    
    export type VendorPortalTab = 'orders' | 'catalog'
    
    /**
     * Angular port of the React VendorSupplierPortal block: a supplier SLA scorecard with an
     * active purchase order console (acknowledge pending POs) and a contracted catalog price list.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-vendor-supplier-portal, [ui-vendor-supplier-portal]',
      standalone: true,
      host: {
        '[attr.data-slot]': '"vendor-supplier-portal"',
        '[class]': 'hostClass',
      },
      imports: [
        LucideAngularModule,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardFooterComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <!-- Header / Supplier SLA Scorecard -->
        <div ui-card>
          <div ui-card-header class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
            <div class="space-y-1">
              <div class="flex items-center gap-2">
                <span class="text-foreground font-mono text-sm font-semibold">{{ vendorId }}</span>
                <span ui-badge variant="outline">{{ tier }}</span>
                <span ui-badge variant="secondary" class="gap-1">
                  <lucide-icon [img]="ShieldCheck" class="text-foreground size-3" />
                  ISO 9001:2015 Certified
                </span>
              </div>
              <h3 ui-card-title class="text-xl">{{ vendorName }}</h3>
              <p ui-card-description>Supplier SLA Performance Scorecard &amp; Active Purchase Order Console</p>
            </div>
    
            <div class="flex items-center gap-2">
              <button ui-button variant="outline" size="sm" class="gap-1.5">
                <lucide-icon [img]="FileSpreadsheet" class="size-3.5" />
                Export Statement
              </button>
              <button ui-button variant="default" size="sm" class="gap-1.5">
                <lucide-icon [img]="Download" class="size-3.5" />
                Download MSA Contract
              </button>
            </div>
          </div>
    
          <div ui-card-content class="border-border grid grid-cols-1 gap-4 border-t pt-4 sm:grid-cols-4">
            <!-- On Time Delivery -->
            <div class="border-border space-y-1 rounded-lg border p-3">
              <div class="text-muted-foreground text-xs font-medium">ON-TIME DELIVERY (OTD)</div>
              <div class="flex items-baseline justify-between pt-1">
                <div class="font-mono text-xl font-semibold">98.6%</div>
                <span class="text-muted-foreground font-mono text-xs">Target: 95.0%</span>
              </div>
            </div>
    
            <!-- QA Acceptance -->
            <div class="border-border space-y-1 rounded-lg border p-3">
              <div class="text-muted-foreground text-xs font-medium">QA ACCEPTANCE RATE</div>
              <div class="flex items-baseline justify-between pt-1">
                <div class="font-mono text-xl font-semibold">99.8%</div>
                <span class="text-muted-foreground font-mono text-xs">&lt; 0.2% NCR</span>
              </div>
            </div>
    
            <!-- Avg Lead Time -->
            <div class="border-border space-y-1 rounded-lg border p-3">
              <div class="text-muted-foreground text-xs font-medium">AVG FULFILLMENT LEAD TIME</div>
              <div class="pt-1 font-mono text-xl font-semibold">5.4 Days</div>
            </div>
    
            <!-- Total YTD Volume -->
            <div class="border-border space-y-1 rounded-lg border p-3">
              <div class="text-muted-foreground text-xs font-medium">ACTIVE PURCHASE VOLUME</div>
              <div class="pt-1 font-mono text-xl font-semibold">$150,100 USD</div>
            </div>
          </div>
        </div>
    
        <!-- Navigation Tabs & Content Card -->
        <div ui-card>
          <div ui-card-header class="flex flex-col gap-3 pb-3 sm:flex-row sm:items-center sm:justify-between">
            <div class="flex items-center gap-2">
              <button
                ui-button
                [variant]="activeTab() === 'orders' ? 'default' : 'outline'"
                size="sm"
                (click)="activeTab.set('orders')"
              >
                Active Purchase Orders ({{ orders().length }})
              </button>
              <button
                ui-button
                [variant]="activeTab() === 'catalog' ? 'default' : 'outline'"
                size="sm"
                (click)="activeTab.set('catalog')"
              >
                Contracted Catalog Prices ({{ catalog.length }})
              </button>
            </div>
    
            <div class="text-muted-foreground text-xs">
              Payment Terms: <span class="text-foreground font-medium">Net-45 Direct ACH Wire</span>
            </div>
          </div>
    
          <div ui-card-content class="p-0">
            <!-- Table for Orders -->
            @if (activeTab() === 'orders') {
              <div ui-table>
                <thead ui-table-header>
                  <tr ui-table-row>
                    <th ui-table-head class="w-[180px]">PO Reference</th>
                    <th ui-table-head>Issue Date</th>
                    <th ui-table-head>Delivery Deadline</th>
                    <th ui-table-head class="text-right">Line Items</th>
                    <th ui-table-head class="text-right">Total Order Value</th>
                    <th ui-table-head>Fulfillment Status</th>
                    <th ui-table-head class="w-[140px] text-right">Action</th>
                  </tr>
                </thead>
                <tbody ui-table-body>
                  @for (order of orders(); track order.id) {
                    <tr ui-table-row>
                      <td ui-table-cell>
                        <div class="text-foreground font-mono font-semibold">{{ order.poNumber }}</div>
                      </td>
    
                      <td ui-table-cell class="text-muted-foreground font-mono text-xs">{{ order.issueDate }}</td>
    
                      <td ui-table-cell class="text-foreground font-mono text-xs font-medium">
                        {{ order.deliveryDeadline }}
                      </td>
    
                      <td ui-table-cell class="text-right font-mono text-sm">{{ order.linesCount }} SKUs</td>
    
                      <td ui-table-cell class="text-foreground text-right font-mono text-sm font-semibold">
                        \${{ formatAmount(order.totalAmount) }} USD
                      </td>
    
                      <td ui-table-cell>
                        <span ui-badge [variant]="statusVariant(order.status)" class="capitalize">
                          {{ statusLabel(order.status) }}
                        </span>
                      </td>
    
                      <td ui-table-cell class="text-right">
                        @if (order.status === 'pending_ack') {
                          <button
                            ui-button
                            size="sm"
                            variant="default"
                            class="h-7 text-xs"
                            (click)="acknowledgePO(order.id)"
                          >
                            Acknowledge PO
                          </button>
                        } @else {
                          <span class="text-muted-foreground text-xs">Processed</span>
                        }
                      </td>
                    </tr>
                  }
                </tbody>
              </div>
            } @else {
              <div ui-table>
                <thead ui-table-header>
                  <tr ui-table-row>
                    <th ui-table-head class="w-[280px]">Product / Description</th>
                    <th ui-table-head>SKU Reference</th>
                    <th ui-table-head class="text-right">Min Order Qty (MOQ)</th>
                    <th ui-table-head class="text-right">Contracted Unit Price</th>
                    <th ui-table-head class="text-right">Standard Lead Time</th>
                    <th ui-table-head>Contract Validity</th>
                  </tr>
                </thead>
                <tbody ui-table-body>
                  @for (item of catalog; track item.id) {
                    <tr ui-table-row>
                      <td ui-table-cell>
                        <div class="text-foreground font-medium">{{ item.name }}</div>
                      </td>
    
                      <td ui-table-cell>
                        <span class="font-mono text-xs font-semibold">{{ item.sku }}</span>
                      </td>
    
                      <td ui-table-cell class="text-right font-mono text-sm">{{ item.moq }} units</td>
    
                      <td ui-table-cell class="text-foreground text-right font-mono text-sm font-semibold">
                        \${{ item.contractedPrice.toFixed(2) }}
                      </td>
    
                      <td ui-table-cell class="text-muted-foreground text-right font-mono text-sm">
                        {{ item.leadTimeDays }} Days
                      </td>
    
                      <td ui-table-cell>
                        <span class="text-muted-foreground font-mono text-xs">Valid to {{ item.validUntil }}</span>
                      </td>
                    </tr>
                  }
                </tbody>
              </div>
            }
          </div>
    
          <div
            ui-card-footer
            class="border-border text-muted-foreground flex items-center justify-between border-t py-3 text-xs"
          >
            <div>Supplier contract SLA terms audited quarterly per ISO 9001 compliance standards.</div>
            <div class="text-foreground font-medium">
              Vendor Status: <span class="text-foreground">Active &amp; In Good Standing</span>
            </div>
          </div>
        </div>
      `,
    })
    export class UiVendorSupplierPortalComponent {
      @Input() vendorName = 'Apex Precision Engineering Ltd'
      @Input() vendorId = 'VND-2026-9901'
      @Input() tier = 'Strategic Tier-1 Supplier'
      @Input('class') className?: string
    
      protected readonly Download = Download
      protected readonly FileSpreadsheet = FileSpreadsheet
      protected readonly ShieldCheck = ShieldCheck
    
      readonly activeTab = signal<VendorPortalTab>('orders')
    
      readonly orders = signal<VendorPO[]>([
        {
          id: 'po-1',
          poNumber: 'PO-2026-8840',
          issueDate: '2026-08-18',
          deliveryDeadline: '2026-08-28',
          linesCount: 4,
          totalAmount: 48500,
          status: 'in_production',
        },
        {
          id: 'po-2',
          poNumber: 'PO-2026-8855',
          issueDate: '2026-08-22',
          deliveryDeadline: '2026-09-02',
          linesCount: 2,
          totalAmount: 19200,
          status: 'pending_ack',
        },
        {
          id: 'po-3',
          poNumber: 'PO-2026-8812',
          issueDate: '2026-08-10',
          deliveryDeadline: '2026-08-21',
          linesCount: 6,
          totalAmount: 82400,
          status: 'shipped',
        },
      ])
    
      readonly catalog: ContractedCatalogItem[] = [
        {
          id: 'cat-1',
          sku: 'SKU-VALVE-99',
          name: 'High-Pressure Hydraulic Valve 3/8"',
          moq: 50,
          contractedPrice: 185.0,
          leadTimeDays: 7,
          validUntil: '2027-06-30',
        },
        {
          id: 'cat-2',
          sku: 'SKU-SEAL-04',
          name: 'Fluorocarbon O-Ring Flange Kit (Pack of 50)',
          moq: 100,
          contractedPrice: 24.5,
          leadTimeDays: 3,
          validUntil: '2027-06-30',
        },
        {
          id: 'cat-3',
          sku: 'SKU-GAUGE-12',
          name: 'Digital Calibration Pressure Gauge 0-600 PSI',
          moq: 20,
          contractedPrice: 420.0,
          leadTimeDays: 14,
          validUntil: '2027-06-30',
        },
      ]
    
      get hostClass(): string {
        return cn('block w-full space-y-6', this.className)
      }
    
      acknowledgePO(id: string): void {
        this.orders.update((prev) =>
          prev.map((order) => (order.id === id ? { ...order, status: 'acknowledged' as const } : order)),
        )
      }
    
      statusVariant(status: VendorPO['status']): BadgeVariant {
        return status === 'shipped' ? 'default' : status === 'in_production' ? 'secondary' : 'outline'
      }
    
      statusLabel(status: VendorPO['status']): string {
        return status.replace('_', ' ')
      }
    
      formatAmount(amount: number): string {
        return amount.toLocaleString()
      }
    }
    
  • src/app/components/blocks/vendor-supplier-portal/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/vendor-supplier-portal.json