Menu

Change language

GitHub repo

Employee Pulse Survey Results

blockhr

Culture Amp style company pulse and eNPS engagement survey results dashboard featuring executive KPI scorecards, department sentiment heatmaps, and priority engagement drivers with action items.

Installation

$npx uipkge-ng@latest add employee-pulse-survey-results
Manifest:https://uipkge.dev/r/angular/employee-pulse-survey-results.jsonInstalls to:src/app/components/blocks/employee-pulse-survey-results/

Variants

Loading interactive previews…

Schema

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

PrimaryMetric
interface PrimaryMetric {
  id: string
  title: string
  value: string
  unit?: string
  badgeText: string
  badgeVariant: BadgeVariant
  detail: string
  subtext: string
  progress: number
  icon: LucideIconData
}
DepartmentRow
interface DepartmentRow {
  name: string
  responseCount: string
  participationRate: string
  cultureValues: number
  managementEffectiveness: number
  workLifeBalance: number
  compensationGrowth: number
  enpsFormatted: string
  enpsRank: string
}
DriverItem
interface DriverItem {
  id: string
  title: string
  score: string
  tag: string
  impact: string
  description: string
  benchmarkDiff: string
}
OpportunityItem
interface OpportunityItem {
  id: string
  title: string
  score: string
  statusBadge: string
  owner: string
  targetDate: string
  actionSummary: string
}

