Ai Crm Pipeline Dashboard
blockmarketingHigh-craft AI CRM deal pipeline, deal velocity, AI win probability scoring, and sales activity tracker inspired by Wise / revenue intelligence platforms. Features revenue velocity metrics, 4-stage pipeline Kanban board with predictive win scores, deal progress bars, and AI meeting summaries with buying intent detection.
Installation
$pnpm dlx uipkge-ng@latest add ai-crm-pipeline-dashboard$npx uipkge-ng@latest add ai-crm-pipeline-dashboard$yarn dlx uipkge-ng@latest add ai-crm-pipeline-dashboard$bunx uipkge-ng@latest add ai-crm-pipeline-dashboardManifest:
https://uipkge.dev/r/angular/ai-crm-pipeline-dashboard.jsonInstalls to:src/app/components/blocks/ai-crm-pipeline-dashboard/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.
DealIteminterface DealItem {
id: string
company: string
companyInitials: string
avatarBg: string
dealName: string
tier: 'Enterprise' | 'Mid-Market' | 'Growth'
value: number
valueFormatted: string
stageId: PipelineStageId
stageName: string
stageProgress: number
aiWinScore: number
contactName: string
contactRole: string
ownerName: string
ownerInitials: string
closeDate: string
daysToClose: string
nextStep: string
isWinReady?: boolean
activityCount: number
}StageSummaryinterface StageSummary {
id: PipelineStageId
name: string
dealCountText: string
totalValueText: string
avgAiScoreText: string
accentColor: string
badgeVariant: string
progressValue: number
description: string
}MeetingInsightinterface MeetingInsight {
id: string
company: string
companyInitials: string
avatarBg: string
dealName: string
meetingTitle: string
timeAgo: string
duration: string
platform: string
attendees: string[]
buyingIntentScore: number
buyingIntentLabel: string
sentimentScore: string
keyTakeaways: string
aiRecommendedAction: string
primaryActionLabel: string
}Files installed (2)
src/app/components/blocks/ai-crm-pipeline-dashboard/ai-crm-pipeline-dashboard.component.ts46.4 kB
// High-craft AI CRM deal pipeline: revenue velocity metrics, 4-stage Kanban board // with predictive win scores, deal progress bars, and AI meeting summaries with // buying intent detection. Port of the React block 1:1. import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core' import { BarChart3, Bot, BrainCircuit, CheckCircle2, DollarSign, Download, FileCheck, Kanban, LucideAngularModule, Plus, Search, Sparkles, Table as TableIcon, Timer, TrendingUp, User, Video, Wand2, Zap, } 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, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, } from '@/ui/card/card.component' import { UiProgressComponent } from '@/ui/progress/progress.component' import { UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, } from '@/ui/table/table.component' export type PipelineStageId = 'discovery' | 'demo' | 'proposal' | 'negotiation' export type PipelineViewMode = 'kanban' | 'table' export type PipelineStageFilter = 'all' | PipelineStageId export interface DealItem { id: string company: string companyInitials: string avatarBg: string dealName: string tier: 'Enterprise' | 'Mid-Market' | 'Growth' value: number valueFormatted: string stageId: PipelineStageId stageName: string stageProgress: number aiWinScore: number contactName: string contactRole: string ownerName: string ownerInitials: string closeDate: string daysToClose: string nextStep: string isWinReady?: boolean activityCount: number } export interface StageSummary { id: PipelineStageId name: string dealCountText: string totalValueText: string avgAiScoreText: string accentColor: string badgeVariant: string progressValue: number description: string } export interface MeetingInsight { id: string company: string companyInitials: string avatarBg: string dealName: string meetingTitle: string timeAgo: string duration: string platform: string attendees: string[] buyingIntentScore: number buyingIntentLabel: string sentimentScore: string keyTakeaways: string aiRecommendedAction: string primaryActionLabel: string } const defaultStages: StageSummary[] = [ { id: 'discovery', name: 'Discovery & Qualification', dealCountText: '6 deals', totalValueText: '$240k', avgAiScoreText: 'AI score 42%', accentColor: 'bg-info', badgeVariant: 'bg-info/10 text-info border-info/20', progressValue: 25, description: 'Initial ICP qualification, technical scoping, and business fit discovery', }, { id: 'demo', name: 'Technical Demo & Evaluation', dealCountText: '8 deals', totalValueText: '$480k', avgAiScoreText: 'AI score 68%', accentColor: 'bg-chart-1', badgeVariant: 'bg-chart-1/10 text-chart-1 border-chart-1/20', progressValue: 50, description: 'Deep-dive sandbox environment, latency benchmarks, and API trials', }, { id: 'proposal', name: 'Executive Proposal & Security', dealCountText: '6 deals', totalValueText: '$520k', avgAiScoreText: 'AI score 84%', accentColor: 'bg-warning', badgeVariant: 'bg-warning/10 text-warning border-warning/20', progressValue: 75, description: 'Security & InfoSec clearance, CFO business case, and custom licensing', }, { id: 'negotiation', name: 'Contract Negotiation & Closing', dealCountText: '4 deals', totalValueText: '$188k', avgAiScoreText: 'AI score 94%', accentColor: 'bg-success', badgeVariant: 'bg-success/10 text-success border-success/20', progressValue: 100, description: 'Master service agreement redlines, final DPA signoff, and onboarding kickoff', }, ] const defaultDeals: DealItem[] = [ { id: 'deal-1', company: 'Acme Corp', companyInitials: 'AC', avatarBg: 'bg-info/15 text-info', dealName: 'Acme Corp - Enterprise 500 Seats', tier: 'Enterprise', value: 120000, valueFormatted: '$120,000 ARR', stageId: 'discovery', stageName: 'Discovery & Qualification', stageProgress: 25, aiWinScore: 48, contactName: 'David Chen', contactRole: 'VP Eng', ownerName: 'Sarah Jenkins (AE)', ownerInitials: 'SJ', closeDate: 'Aug 28', daysToClose: '7 days', nextStep: 'Complete Security Questionnaire review', activityCount: 4, }, { id: 'deal-2', company: 'HyperScale Cloud', companyInitials: 'HC', avatarBg: 'bg-info/15 text-info', dealName: 'HyperScale Cloud - Infra Observability', tier: 'Growth', value: 75000, valueFormatted: '$75,000 ARR', stageId: 'discovery', stageName: 'Discovery & Qualification', stageProgress: 25, aiWinScore: 42, contactName: 'Elena Rostova', contactRole: 'Head of DevOps', ownerName: 'Michael Chang (AE)', ownerInitials: 'MC', closeDate: 'Sep 14', daysToClose: '24 days', nextStep: 'Deliver architectural scoping document', activityCount: 3, }, { id: 'deal-3', company: 'Global Logistics Hub', companyInitials: 'GL', avatarBg: 'bg-cyan-500/15 text-cyan-600 dark:text-cyan-400', dealName: 'Global Logistics - Fleet Telematics', tier: 'Mid-Market', value: 45000, valueFormatted: '$45,000 ARR', stageId: 'discovery', stageName: 'Discovery & Qualification', stageProgress: 25, aiWinScore: 36, contactName: 'Marcus Brody', contactRole: 'Dir. Operations', ownerName: 'Jessica Wu (AE)', ownerInitials: 'JW', closeDate: 'Sep 22', daysToClose: '32 days', nextStep: 'Schedule technical discovery sync', activityCount: 2, }, { id: 'deal-4', company: 'QuantumPay', companyInitials: 'QP', avatarBg: 'bg-chart-1/15 text-chart-1', dealName: 'QuantumPay - Core Banking Modernization', tier: 'Enterprise', value: 160000, valueFormatted: '$160,000 ARR', stageId: 'demo', stageName: 'Technical Demo & Evaluation', stageProgress: 50, aiWinScore: 74, contactName: 'Amina Al-Mansoor', contactRole: 'CTO', ownerName: 'Sarah Jenkins (AE)', ownerInitials: 'SJ', closeDate: 'Sep 08', daysToClose: '18 days', nextStep: 'VPC sandbox load testing & latency trial', activityCount: 6, }, { id: 'deal-5', company: 'Orbit Data Systems', companyInitials: 'OD', avatarBg: 'bg-chart-2/15 text-chart-2', dealName: 'Orbit Data - Analytics Fabric 2.0', tier: 'Enterprise', value: 135000, valueFormatted: '$135,000 ARR', stageId: 'demo', stageName: 'Technical Demo & Evaluation', stageProgress: 50, aiWinScore: 68, contactName: 'Liam O’Connor', contactRole: 'VP Data Systems', ownerName: 'Carlos Mendez (AE)', ownerInitials: 'CM', closeDate: 'Sep 18', daysToClose: '28 days', nextStep: 'Deliver custom throughput benchmark report', activityCount: 5, }, { id: 'deal-6', company: 'CyberGuard Defense', companyInitials: 'CG', avatarBg: 'bg-chart-3/15 text-chart-3', dealName: 'CyberGuard - SIEM Log Ingestion', tier: 'Enterprise', value: 185000, valueFormatted: '$185,000 ARR', stageId: 'demo', stageName: 'Technical Demo & Evaluation', stageProgress: 50, aiWinScore: 65, contactName: 'Rachel Vance', contactRole: 'CISO', ownerName: 'Michael Chang (AE)', ownerInitials: 'MC', closeDate: 'Sep 30', daysToClose: '40 days', nextStep: 'InfoSec team architecture deep-dive', activityCount: 4, }, { id: 'deal-7', company: 'Vertex Bio Labs', companyInitials: 'VB', avatarBg: 'bg-warning/15 text-warning', dealName: 'Vertex Bio - C-Suite Platform Rollout', tier: 'Enterprise', value: 210000, valueFormatted: '$210,000 ARR', stageId: 'proposal', stageName: 'Executive Proposal & Security', stageProgress: 75, aiWinScore: 88, contactName: 'Dr. Julian Sterling', contactRole: 'Chief Sci Officer', ownerName: 'Emily Thorne (AE)', ownerInitials: 'ET', closeDate: 'Aug 30', daysToClose: '9 days', nextStep: 'Final CFO 2-year commitment signoff', activityCount: 8, }, { id: 'deal-8', company: 'Horizon FinServ', companyInitials: 'HF', avatarBg: 'bg-orange-500/15 text-orange-600 dark:text-orange-400', dealName: 'Horizon FinServ - Wealth Management Suite', tier: 'Enterprise', value: 190000, valueFormatted: '$190,000 ARR', stageId: 'proposal', stageName: 'Executive Proposal & Security', stageProgress: 75, aiWinScore: 84, contactName: 'Nathalie Dupont', contactRole: 'Managing Director', ownerName: 'Sarah Jenkins (AE)', ownerInitials: 'SJ', closeDate: 'Sep 04', daysToClose: '14 days', nextStep: 'Vendor risk committee formal approval', activityCount: 7, }, { id: 'deal-9', company: 'Pulse Healthcare', companyInitials: 'PH', avatarBg: 'bg-destructive/15 text-destructive', dealName: 'Pulse Health - Telehealth Core Engine', tier: 'Growth', value: 120000, valueFormatted: '$120,000 ARR', stageId: 'proposal', stageName: 'Executive Proposal & Security', stageProgress: 75, aiWinScore: 82, contactName: 'Dr. Alan Moore', contactRole: 'Chief Med Officer', ownerName: 'Carlos Mendez (AE)', ownerInitials: 'CM', closeDate: 'Sep 12', daysToClose: '22 days', nextStep: 'Execute HIPAA BAA & data privacy rider', activityCount: 5, }, { id: 'deal-10', company: 'Nova Fintech', companyInitials: 'NF', avatarBg: 'bg-success/15 text-success', dealName: 'Nova Fintech - Multi-Region VPC Latency', tier: 'Enterprise', value: 98000, valueFormatted: '$98,000 ARR', stageId: 'negotiation', stageName: 'Contract Negotiation & Closing', stageProgress: 100, aiWinScore: 96, isWinReady: true, contactName: 'Samantha Ray', contactRole: 'Head of Procurement', ownerName: 'Emily Thorne (AE)', ownerInitials: 'ET', closeDate: 'Aug 24', daysToClose: '3 days', nextStep: 'DocuSign envelope awaiting CEO countersignature', activityCount: 11, }, { id: 'deal-11', company: 'Apex AI Labs', companyInitials: 'AA', avatarBg: 'bg-teal-500/15 text-teal-600 dark:text-teal-400', dealName: 'Apex AI - Model Orchestration Cluster', tier: 'Growth', value: 90000, valueFormatted: '$90,000 ARR', stageId: 'negotiation', stageName: 'Contract Negotiation & Closing', stageProgress: 100, aiWinScore: 92, isWinReady: true, contactName: 'Vikram Patel', contactRole: 'VP Infrastructure', ownerName: 'Michael Chang (AE)', ownerInitials: 'MC', closeDate: 'Aug 26', daysToClose: '5 days', nextStep: 'Send customer onboarding package & kickoff invite', activityCount: 9, }, ] const defaultMeetingInsights: MeetingInsight[] = [ { id: 'meet-1', company: 'Acme Corp', companyInitials: 'AC', avatarBg: 'bg-info/15 text-info', dealName: 'Acme Corp - Enterprise 500 Seats', meetingTitle: 'Security & Legal Architecture Review', timeAgo: 'Today at 10:30 AM', duration: '45 mins', platform: 'Zoom Meeting', attendees: ['Sarah Jenkins (AE)', 'David Chen (VP Eng)', 'Sarah Lin (Legal Counsel)'], buyingIntentScore: 94, buyingIntentLabel: '94% High Buying Intent', sentimentScore: '+0.88 Positive', keyTakeaways: 'SOC2 Type II cleared with zero exceptions. InfoSec approved AWS eu-central-1 data residency. Legal accepted master indemnity terms with 1 minor clarification on 99.95% uptime SLA credit.', aiRecommendedAction: 'Dispatch updated Master Services Agreement (MSA) & DPA with revised 99.95% SLA clause before 4:00 PM today.', primaryActionLabel: 'Dispatch MSA & DPA', }, { id: 'meet-2', company: 'Vertex Bio Labs', companyInitials: 'VB', avatarBg: 'bg-warning/15 text-warning', dealName: 'Vertex Bio - C-Suite Platform Rollout', meetingTitle: 'CFO Business Case & ROI Alignment', timeAgo: 'Yesterday at 3:15 PM', duration: '30 mins', platform: 'Google Meet', attendees: ['Emily Thorne (AE)', 'Dr. Julian Sterling (CSO)', 'Mark Hansen (CFO)'], buyingIntentScore: 88, buyingIntentLabel: '88% Strong Buying Intent', sentimentScore: '+0.82 Positive', keyTakeaways: 'CFO Mark Hansen confirmed budget authorization for 2-year enterprise commitment ($420k TCV). Requested expedited 14-day onboarding plan for 450 laboratory technicians across Cambridge and Basel campuses.', aiRecommendedAction: 'Deliver 14-day technical migration blueprint & assign dedicated Customer Success Architect by 12:00 PM tomorrow.', primaryActionLabel: 'Send Onboarding Plan', }, { id: 'meet-3', company: 'QuantumPay', companyInitials: 'QP', avatarBg: 'bg-chart-1/15 text-chart-1', dealName: 'QuantumPay - Core Banking Modernization', meetingTitle: 'Technical Sandbox & Latency Benchmark Debrief', timeAgo: 'Aug 19 at 11:00 AM', duration: '60 mins', platform: 'In-Person Executive Sync', attendees: ['Sarah Jenkins (AE)', 'Amina Al-Mansoor (CTO)', '4 Principal Engineers'], buyingIntentScore: 78, buyingIntentLabel: '78% Moderate-High Intent', sentimentScore: '+0.74 Validated', keyTakeaways: 'Completed live latency benchmark in us-east-1 and eu-central-1 sandbox with sustained 12,500 req/sec at 11.8ms p99 latency. CTO asked for tiered discount model if expanding to APAC in Q1 2027.', aiRecommendedAction: 'Schedule executive sponsor call with VP Engineering and send multi-region Tier-1 SLA pricing options.', primaryActionLabel: 'Generate Tier-1 Quote', }, ] @Component({ changeDetection: ChangeDetectionStrategy.Eager, selector: 'ui-ai-crm-pipeline-dashboard, [ui-ai-crm-pipeline-dashboard]', 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, UiCardDescriptionComponent, UiCardHeaderComponent, UiCardTitleComponent, UiProgressComponent, UiTableBodyComponent, UiTableCellComponent, UiTableComponent, UiTableHeadComponent, UiTableHeaderComponent, UiTableRowComponent, ], template: ` <div data-slot="ai-crm-pipeline-dashboard" [class]="rootClass"> <!-- Header Section --> <header class="border-border flex flex-col gap-4 border-b pb-6 lg:flex-row lg:items-center lg:justify-between"> <div class="space-y-1.5"> <div class="flex flex-wrap items-center gap-2"> <span ui-badge wrap variant="outline" class="gap-1.5 text-xs font-medium"> <span class="bg-success size-2 animate-pulse rounded-full"></span> Enterprise Mid-Market Squad · 6 Account Executives </span> <span ui-badge wrap variant="secondary" class="gap-1 text-xs"> <lucide-icon [img]="Sparkles" class="text-primary size-3" /> Live AI Revenue Intelligence </span> </div> <h1 class="text-foreground text-2xl font-bold tracking-tight sm:text-3xl"> Sales Pipeline & Revenue Velocity </h1> <p class="text-muted-foreground text-sm"> <span class="text-foreground font-semibold tabular-nums">$1,428,500.00 Pipeline · 24 Deals</span> <span class="text-border mx-2">|</span> <span>Q3 Closed ARR Target: $1.20M (86% on-pace probability)</span> </p> </div> <div class="flex flex-wrap items-center gap-2.5"> <div class="border-border bg-muted/40 inline-flex rounded-lg border p-1"> <button type="button" [class]="viewToggleClass('kanban')" (click)="viewMode.set('kanban')"> <lucide-icon [img]="Kanban" class="size-3.5" /> Board View </button> <button type="button" [class]="viewToggleClass('table')" (click)="viewMode.set('table')"> <lucide-icon [img]="TableIcon" class="size-3.5" /> Table View </button> </div> <button ui-button aria-label="Download attachment" variant="outline" size="sm" class="gap-1.5 shadow-xs"> <lucide-icon [img]="Download" class="size-4" /> Export CRM CSV </button> <button ui-button size="sm" class="gap-1.5 shadow-xs"> <lucide-icon [img]="Plus" class="size-4" /> New Deal </button> </div> </header> <!-- 4 CRM Health KPI Cards --> <section aria-label="Pipeline Health KPI Summary" class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4"> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Total Pipeline Value </h3> <div class="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg"> <lucide-icon [img]="DollarSign" class="size-4" /> </div> </div> <div ui-card-content class="space-y-2"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">$1.43M</div> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground">Across 24 qualified deals</span> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success gap-1 text-xs font-medium" > <lucide-icon [img]="TrendingUp" class="size-3" /> +17.6% MoM </span> </div> <div class="text-muted-foreground text-xs"> Weighted Forecast: <span class="text-foreground font-semibold tabular-nums">$976,500.00</span> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Win Probability Index </h3> <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-lg"> <lucide-icon [img]="Sparkles" class="size-4" /> </div> </div> <div ui-card-content class="space-y-2"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">68.4%</div> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground">AI Win Probability</span> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success gap-1 text-xs font-medium" > <lucide-icon [img]="BrainCircuit" class="size-3" /> +5.8% shift </span> </div> <div class="text-muted-foreground text-xs"> High Confidence: <span class="text-foreground font-semibold tabular-nums">14 deals (>70% score)</span> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Avg Deal Size </h3> <div class="bg-info/10 text-info flex size-8 items-center justify-center rounded-lg"> <lucide-icon [img]="BarChart3" class="size-4" /> </div> </div> <div ui-card-content class="space-y-2"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">$59,500.00</div> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground">ARR / deal average</span> <span ui-badge wrap variant="outline" class="border-info/20 bg-info/10 text-info gap-1 text-xs font-medium" > <lucide-icon [img]="TrendingUp" class="size-3" /> +$6.2k vs FY25 </span> </div> <div class="text-muted-foreground text-xs"> Median Deal Value: <span class="text-foreground font-semibold tabular-nums">$52,000.00 ARR</span> </div> </div> </div> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="flex flex-row items-center justify-between pb-2"> <h3 ui-card-title class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Deal Velocity </h3> <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-lg"> <lucide-icon [img]="Zap" class="size-4" /> </div> </div> <div ui-card-content class="space-y-2"> <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">22 Days</div> <div class="flex items-center justify-between text-xs"> <span class="text-muted-foreground">Avg sales cycle · -4d vs Q2</span> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success gap-1 text-xs font-medium" > <lucide-icon [img]="Timer" class="size-3" /> 15.4% faster </span> </div> <div class="text-muted-foreground text-xs"> Fastest Velocity: <span class="text-foreground font-semibold tabular-nums">11d (Nova Fintech)</span> </div> </div> </div> </section> <!-- Search & Stage Filtering Bar --> <div class="border-border bg-card flex flex-col gap-3 rounded-xl border p-3.5 shadow-xs sm:flex-row sm:items-center sm:justify-between" > <div class="relative w-full max-w-sm"> <lucide-icon [img]="Search" class="text-muted-foreground absolute top-1/2 left-3 size-4 -translate-y-1/2" /> <input [value]="searchQuery()" (input)="searchQuery.set($any($event.target).value)" type="text" placeholder="Filter deals, contacts, account executives..." class="border-input bg-background text-foreground placeholder:text-muted-foreground focus-visible:ring-ring h-9 w-full rounded-md border pr-3 pl-9 text-xs focus-visible:ring-2 focus-visible:outline-none" /> </div> <div class="flex flex-wrap items-center gap-1.5"> <button type="button" [class]="stageFilterClass('all')" (click)="stageFilter.set('all')"> All Stages ({{ totalDeals }}) </button> @for (stg of stages; track stg.id) { <button type="button" [class]="stageFilterClass(stg.id)" (click)="stageFilter.set(stg.id)"> {{ stageShortName(stg) }} ({{ stageShortCount(stg) }}) </button> } </div> </div> <!-- 4-Stage Kanban / Pipeline Board --> @if (viewMode() === 'kanban') { <div class="grid grid-cols-1 items-start gap-4 md:grid-cols-2 xl:grid-cols-4"> @for (stg of stages; track stg.id; let idx = $index) { <div class="border-border/80 bg-muted/30 flex flex-col gap-3 rounded-xl border p-3"> <div class="border-border/60 bg-card space-y-2 rounded-lg border p-3 shadow-xs"> <div class="flex items-center justify-between"> <div class="flex items-center gap-2"> <span [class]="stageDotClass(stg)"></span> <h2 class="text-foreground line-clamp-1 text-xs font-semibold tracking-tight">{{ stg.name }}</h2> </div> <span ui-badge wrap variant="outline" [class]="stageBadgeClass(stg)"> {{ stg.dealCountText }} </span> </div> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span class="text-foreground font-bold tabular-nums">{{ stg.totalValueText }}</span> <span class="text-success inline-flex items-center gap-1 font-medium"> <lucide-icon [img]="Sparkles" class="size-3" /> {{ stg.avgAiScoreText }} </span> </div> <div class="space-y-1 pt-1"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Stage {{ idx + 1 }} of 4</span> <span class="text-foreground font-medium tabular-nums">{{ stg.progressValue }}%</span> </div> <ui-progress [value]="stg.progressValue" class="h-1.5" /> </div> </div> <div class="flex flex-col gap-3"> @for (deal of getStageDeals(stg.id); track deal.id) { <div role="button" tabindex="0" [attr.aria-pressed]="selectedDealId() === deal.id" [class]="dealCardClass(deal)" (click)="handleSelectDeal(deal.id)" (keydown)="onDealKeydown($event, deal.id)" > <div class="flex flex-wrap items-start justify-between gap-2"> <div class="flex items-center gap-2.5"> <ui-avatar class="border-border/50 size-8 rounded-lg border"> <ui-avatar-fallback [class]="avatarFallbackClass(deal)"> {{ deal.companyInitials }} </ui-avatar-fallback> </ui-avatar> <div> <h3 class="text-foreground text-xs font-bold">{{ deal.company }}</h3> <p class="text-muted-foreground text-xs">{{ deal.tier }}</p> </div> </div> <div class="flex flex-col items-end gap-1"> @if (deal.isWinReady) { <span ui-badge wrap variant="outline" class="border-success/30 bg-success/15 text-success gap-1 text-xs font-semibold" > <lucide-icon [img]="CheckCircle2" class="text-success size-3" /> Win Ready </span> } <span ui-badge wrap variant="outline" [class]="aiScoreBadgeClass(deal.aiWinScore)"> <lucide-icon [img]="Sparkles" class="size-3" /> {{ deal.aiWinScore }}% AI Score </span> </div> </div> <div class="space-y-1"> <p class="text-foreground line-clamp-1 text-xs font-medium tracking-tight">{{ deal.dealName }}</p> <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5"> <span class="text-foreground text-base font-bold tabular-nums">{{ deal.valueFormatted }}</span> <span class="text-muted-foreground text-xs tabular-nums"> Close: {{ deal.closeDate }} ({{ deal.daysToClose }}) </span> </div> </div> <div class="space-y-1"> <div class="text-muted-foreground flex items-center justify-between text-xs"> <span>Momentum</span> <span class="text-foreground font-medium tabular-nums">{{ deal.stageProgress }}%</span> </div> <ui-progress [value]="deal.stageProgress" class="h-1.5" /> </div> <div class="text-muted-foreground border-border/60 flex items-center justify-between border-t pt-1 text-xs" > <div class="flex items-center gap-1.5"> <lucide-icon [img]="User" class="text-muted-foreground size-3.5" /> <span class="max-w-[110px] truncate">{{ deal.contactName }} ({{ deal.contactRole }})</span> </div> <div class="flex items-center gap-1"> <span class="bg-muted text-foreground inline-flex size-5 items-center justify-center rounded-full text-xs font-semibold" > {{ deal.ownerInitials }} </span> </div> </div> <div class="border-border/60 bg-muted/40 text-muted-foreground rounded-md border p-2 text-xs"> <div class="flex items-start gap-1.5"> <lucide-icon [img]="Wand2" class="text-primary mt-0.5 size-3.5 shrink-0" /> <span class="line-clamp-2 leading-relaxed"> <strong class="text-foreground font-semibold">AI Next Action:</strong> {{ deal.nextStep }} </span> </div> </div> </div> } @if (getStageDeals(stg.id).length === 0) { <div class="border-border text-muted-foreground flex flex-col items-center justify-center rounded-lg border border-dashed p-6 text-center text-xs" > <p>No matching deals in this stage</p> </div> } </div> </div> } </div> } @else { <div class="border-border bg-card overflow-hidden rounded-xl border shadow-xs"> <div class="overflow-x-auto"> <table ui-table> <thead ui-table-header> <tr ui-table-row> <th ui-table-head class="min-w-[240px]">Account & Deal</th> <th ui-table-head>Stage</th> <th ui-table-head class="text-right">ARR Value</th> <th ui-table-head>AI Win Score</th> <th ui-table-head class="min-w-[180px]">Key Contact</th> <th ui-table-head>AE Owner</th> <th ui-table-head>Close Date</th> <th ui-table-head class="min-w-[240px]">AI Next Action</th> </tr> </thead> <tbody ui-table-body> @for (deal of filteredDeals(); track deal.id) { <tr ui-table-row tabindex="0" [class]="tableRowClass" (click)="handleSelectDeal(deal.id)" (keydown)="onDealKeydown($event, deal.id)" > <td ui-table-cell> <div class="flex items-center gap-3"> <ui-avatar class="border-border/50 size-8 rounded-lg border"> <ui-avatar-fallback [class]="avatarFallbackClass(deal)"> {{ deal.companyInitials }} </ui-avatar-fallback> </ui-avatar> <div class="space-y-0.5"> <div class="flex items-center gap-1.5"> <span class="text-foreground text-xs font-semibold">{{ deal.company }}</span> <span ui-badge wrap variant="secondary" class="px-1.5 py-0 text-xs">{{ deal.tier }}</span> @if (deal.isWinReady) { <span ui-badge wrap variant="outline" class="border-success/30 bg-success/15 text-success px-1.5 py-0 text-xs font-medium" > Win Ready </span> } </div> <p class="text-muted-foreground text-xs">{{ deal.dealName }}</p> </div> </div> </td> <td ui-table-cell> <div class="space-y-1"> <span class="text-foreground text-xs font-medium">{{ deal.stageName }}</span> <ui-progress [value]="deal.stageProgress" class="h-1.5 w-24" /> </div> </td> <td ui-table-cell class="text-foreground text-right text-xs font-bold tabular-nums"> {{ deal.valueFormatted }} </td> <td ui-table-cell> <span ui-badge wrap variant="outline" [class]="aiScoreBadgeClass(deal.aiWinScore)"> <lucide-icon [img]="Sparkles" class="size-3" /> {{ deal.aiWinScore }}% </span> </td> <td ui-table-cell> <div class="text-xs"> <p class="text-foreground font-medium">{{ deal.contactName }}</p> <p class="text-muted-foreground">{{ deal.contactRole }}</p> </div> </td> <td ui-table-cell> <span class="text-foreground text-xs font-medium">{{ deal.ownerName }}</span> </td> <td ui-table-cell> <div class="text-xs tabular-nums"> <p class="text-foreground font-medium">{{ deal.closeDate }}</p> <p class="text-muted-foreground">{{ deal.daysToClose }}</p> </div> </td> <td ui-table-cell> <div class="text-muted-foreground flex items-center gap-1.5 text-xs"> <lucide-icon [img]="Wand2" class="text-primary size-3.5 shrink-0" /> <span class="max-w-[220px] truncate">{{ deal.nextStep }}</span> </div> </td> </tr> } </tbody> </table> </div> <div class="border-border text-muted-foreground flex items-center justify-between border-t p-3 text-xs"> <span> Showing <strong class="text-foreground">{{ filteredDeals().length }}</strong> of {{ totalDeals }} deals </span> <span> Pipeline Velocity Index: <strong class="text-foreground">68.4% AI Score Average</strong> </span> </div> </div> } <!-- Recent Deal Activities & AI Meeting Insights Card --> @if (showInsights) { <section aria-label="AI Meeting Insights & Deal Activities" class="space-y-4"> <div ui-card class="border-border shadow-xs"> <div ui-card-header class="border-border flex flex-col gap-2 border-b pb-4 sm:flex-row sm:items-center sm:justify-between" > <div class="space-y-1"> <div class="flex items-center gap-2"> <div class="bg-primary/10 text-primary flex size-7 items-center justify-center rounded-md"> <lucide-icon [img]="BrainCircuit" class="size-4" /> </div> <h3 ui-card-title class="text-foreground text-base font-bold"> Recent Deal Activities & AI Meeting Insights </h3> </div> <p ui-card-description class="text-muted-foreground text-xs"> Automated multi-speaker transcript intelligence, buyer sentiment scoring, and next-best actions </p> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success gap-1.5 self-start text-xs font-semibold sm:self-center" > <lucide-icon [img]="Sparkles" class="size-3" />3 Executive Meetings Processed Today </span> </div> <div ui-card-content class="grid grid-cols-1 gap-4 pt-6 lg:grid-cols-3"> @for (insight of meetingInsights; track insight.id) { <div class="border-border bg-card hover:border-primary/40 flex flex-col justify-between space-y-4 rounded-xl border p-4 shadow-xs transition-colors" > <div class="space-y-3"> <div class="flex flex-wrap items-start justify-between gap-2"> <div class="flex items-center gap-2.5"> <ui-avatar class="border-border/60 size-9 rounded-lg border"> <ui-avatar-fallback [class]="insightAvatarClass(insight)"> {{ insight.companyInitials }} </ui-avatar-fallback> </ui-avatar> <div> <h4 class="text-foreground text-xs font-bold">{{ insight.company }}</h4> <p class="text-muted-foreground text-xs">{{ insight.platform }} · {{ insight.duration }}</p> </div> </div> <span ui-badge wrap variant="outline" class="border-success/20 bg-success/10 text-success gap-1 text-xs font-semibold tabular-nums" > <lucide-icon [img]="Sparkles" class="text-success size-3" /> {{ insight.buyingIntentLabel }} </span> </div> <div> <h5 class="text-foreground text-xs font-semibold tracking-tight">{{ insight.meetingTitle }}</h5> <p class="text-muted-foreground mt-0.5 text-xs">{{ insight.timeAgo }}</p> </div> <div class="space-y-1"> <span class="text-muted-foreground text-xs font-medium tracking-wider uppercase"> Stakeholders & Reps </span> <div class="flex flex-wrap gap-1"> @for (att of insight.attendees; track att) { <span class="bg-muted/60 text-foreground inline-flex items-center rounded-md px-2 py-0.5 text-xs" > {{ att }} </span> } </div> </div> <div class="bg-muted/40 border-border/50 space-y-1.5 rounded-lg border p-3"> <div class="flex items-center justify-between text-xs"> <span class="text-foreground flex items-center gap-1.5 font-semibold"> <lucide-icon [img]="Bot" class="text-primary size-3.5" /> AI Detected Sentiment </span> <span class="text-success text-xs font-semibold">{{ insight.sentimentScore }}</span> </div> <p class="text-muted-foreground text-xs leading-relaxed">{{ insight.keyTakeaways }}</p> </div> <div class="bg-primary/5 border-primary/20 space-y-1 rounded-lg border p-3"> <div class="text-primary flex items-center gap-1 text-xs font-semibold"> <lucide-icon [img]="Wand2" class="size-3.5" /> AI Next Recommendation </div> <p class="text-foreground text-xs leading-relaxed">{{ insight.aiRecommendedAction }}</p> </div> </div> <div class="border-border/60 flex items-center justify-between gap-2 border-t pt-2"> <button ui-button size="sm" variant="default" class="min-w-0 flex-1 gap-1.5 text-xs font-medium"> <lucide-icon [img]="FileCheck" class="size-3.5" /> {{ insight.primaryActionLabel }} </button> <button ui-button size="sm" variant="outline" class="shrink-0 gap-1 text-xs"> <lucide-icon [img]="Video" class="size-3.5" /> Recording </button> </div> </div> } </div> </div> </section> } </div> `, }) export class UiAiCrmPipelineDashboardComponent { protected readonly Bot = Bot protected readonly BrainCircuit = BrainCircuit protected readonly CheckCircle2 = CheckCircle2 protected readonly DollarSign = DollarSign protected readonly Download = Download protected readonly FileCheck = FileCheck protected readonly Kanban = Kanban protected readonly Plus = Plus protected readonly Search = Search protected readonly Sparkles = Sparkles protected readonly TableIcon = TableIcon protected readonly Timer = Timer protected readonly TrendingUp = TrendingUp protected readonly BarChart3 = BarChart3 protected readonly User = User protected readonly Video = Video protected readonly Wand2 = Wand2 protected readonly Zap = Zap protected readonly stages = defaultStages protected readonly meetingInsights = defaultMeetingInsights protected readonly totalDeals = defaultDeals.length protected readonly tableRowClass = 'hover:bg-muted/40 focus-visible:ring-ring cursor-pointer transition-colors focus-visible:ring-2 focus-visible:outline-none' @Input() set initialViewMode(v: PipelineViewMode | undefined) { this.viewMode.set(v ?? 'kanban') } @Input() set initialStageFilter(v: PipelineStageFilter | undefined) { this.stageFilter.set(v ?? 'all') } @Input() set initialSearch(v: string | undefined) { this.searchQuery.set(v ?? '') } @Input() showInsights = true @Input('class') className?: string readonly viewMode = signal<PipelineViewMode>('kanban') readonly stageFilter = signal<PipelineStageFilter>('all') readonly searchQuery = signal('') readonly selectedDealId = signal<string | null>(null) readonly filteredDeals = computed(() => { const stage = this.stageFilter() const query = this.searchQuery().trim().toLowerCase() return defaultDeals.filter((deal) => { const matchesStage = stage === 'all' || deal.stageId === stage if (!query) return matchesStage const matchesSearch = deal.company.toLowerCase().includes(query) || deal.dealName.toLowerCase().includes(query) || deal.contactName.toLowerCase().includes(query) || deal.ownerName.toLowerCase().includes(query) || deal.nextStep.toLowerCase().includes(query) return matchesStage && matchesSearch }) }) get rootClass(): string { return cn('flex w-full flex-col gap-6', this.className) } getStageDeals(stageId: PipelineStageId): DealItem[] { return this.filteredDeals().filter((deal) => deal.stageId === stageId) } stageShortName(stg: StageSummary): string { return stg.name.split(' ')[0] ?? stg.name } stageShortCount(stg: StageSummary): string { return stg.dealCountText.split(' ')[0] ?? stg.dealCountText } stageDotClass(stg: StageSummary): string { return cn('size-2.5 rounded-full', stg.accentColor) } stageBadgeClass(stg: StageSummary): string { return cn('text-xs font-semibold tabular-nums', stg.badgeVariant) } viewToggleClass(mode: PipelineViewMode): string { return cn( 'focus-visible:ring-ring inline-flex items-center gap-1.5 rounded-md px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', this.viewMode() === mode ? 'bg-background text-foreground shadow-xs' : 'text-muted-foreground hover:text-foreground', ) } stageFilterClass(filter: PipelineStageFilter): string { return cn( 'focus-visible:ring-ring rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none', this.stageFilter() === filter ? 'bg-primary text-primary-foreground' : 'bg-muted/60 text-muted-foreground hover:bg-accent hover:text-foreground', ) } dealCardClass(deal: DealItem): string { return cn( 'group border-border bg-card hover:border-primary/50 focus-visible:ring-ring relative flex cursor-pointer flex-col gap-3 rounded-lg border p-3.5 shadow-xs transition-colors duration-150 hover:shadow-sm focus-visible:ring-2 focus-visible:outline-none', this.selectedDealId() === deal.id && 'ring-primary border-primary ring-2', ) } avatarFallbackClass(deal: DealItem): string { return cn('rounded-lg text-xs font-bold', deal.avatarBg) } insightAvatarClass(insight: MeetingInsight): string { return cn('rounded-lg text-xs font-bold', insight.avatarBg) } aiScoreBadgeClass(score: number): string { return cn('gap-1 text-xs font-semibold tabular-nums', this.aiScoreClass(score)) } aiScoreClass(score: number): string { if (score >= 85) return 'bg-success/10 text-success border-success/20' if (score >= 70) return 'bg-info/10 text-info border-info/20' if (score >= 50) return 'bg-warning/10 text-warning border-warning/20' return 'bg-slate-500/10 text-slate-600 text-muted-foreground border-slate-500/20' } handleSelectDeal(id: string): void { this.selectedDealId.update((prev) => (prev === id ? null : id)) } onDealKeydown(event: KeyboardEvent, id: string): void { if (event.key === 'Enter' || event.key === ' ') { event.preventDefault() this.handleSelectDeal(id) } } }src/app/components/blocks/ai-crm-pipeline-dashboard/index.ts0.2 kB
export { UiAiCrmPipelineDashboardComponent, type DealItem, type MeetingInsight, type PipelineStageFilter, type PipelineStageId, type PipelineViewMode, type StageSummary, } from './ai-crm-pipeline-dashboard.component'
Raw manifest:https://uipkge.dev/r/angular/ai-crm-pipeline-dashboard.json