Menu

Change language

GitHub repo

Ai Crm Pipeline Dashboard

blockmarketing

High-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

$npx uipkge-ng@latest add ai-crm-pipeline-dashboard
Manifest:https://uipkge.dev/r/angular/ai-crm-pipeline-dashboard.jsonInstalls to:src/app/components/blocks/ai-crm-pipeline-dashboard/

Variants

Loading interactive previews…

Schema

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

DealItem
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
}
StageSummary
interface StageSummary {
  id: PipelineStageId
  name: string
  dealCountText: string
  totalValueText: string
  avgAiScoreText: string
  accentColor: string
  badgeVariant: string
  progressValue: number
  description: string
}
MeetingInsight
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
}

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 (&gt;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

Raw manifest:https://uipkge.dev/r/angular/ai-crm-pipeline-dashboard.json