Files installed (2)

  • src/app/components/blocks/employee-pulse-survey-results/employee-pulse-survey-results.component.ts23.5 kB
    // Culture Amp style company pulse and eNPS engagement survey results dashboard with executive
    // KPI scorecards, a department sentiment heatmap, and priority engagement drivers with action
    // items. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import {
      AlertCircle,
      Award,
      FileDown,
      HeartHandshake,
      Lightbulb,
      LucideAngularModule,
      ShieldCheck,
      TrendingUp,
      Users,
      Zap,
      type LucideIconData,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import { UiBadgeComponent, type BadgeVariant } from '@/ui/badge/badge.component'
    import { UiButtonComponent } from '@/ui/button/button.component'
    import {
      UiCardComponent,
      UiCardContentComponent,
      UiCardDescriptionComponent,
      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'
    
    interface PrimaryMetric {
      id: string
      title: string
      value: string
      unit?: string
      badgeText: string
      badgeVariant: BadgeVariant
      detail: string
      subtext: string
      progress: number
      icon: LucideIconData
    }
    
    interface DepartmentRow {
      name: string
      responseCount: string
      participationRate: string
      cultureValues: number
      managementEffectiveness: number
      workLifeBalance: number
      compensationGrowth: number
      enpsFormatted: string
      enpsRank: string
    }
    
    interface DriverItem {
      id: string
      title: string
      score: string
      tag: string
      impact: string
      description: string
      benchmarkDiff: string
    }
    
    interface OpportunityItem {
      id: string
      title: string
      score: string
      statusBadge: string
      owner: string
      targetDate: string
      actionSummary: string
    }
    
    const primaryMetrics: PrimaryMetric[] = [
      {
        id: 'enps-score',
        title: 'Employee Net Promoter Score',
        value: '+54',
        unit: 'eNPS',
        badgeText: '+22 vs tech benchmark',
        badgeVariant: 'success',
        detail: '64% Promoters · 26% Passives · 10% Detractors',
        subtext: 'Benchmark: +32 eNPS across peer tech scaleups',
        progress: 77,
        icon: TrendingUp,
      },
      {
        id: 'engagement-index',
        title: 'Overall Engagement Index',
        value: '86%',
        unit: 'Favorable',
        badgeText: '+4% vs Q2',
        badgeVariant: 'success',
        detail: '86% Favorable · 10% Neutral · 4% Unfavorable',
        subtext: '4.3 / 5.0 mean agreement across all survey items',
        progress: 86,
        icon: HeartHandshake,
      },
      {
        id: 'participation-rate',
        title: 'Survey Participation Rate',
        value: '94%',
        unit: '139 / 148',
        badgeText: 'High Confidence',
        badgeVariant: 'secondary',
        detail: '94% across 5 active departments',
        subtext: 'Statistically significant sample size (p < 0.01)',
        progress: 94,
        icon: Users,
      },
      {
        id: 'retention-intent',
        title: 'Intent to Stay 2+ Years',
        value: '88%',
        unit: 'Retention',
        badgeText: '+6% vs Q2',
        badgeVariant: 'success',
        detail: '88% positive retention sentiment',
        subtext: 'Low voluntary flight risk across engineering & product',
        progress: 88,
        icon: ShieldCheck,
      },
    ]
    
    const departments: DepartmentRow[] = [
      {
        name: 'Engineering',
        responseCount: '46 / 48 responses',
        participationRate: '96%',
        cultureValues: 92,
        managementEffectiveness: 88,
        workLifeBalance: 84,
        compensationGrowth: 74,
        enpsFormatted: '+62',
        enpsRank: 'Top Quartile',
      },
      {
        name: 'Product & Design',
        responseCount: '28 / 30 responses',
        participationRate: '93%',
        cultureValues: 94,
        managementEffectiveness: 91,
        workLifeBalance: 82,
        compensationGrowth: 76,
        enpsFormatted: '+58',
        enpsRank: 'Top Quartile',
      },
      {
        name: 'Marketing',
        responseCount: '22 / 24 responses',
        participationRate: '92%',
        cultureValues: 89,
        managementEffectiveness: 85,
        workLifeBalance: 86,
        compensationGrowth: 71,
        enpsFormatted: '+48',
        enpsRank: 'Above Average',
      },
      {
        name: 'Sales & GTM',
        responseCount: '25 / 26 responses',
        participationRate: '96%',
        cultureValues: 86,
        managementEffectiveness: 84,
        workLifeBalance: 78,
        compensationGrowth: 68,
        enpsFormatted: '+45',
        enpsRank: 'Above Average',
      },
      {
        name: 'Operations',
        responseCount: '18 / 20 responses',
        participationRate: '90%',
        cultureValues: 90,
        managementEffectiveness: 87,
        workLifeBalance: 88,
        compensationGrowth: 72,
        enpsFormatted: '+52',
        enpsRank: 'Top Quartile',
      },
    ]
    
    const topDrivers: DriverItem[] = [
      {
        id: 'driver-1',
        title: 'Psychological safety & peer collaboration',
        score: '96% favorable',
        tag: 'Highest Scored Driver',
        impact: 'Highest correlation with company eNPS (+0.74 r)',
        description:
          'Squads report high mutual trust, blameless incident reviews, and transparent pairing culture without fear of failure.',
        benchmarkDiff: '+14% vs tech benchmark',
      },
      {
        id: 'driver-2',
        title: 'Mission alignment & executive transparency',
        score: '92% favorable',
        tag: 'Core Driver',
        impact: 'Strongest predictor of organizational retention',
        description:
          'Bi-weekly all-hands, transparent financials, and crisp quarterly OKRs provide unequivocal clarity on company trajectory.',
        benchmarkDiff: '+11% vs tech benchmark',
      },
      {
        id: 'driver-3',
        title: 'Manager trust & day-to-day autonomy',
        score: '90% favorable',
        tag: 'Core Driver',
        impact: 'Key catalyst for execution velocity & morale',
        description:
          'Team members highlight minimal micromanagement, supportive weekly 1:1 coaching, and autonomy over technical decisions.',
        benchmarkDiff: '+9% vs tech benchmark',
      },
    ]
    
    const opportunityAreas: OpportunityItem[] = [
      {
        id: 'opp-1',
        title: 'Clarity on promotion cycles and IC compensation bands',
        score: '68% favorable',
        statusBadge: 'Action plan active',
        owner: 'People Ops & Total Rewards',
        targetDate: 'Q4 2026 Rollout',
        actionSummary:
          'Publish transparent engineering & IC level matrices with defined salary bands, equity guidelines, and semi-annual calibration windows.',
      },
      {
        id: 'opp-2',
        title: 'Cross-functional roadmap visibility & tool sprawl',
        score: '71% favorable',
        statusBadge: 'Action plan active',
        owner: 'Product Ops & Platform Squad',
        targetDate: 'End of Q3',
        actionSummary:
          'Consolidate sprint tracking into unified Linear workspaces and establish bi-weekly cross-discipline demo showcases.',
      },
      {
        id: 'opp-3',
        title: 'Focus time & meeting cadence optimization',
        score: '73% favorable',
        statusBadge: 'Action plan active',
        owner: 'Engineering Leadership Council',
        targetDate: 'Active Trial',
        actionSummary:
          'Implement company-wide "No Meeting Wednesdays" and audit recurring status meetings in favor of asynchronous updates.',
      },
    ]
    
    function getScoreBadgeClass(score: number): string {
      if (score >= 80) return 'border-success/20 bg-success/10 text-success'
      if (score >= 70) return 'border-warning/20 bg-warning/10 text-warning'
      return 'border-destructive/20 bg-destructive/10 text-destructive'
    }
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-employee-pulse-survey-results, [ui-employee-pulse-survey-results]',
      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,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiProgressComponent,
        UiTableComponent,
        UiTableHeaderComponent,
        UiTableBodyComponent,
        UiTableRowComponent,
        UiTableHeadComponent,
        UiTableCellComponent,
      ],
      template: `
        <div data-uipkge="" data-slot="employee-pulse-survey-results" [class]="rootClass">
          <!-- Header Section -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-content class="p-6">
              <div class="flex flex-col gap-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.5">
                    <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">
                      Company Pulse &amp; eNPS Engagement Survey
                    </h2>
                    <span ui-badge variant="outline" [wrap]="true" class="font-mono text-xs">Q3 2026</span>
                  </div>
                  <p class="text-muted-foreground text-xs leading-relaxed sm:text-sm">
                    Q3 2026 Pulse Survey · 94% Participation Rate · 139 / 148 Responses
                  </p>
                </div>
    
                <div class="flex flex-wrap items-center gap-3">
                  <div
                    class="border-success/30 bg-success/10 text-success dark:text-foreground flex min-w-0 items-center gap-2 rounded-lg border px-3.5 py-2 shadow-xs"
                  >
                    <lucide-icon [img]="TrendingUp" class="text-success size-4 shrink-0" />
                    <span class="text-sm font-bold tracking-tight tabular-nums">+54 eNPS · Excellent</span>
                  </div>
                  <button ui-button variant="outline" size="sm" class="gap-2 shadow-xs">
                    <lucide-icon [img]="FileDown" class="size-4" />
                    <span>Export Executive Summary</span>
                  </button>
                </div>
              </div>
            </div>
          </div>
    
          <!-- 4 Primary Engagement KPI Cards -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            @for (metric of primaryMetrics; track metric.id) {
              <div ui-card class="border-border bg-card hover:border-border/80 shadow-xs transition-colors">
                <div ui-card-header class="flex flex-row items-center justify-between space-y-0 pb-2">
                  <h3 ui-card-title class="text-muted-foreground text-xs font-medium">{{ metric.title }}</h3>
                  <lucide-icon [img]="metric.icon" class="text-muted-foreground size-4" />
                </div>
                <div ui-card-content class="space-y-3">
                  <div class="flex flex-wrap items-baseline justify-between gap-2">
                    <div class="flex items-baseline gap-1.5">
                      <span class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">{{
                        metric.value
                      }}</span>
                      @if (metric.unit) {
                        <span class="text-muted-foreground text-xs font-medium">{{ metric.unit }}</span>
                      }
                    </div>
                    <span ui-badge [variant]="metric.badgeVariant" [wrap]="true" class="text-xs font-medium tabular-nums">{{
                      metric.badgeText
                    }}</span>
                  </div>
    
                  <div class="space-y-1.5">
                    <ui-progress [value]="metric.progress" class="h-1.5 w-full" />
                    <div class="space-y-0.5 pt-0.5">
                      <p class="text-foreground text-xs leading-normal font-medium">{{ metric.detail }}</p>
                      <p class="text-muted-foreground text-xs leading-normal">{{ metric.subtext }}</p>
                    </div>
                  </div>
                </div>
              </div>
            }
          </div>
    
          <!-- Department Sentiment Heatmap Table -->
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
                <div>
                  <div class="flex min-w-0 items-center gap-2">
                    <h3 ui-card-title class="text-base font-semibold">Department Sentiment Heatmap</h3>
                    <span ui-badge variant="outline" [wrap]="true" class="text-xs font-normal"
                      >5 Departments · 139 Responses</span
                    >
                  </div>
                  <p ui-card-description class="mt-1 text-xs">
                    Culture Amp style multidimensional sentiment matrix mapped across key operational pillars.
                  </p>
                </div>
    
                <!-- Heatmap Legend -->
                <div class="flex flex-wrap items-center gap-2 pt-1 text-xs sm:pt-0">
                  <span class="text-muted-foreground font-medium">Sentiment Scale:</span>
                  <div
                    class="border-success/20 bg-success/10 text-success flex items-center gap-1 rounded-md border px-2 py-0.5"
                  >
                    <span class="bg-success size-1.5 rounded-full"></span>
                    <span class="font-medium">Favorable (≥80%)</span>
                  </div>
                  <div
                    class="border-warning/20 bg-warning/10 text-warning flex items-center gap-1 rounded-md border px-2 py-0.5"
                  >
                    <span class="bg-warning size-1.5 rounded-full"></span>
                    <span class="font-medium">Moderate (70-79%)</span>
                  </div>
                  <div
                    class="border-destructive/20 bg-destructive/10 text-destructive flex items-center gap-1 rounded-md border px-2 py-0.5"
                  >
                    <span class="bg-destructive size-1.5 rounded-full"></span>
                    <span class="font-medium">Opportunity (&lt;70%)</span>
                  </div>
                </div>
              </div>
            </div>
    
            <div ui-card-content class="p-0">
              <div class="border-border overflow-x-auto border-t">
                <table ui-table>
                  <thead ui-table-header class="bg-muted/40">
                    <tr ui-table-row class="hover:bg-transparent">
                      <th ui-table-head class="text-foreground w-60 text-xs font-semibold">
                        Department &amp; Participation
                      </th>
                      <th ui-table-head class="text-foreground text-center text-xs font-semibold">Culture &amp; Values</th>
                      <th ui-table-head class="text-foreground text-center text-xs font-semibold">
                        Management Effectiveness
                      </th>
                      <th ui-table-head class="text-foreground text-center text-xs font-semibold">Work-Life Balance</th>
                      <th ui-table-head class="text-foreground text-center text-xs font-semibold">
                        Compensation &amp; Growth
                      </th>
                      <th ui-table-head class="text-foreground text-right text-xs font-semibold">Overall eNPS Score</th>
                    </tr>
                  </thead>
                  <tbody ui-table-body>
                    @for (dept of departments; track dept.name) {
                      <tr ui-table-row class="hover:bg-muted/30">
                        <td ui-table-cell class="py-3.5">
                          <div class="space-y-0.5">
                            <p class="text-foreground text-sm font-semibold">{{ dept.name }}</p>
                            <p class="text-muted-foreground text-xs tabular-nums">
                              {{ dept.responseCount }} ·
                              <span class="text-foreground font-medium">{{ dept.participationRate }}</span>
                            </p>
                          </div>
                        </td>
                        <td ui-table-cell class="py-3.5 text-center">
                          <div class="inline-flex flex-col items-center gap-1">
                            <div [class]="scoreBadgeClass(dept.cultureValues)">{{ dept.cultureValues }}%</div>
                          </div>
                        </td>
                        <td ui-table-cell class="py-3.5 text-center">
                          <div class="inline-flex flex-col items-center gap-1">
                            <div [class]="scoreBadgeClass(dept.managementEffectiveness)">
                              {{ dept.managementEffectiveness }}%
                            </div>
                          </div>
                        </td>
                        <td ui-table-cell class="py-3.5 text-center">
                          <div class="inline-flex flex-col items-center gap-1">
                            <div [class]="scoreBadgeClass(dept.workLifeBalance)">{{ dept.workLifeBalance }}%</div>
                          </div>
                        </td>
                        <td ui-table-cell class="py-3.5 text-center">
                          <div class="inline-flex flex-col items-center gap-1">
                            <div [class]="scoreBadgeClass(dept.compensationGrowth)">{{ dept.compensationGrowth }}%</div>
                          </div>
                        </td>
                        <td ui-table-cell class="py-3.5 text-right">
                          <div class="flex flex-col items-end gap-1">
                            <div
                              class="border-success/30 bg-success/10 text-success inline-flex items-center gap-1.5 rounded-md border px-2.5 py-1 text-xs font-bold tabular-nums"
                            >
                              <lucide-icon [img]="TrendingUp" class="text-success size-3" />
                              <span>{{ dept.enpsFormatted }} eNPS</span>
                            </div>
                            <span class="text-muted-foreground text-xs font-normal">{{ dept.enpsRank }}</span>
                          </div>
                        </td>
                      </tr>
                    }
                  </tbody>
                </table>
              </div>
            </div>
          </div>
    
          <!-- Top Engagement Drivers & Action Items -->
          <div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
            <!-- Left Column: Top Engagement Drivers -->
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="pb-4">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex min-w-0 items-center gap-2">
                      <h3 ui-card-title class="text-base font-semibold">Top Engagement Drivers</h3>
                      <span ui-badge variant="success" [wrap]="true" class="text-xs">Organizational Strengths</span>
                    </div>
                    <p ui-card-description class="text-xs">
                      Highest scored sentiment drivers providing outsized positive impact on overall eNPS.
                    </p>
                  </div>
                  <lucide-icon [img]="Award" class="text-success size-5 shrink-0" />
                </div>
              </div>
    
              <div ui-card-content class="space-y-4">
                @for (driver of topDrivers; track driver.id) {
                  <div class="bg-muted/30 border-border/80 space-y-2.5 rounded-lg border p-4 shadow-xs">
                    <div class="flex flex-wrap items-start justify-between gap-2">
                      <div class="space-y-1">
                        <div class="flex flex-wrap items-center gap-2">
                          <h4 class="text-foreground text-sm font-semibold">{{ driver.title }}</h4>
                          <span ui-badge variant="outline" [wrap]="true" class="text-xs font-normal">{{ driver.tag }}</span>
                        </div>
                        <p class="text-muted-foreground text-xs">{{ driver.impact }}</p>
                      </div>
    
                      <div
                        class="border-success/20 bg-success/10 text-success flex shrink-0 items-center gap-1 rounded-md border px-2.5 py-1 text-xs font-bold tabular-nums"
                      >
                        <lucide-icon [img]="TrendingUp" class="text-success size-3" />
                        <span>{{ driver.score }}</span>
                      </div>
                    </div>
    
                    <p class="text-muted-foreground text-xs leading-relaxed">{{ driver.description }}</p>
    
                    <div class="border-border/60 flex items-center justify-between border-t pt-2 text-xs">
                      <span class="text-muted-foreground">Peer Benchmark:</span>
                      <span class="text-success font-semibold tabular-nums">{{ driver.benchmarkDiff }}</span>
                    </div>
                  </div>
                }
              </div>
            </div>
    
            <!-- Right Column: Priority Opportunity Areas -->
            <div ui-card class="border-border bg-card shadow-xs">
              <div ui-card-header class="pb-4">
                <div class="flex flex-wrap items-center justify-between gap-2">
                  <div class="space-y-1">
                    <div class="flex min-w-0 items-center gap-2">
                      <h3 ui-card-title class="text-base font-semibold">Priority Opportunity Areas</h3>
                      <span ui-badge variant="warning" [wrap]="true" class="text-xs">Active Remediation</span>
                    </div>
                    <p ui-card-description class="text-xs">
                      Key focus areas requiring leadership follow-through and documented action plans.
                    </p>
                  </div>
                  <lucide-icon [img]="Lightbulb" class="text-warning size-5 shrink-0" />
                </div>
              </div>
    
              <div ui-card-content class="space-y-4">
                @for (opp of opportunityAreas; track opp.id) {
                  <div class="bg-muted/30 border-border/80 space-y-2.5 rounded-lg border p-4 shadow-xs">
                    <div class="flex flex-wrap items-start justify-between gap-2">
                      <div class="space-y-1">
                        <h4 class="text-foreground text-sm font-semibold">{{ opp.title }}</h4>
                        <div class="flex flex-wrap items-center gap-2">
                          <span ui-badge variant="warning" [wrap]="true" class="text-xs font-medium">{{
                            opp.statusBadge
                          }}</span>
                          <span class="text-muted-foreground text-xs">
                            Target: <strong class="text-foreground font-medium">{{ opp.targetDate }}</strong>
                          </span>
                        </div>
                      </div>
    
                      <div
                        class="border-warning/20 bg-warning/10 text-warning flex shrink-0 items-center gap-1 rounded-md border px-2.5 py-1 text-xs font-bold tabular-nums"
                      >
                        <lucide-icon [img]="AlertCircle" class="text-warning size-3" />
                        <span>{{ opp.score }}</span>
                      </div>
                    </div>
    
                    <div class="bg-card/70 border-border/60 space-y-1 rounded-md border p-2.5">
                      <div class="text-foreground flex items-center gap-1 text-xs font-medium">
                        <lucide-icon [img]="Zap" class="text-primary size-3 shrink-0" />
                        <span>Action Plan &amp; Deliverables</span>
                      </div>
                      <p class="text-muted-foreground text-xs leading-relaxed">{{ opp.actionSummary }}</p>
                    </div>
    
                    <div class="border-border/60 flex items-center justify-between border-t pt-2 text-xs">
                      <span class="text-muted-foreground">Action Owner:</span>
                      <span class="text-foreground font-medium">{{ opp.owner }}</span>
                    </div>
                  </div>
                }
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiEmployeePulseSurveyResultsComponent {
      protected readonly AlertCircle = AlertCircle
      protected readonly Award = Award
      protected readonly FileDown = FileDown
      protected readonly Lightbulb = Lightbulb
      protected readonly TrendingUp = TrendingUp
      protected readonly Zap = Zap
    
      protected readonly primaryMetrics = primaryMetrics
      protected readonly departments = departments
      protected readonly topDrivers = topDrivers
      protected readonly opportunityAreas = opportunityAreas
    
      @Input('class') className?: string
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      scoreBadgeClass(score: number): string {
        return cn(
          'flex min-w-[72px] items-center justify-center rounded-md border px-3 py-1.5 text-xs font-bold tabular-nums shadow-xs',
          getScoreBadgeClass(score),
        )
      }
    }
    
  • src/app/components/blocks/employee-pulse-survey-results/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/employee-pulse-survey-results.json