Vendor Supplier Portal
blockdashboardSupplier and vendor relationship workbench with SLA scorecard metrics, purchase order status, and contracted pricing catalog.
Installation
$pnpm dlx uipkge-ng@latest add vendor-supplier-portal$npx uipkge-ng@latest add vendor-supplier-portal$yarn dlx uipkge-ng@latest add vendor-supplier-portal$bunx uipkge-ng@latest add vendor-supplier-portalManifest:
https://uipkge.dev/r/angular/vendor-supplier-portal.jsonInstalls to:src/app/components/blocks/vendor-supplier-portal/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
VendorPOinterface VendorPO {
id: string
poNumber: string
issueDate: string
deliveryDeadline: string
linesCount: number
totalAmount: number
status: 'acknowledged' | 'in_production' | 'shipped' | 'pending_ack'
}ContractedCatalogIteminterface 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 & 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">< 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 & 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
export { UiVendorSupplierPortalComponent, type VendorPO, type ContractedCatalogItem, type VendorPortalProps, type VendorPortalTab, } from './vendor-supplier-portal.component'
Raw manifest:https://uipkge.dev/r/angular/vendor-supplier-portal.json