Menu

Change language

GitHub repo

Performance Review Matrix

blockhr

360-degree employee performance review scorecard and competency matrix: executive metrics, self vs manager rating comparison, detailed competency accordion with manager remarks, OKR progress table, and peer feedback quotes.

Installation

$npx uipkge-ng@latest add performance-review-matrix
Manifest:https://uipkge.dev/r/angular/performance-review-matrix.jsonInstalls to:src/app/components/blocks/performance-review-matrix/

Variants

Loading interactive previews…

Schema

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

MetricTile
interface MetricTile {
  title: string
  value: string
  subtitle: string
  progress: number
  icon: unknown
}
AssessmentPillar
interface AssessmentPillar {
  title: string
  selfScore: number
  managerScore: number
  delta: string
  status: 'consensus' | 'above'
}
CompetencyCategory
interface CompetencyCategory {
  id: string
  title: string
  score: string
  ratingLabel: string
  badgeVariant: 'success' | 'default' | 'secondary'
  progress: number
  criteria: string
  managerRemarks: string
  strengths: string[]
}
OKRItem
interface OKRItem {
  id: string
  title: string
  description: string
  target: string
  progress: number
  status: 'Completed' | 'Exceeded' | 'On Track'
  statusVariant: 'success' | 'default' | 'secondary'
}
PeerFeedback
interface PeerFeedback {
  id: string
  quote: string
  author: string
  role: string
  category: string
}

Files installed (2)

  • src/app/components/blocks/performance-review-matrix/performance-review-matrix.component.ts22.1 kB
    import { ChangeDetectionStrategy, Component, Input } from '@angular/core'
    import {
      Award,
      Calendar,
      CheckCircle2,
      FileDown,
      LucideAngularModule,
      MessageSquareQuote,
      Target,
      TrendingUp,
      Users,
      Zap,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    import {
      UiAccordionComponent,
      UiAccordionContentComponent,
      UiAccordionItemComponent,
      UiAccordionTriggerComponent,
    } from '@/ui/accordion/accordion.component'
    import { UiAvatarComponent, UiAvatarFallbackComponent, UiAvatarImageComponent } 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 { UiSeparatorComponent } from '@/ui/separator/separator.component'
    import {
      UiTableBodyComponent,
      UiTableCellComponent,
      UiTableComponent,
      UiTableHeadComponent,
      UiTableHeaderComponent,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    interface MetricTile {
      title: string
      value: string
      subtitle: string
      progress: number
      icon: unknown
    }
    
    interface AssessmentPillar {
      title: string
      selfScore: number
      managerScore: number
      delta: string
      status: 'consensus' | 'above'
    }
    
    interface CompetencyCategory {
      id: string
      title: string
      score: string
      ratingLabel: string
      badgeVariant: 'success' | 'default' | 'secondary'
      progress: number
      criteria: string
      managerRemarks: string
      strengths: string[]
    }
    
    interface OKRItem {
      id: string
      title: string
      description: string
      target: string
      progress: number
      status: 'Completed' | 'Exceeded' | 'On Track'
      statusVariant: 'success' | 'default' | 'secondary'
    }
    
    interface PeerFeedback {
      id: string
      quote: string
      author: string
      role: string
      category: string
    }
    
    const metricTiles: MetricTile[] = [
      { title: 'Core Goals Achieved', value: '94%', subtitle: '+4% vs H1 target', progress: 94, icon: Target },
      { title: 'Technical Leadership', value: '4.8 / 5.0', subtitle: 'Top 5% org percentile', progress: 96, icon: Award },
      { title: 'Culture & Mentorship', value: '4.7 / 5.0', subtitle: '3 mentees promoted', progress: 94, icon: Users },
      { title: 'Execution Velocity', value: '4.5 / 5.0', subtitle: '18 epics delivered', progress: 90, icon: Zap },
    ]
    
    const assessmentPillars: AssessmentPillar[] = [
      {
        title: 'Technical Excellence & Architecture',
        selfScore: 4.8,
        managerScore: 5.0,
        delta: '+0.2 Manager higher',
        status: 'above',
      },
      {
        title: 'Collaboration & Mentorship',
        selfScore: 4.5,
        managerScore: 4.5,
        delta: '0.0 Full consensus',
        status: 'consensus',
      },
      {
        title: 'Strategic Impact & Roadmapping',
        selfScore: 4.2,
        managerScore: 4.5,
        delta: '+0.3 Manager higher',
        status: 'above',
      },
      {
        title: 'Execution Velocity & Delivery',
        selfScore: 4.5,
        managerScore: 4.5,
        delta: '0.0 Full consensus',
        status: 'consensus',
      },
    ]
    
    const competencyCategories: CompetencyCategory[] = [
      {
        id: 'tech-excellence',
        title: 'Technical Excellence & Architecture',
        score: '5.0 / 5.0',
        ratingLabel: 'Outstanding (5.0 / 5.0)',
        badgeVariant: 'success',
        progress: 100,
        criteria:
          'Architects resilient, distributed frontend systems. Sets technical standard across multiple squads and prevents architectural debt.',
        managerRemarks:
          'Led the migration to micro-frontends with 0 downtime. Spearheaded the core component architecture and established strict bundle size budgets across the team, lowering bundle footprint by 38%.',
        strengths: [
          'Zero-downtime micro-frontend rollout',
          'Distributed caching architecture',
          'Framework token standardization',
        ],
      },
      {
        id: 'collab-mentorship',
        title: 'Collaboration & Mentorship',
        score: '4.5 / 5.0',
        ratingLabel: 'Exceeds (4.5 / 5.0)',
        badgeVariant: 'default',
        progress: 90,
        criteria:
          'Actively grows engineering talent, fosters inclusive code review practices, and unblocks cross-discipline initiatives.',
        managerRemarks:
          'Mentored 3 junior engineers to promotion. Fostered an open engineering culture, instituted weekly architecture reviews, and unblocked cross-functional teams across APAC and EMEA.',
        strengths: ['Promotion sponsorship (3 engineers)', 'Weekly architecture reviews', 'Cross-squad pairing cohorts'],
      },
      {
        id: 'strategic-impact',
        title: 'Strategic Impact & Roadmapping',
        score: '4.5 / 5.0',
        ratingLabel: 'Exceeds (4.5 / 5.0)',
        badgeVariant: 'default',
        progress: 90,
        criteria:
          'Aligns engineering strategy with quarterly business goals. Identifies high-ROI technical initiatives and champions developer productivity.',
        managerRemarks:
          'Key driver of developer experience KPIs. Aligned product roadmaps with technical debt reduction, reducing build times by 42% across CI and accelerating shipping velocity.',
        strengths: ['Developer experience KPIs', 'CI turnaround optimization (-42%)', 'Quarterly tech debt roadmap'],
      },
    ]
    
    const okrItems: OKRItem[] = [
      {
        id: 'okr-1',
        title: 'Micro-Frontend Migration Phase II',
        description: 'Decompose legacy monolithic checkout and account dashboards into standalone remote modules',
        target: '100% modules · 0 downtime',
        progress: 100,
        status: 'Completed',
        statusVariant: 'success',
      },
      {
        id: 'okr-2',
        title: 'CI Pipeline & Build Acceleration',
        description: 'Optimize build caching, monorepo graph execution, and parallelize end-to-end test suites',
        target: '< 4.0m build time (Achieved 3.2m)',
        progress: 100,
        status: 'Exceeded',
        statusVariant: 'success',
      },
      {
        id: 'okr-3',
        title: 'Design System Token Federation',
        description: 'Automate Tailwind OKLCH token sync between Figma variables and registry codebases',
        target: 'Full Vue & React parity',
        progress: 85,
        status: 'On Track',
        statusVariant: 'default',
      },
      {
        id: 'okr-4',
        title: 'Staff Mentorship & Tech Talks',
        description: 'Host bi-weekly deep dives on web performance, distributed state, and accessibility standards',
        target: '12 sessions · 3 mentees',
        progress: 100,
        status: 'Completed',
        statusVariant: 'secondary',
      },
    ]
    
    const peerFeedbacks: PeerFeedback[] = [
      {
        id: 'peer-1',
        quote:
          'Elena’s architectural guidance transformed our release velocity. Her deep dive sessions on performance optimization helped the frontend team cut latency in half.',
        author: 'Anonymous Peer',
        role: 'Staff Frontend Engineer · Checkout Core',
        category: 'Peer Review · Systems',
      },
      {
        id: 'peer-2',
        quote:
          'Mentoring from Elena was the highlight of my year. Her patient code reviews and architecture walkthroughs gave me the confidence to lead our squad’s largest migration.',
        author: 'Direct Mentee',
        role: 'Software Engineer II · Design Systems',
        category: 'Mentee Review · Growth',
      },
      {
        id: 'peer-3',
        quote:
          'Elena bridges technical complexity with product goals effortlessly. She spots risks weeks in advance and consistently delivers robust, high-impact platform features ahead of schedule.',
        author: 'Cross-Functional Partner',
        role: 'Principal Product Manager · Core Platform',
        category: 'Partner Review · Strategy',
      },
    ]
    
    /**
     * PerformanceReviewMatrix -- the React block, 1:1: employee header with overall rating,
     * executive scorecard tiles, self-vs-manager calibration pillars, a competency accordion
     * with manager remarks, an OKR progress table, and 360 peer feedback quotes.
     */
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-performance-review-matrix, [ui-performance-review-matrix]',
      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,
        UiAccordionComponent,
        UiAccordionContentComponent,
        UiAccordionItemComponent,
        UiAccordionTriggerComponent,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardDescriptionComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiProgressComponent,
        UiSeparatorComponent,
        UiTableComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableHeadComponent,
        UiTableHeaderComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="performance-review-matrix" [class]="rootClass">
          <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="flex flex-col gap-4 sm:flex-row sm:items-center">
                  <ui-avatar class="border-border size-16 shrink-0 border sm:size-20">
                    <ui-avatar-image
                      src="https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=200&auto=format&fit=crop&q=80"
                      alt="Elena Rostova"
                    />
                    <ui-avatar-fallback>ER</ui-avatar-fallback>
                  </ui-avatar>
                  <div class="space-y-1.5">
                    <div class="flex flex-wrap items-center gap-2">
                      <h2 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">Elena Rostova</h2>
                      <span ui-badge variant="secondary" class="font-medium">Senior Staff Engineer</span>
                    </div>
                    <p class="text-muted-foreground text-sm">Core Platform & Architecture · Engineering</p>
                    <div class="flex flex-wrap items-center gap-3 pt-1">
                      <span ui-badge variant="outline" class="gap-1.5 text-xs font-normal">
                        <lucide-icon [img]="Calendar" class="size-3.5" />
                        H2 2026 Review Cycle
                      </span>
                      <ui-separator orientation="vertical" class="hidden h-3.5 sm:block" />
                      <span class="text-muted-foreground text-xs">
                        Reviewer: <strong class="text-foreground font-medium">Manager: Marcus Vance</strong>
                      </span>
                    </div>
                  </div>
                </div>
                <div class="flex flex-wrap items-center gap-3 sm:self-start lg:self-center">
                  <span ui-badge variant="success" class="gap-1.5 px-3.5 py-1.5 text-sm font-semibold shadow-xs">
                    <lucide-icon [img]="Award" class="size-4 shrink-0" />
                    Exceeds Expectations · 4.6 / 5.0
                  </span>
                  <button ui-button variant="outline" size="sm" class="gap-2 shadow-xs">
                    <lucide-icon [img]="FileDown" class="size-4" />
                    Export PDF Report
                  </button>
                </div>
              </div>
            </div>
          </div>
    
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            @for (tile of metricTiles; track tile.title) {
              <div ui-card class="border-border bg-card shadow-xs">
                <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">{{ tile.title }}</h3>
                  <lucide-icon [img]="asIcon(tile.icon)" class="text-muted-foreground size-4" />
                </div>
                <div ui-card-content class="space-y-2">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums sm:text-3xl">
                    {{ tile.value }}
                  </div>
                  <div class="space-y-1.5">
                    <ui-progress [value]="tile.progress" class="h-1.5 w-full" />
                    <div class="flex items-center gap-1">
                      <lucide-icon [img]="TrendingUp" class="text-success size-3" />
                      <p class="text-muted-foreground text-xs">{{ tile.subtitle }}</p>
                    </div>
                  </div>
                </div>
              </div>
            }
          </div>
    
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="flex flex-col gap-2 pb-4 sm:flex-row sm:items-center sm:justify-between">
              <div>
                <h3 ui-card-title class="text-base font-semibold">Self-Assessment vs Manager Assessment</h3>
                <p ui-card-description class="text-xs">Consensus and score calibration across core performance pillars.</p>
              </div>
              <span ui-badge variant="outline" class="gap-1.5 text-xs font-normal">
                <lucide-icon [img]="CheckCircle2" class="text-success size-3.5" />
                96% Consensus Index
              </span>
            </div>
            <div ui-card-content class="space-y-4">
              <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
                @for (pillar of assessmentPillars; track pillar.title) {
                  <div class="bg-muted/40 border-border space-y-2.5 rounded-lg border p-3.5">
                    <div class="flex items-start justify-between gap-2">
                      <span class="text-foreground line-clamp-1 text-xs font-medium">{{ pillar.title }}</span>
                      <span
                        ui-badge
                        [variant]="pillar.status === 'consensus' ? 'secondary' : 'success'"
                        class="px-1.5 py-0 text-xs font-normal"
                      >
                        {{ pillar.delta }}
                      </span>
                    </div>
                    <div class="space-y-1.5">
                      <div class="flex items-center justify-between text-xs">
                        <span class="text-muted-foreground">Self:</span>
                        <span class="text-foreground font-semibold tabular-nums">{{ pillar.selfScore }} / 5.0</span>
                      </div>
                      <ui-progress [value]="(pillar.selfScore / 5) * 100" class="h-1.5 w-full" />
                    </div>
                    <div class="space-y-1.5">
                      <div class="flex items-center justify-between text-xs">
                        <span class="text-muted-foreground">Manager:</span>
                        <span class="text-foreground font-semibold tabular-nums">{{ pillar.managerScore }} / 5.0</span>
                      </div>
                      <ui-progress [value]="(pillar.managerScore / 5) * 100" class="h-1.5 w-full" />
                    </div>
                  </div>
                }
              </div>
            </div>
          </div>
    
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <h3 ui-card-title class="text-base font-semibold">Competency Breakdown</h3>
              <p ui-card-description class="text-xs">
                Detailed behavioral criteria, manager qualitative remarks, and observable strengths.
              </p>
            </div>
            <div ui-card-content>
              <ui-accordion type="multiple" [defaultValue]="openCompetencies" class="space-y-3">
                @for (category of competencyCategories; track category.id) {
                  <ui-accordion-item [value]="category.id" class="border-border bg-card overflow-hidden rounded-lg border">
                    <button ui-accordion-trigger class="px-5 py-4 hover:no-underline">
                      <div class="flex w-full flex-wrap items-center justify-between gap-3 pr-2 text-left">
                        <div class="space-y-0.5">
                          <span class="text-foreground text-sm font-semibold">{{ category.title }}</span>
                          <p class="text-muted-foreground line-clamp-1 text-xs">{{ category.criteria }}</p>
                        </div>
                        <div class="flex flex-wrap items-center gap-3">
                          <span class="text-foreground shrink-0 text-xs font-semibold tabular-nums">{{
                            category.score
                          }}</span>
                          <span ui-badge [variant]="category.badgeVariant" class="text-xs">{{ category.ratingLabel }}</span>
                        </div>
                      </div>
                    </button>
                    <ui-accordion-content class="px-5 pt-0 pb-5">
                      <div class="border-border/60 space-y-4 border-t pt-4">
                        <div class="space-y-1.5">
                          <div class="flex items-center justify-between text-xs">
                            <span class="text-muted-foreground font-medium">Competency Mastery</span>
                            <span class="text-foreground font-semibold tabular-nums">{{ category.progress }}%</span>
                          </div>
                          <ui-progress [value]="category.progress" class="h-2 w-full" />
                        </div>
                        <div class="bg-muted/40 border-border/80 space-y-1.5 rounded-md border p-3.5">
                          <div class="flex items-center gap-1.5">
                            <lucide-icon [img]="MessageSquareQuote" class="text-primary size-4 shrink-0" />
                            <span class="text-foreground text-xs font-medium">Manager Remarks</span>
                          </div>
                          <p class="text-muted-foreground text-xs leading-relaxed">{{ category.managerRemarks }}</p>
                        </div>
                        <div class="space-y-2">
                          <span class="text-muted-foreground text-xs font-medium">Key Demonstrated Strengths</span>
                          <div class="flex flex-wrap gap-1.5">
                            @for (strength of category.strengths; track strength) {
                              <span ui-badge variant="outline" class="text-xs font-normal">{{ strength }}</span>
                            }
                          </div>
                        </div>
                      </div>
                    </ui-accordion-content>
                  </ui-accordion-item>
                }
              </ui-accordion>
            </div>
          </div>
    
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <h3 ui-card-title class="text-base font-semibold">Goals & Objectives Progress</h3>
              <p ui-card-description class="text-xs">
                Evaluation of quarterly OKRs, measurable deliverables, and milestone completion.
              </p>
            </div>
            <div ui-card-content>
              <div class="border-border overflow-hidden rounded-lg border">
                <div class="overflow-x-auto">
                  <table ui-table>
                    <thead ui-table-header class="bg-muted/50">
                      <tr ui-table-row>
                        <th ui-table-head class="text-xs">Objective & Key Result</th>
                        <th ui-table-head class="text-xs">Target Metric</th>
                        <th ui-table-head class="w-44 text-xs">Progress</th>
                        <th ui-table-head class="text-right text-xs">Status</th>
                      </tr>
                    </thead>
                    <tbody ui-table-body>
                      @for (okr of okrItems; track okr.id) {
                        <tr ui-table-row>
                          <td ui-table-cell class="py-3">
                            <div class="space-y-0.5">
                              <p class="text-foreground text-sm font-medium">{{ okr.title }}</p>
                              <p class="text-muted-foreground text-xs">{{ okr.description }}</p>
                            </div>
                          </td>
                          <td ui-table-cell class="text-muted-foreground py-3 text-xs tabular-nums">{{ okr.target }}</td>
                          <td ui-table-cell class="py-3">
                            <div class="flex items-center gap-2">
                              <ui-progress [value]="okr.progress" class="h-2 w-24" />
                              <span class="text-muted-foreground text-xs font-medium tabular-nums"
                                >{{ okr.progress }}%</span
                              >
                            </div>
                          </td>
                          <td ui-table-cell class="py-3 text-right">
                            <span ui-badge [variant]="okr.statusVariant" class="text-xs">{{ okr.status }}</span>
                          </td>
                        </tr>
                      }
                    </tbody>
                  </table>
                </div>
              </div>
            </div>
          </div>
    
          <div ui-card class="border-border bg-card shadow-xs">
            <div ui-card-header class="pb-4">
              <h3 ui-card-title class="text-base font-semibold">360° Peer Feedback</h3>
              <p ui-card-description class="text-xs">
                Synthesized qualitative reviews from cross-functional peers, mentees, and collaborators.
              </p>
            </div>
            <div ui-card-content>
              <div class="grid grid-cols-1 gap-4 lg:grid-cols-3">
                @for (peer of peerFeedbacks; track peer.id) {
                  <div class="bg-muted/30 border-border flex flex-col justify-between space-y-3 rounded-lg border p-4.5">
                    <div class="space-y-2">
                      <span ui-badge variant="outline" class="text-xs font-normal">{{ peer.category }}</span>
                      <p class="text-foreground text-xs leading-relaxed italic">“{{ peer.quote }}”</p>
                    </div>
                    <div class="border-border/60 border-t pt-3">
                      <p class="text-foreground text-xs font-semibold">{{ peer.author }}</p>
                      <p class="text-muted-foreground text-xs">{{ peer.role }}</p>
                    </div>
                  </div>
                }
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiPerformanceReviewMatrixComponent {
      protected readonly Award = Award
      protected readonly Calendar = Calendar
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly FileDown = FileDown
      protected readonly MessageSquareQuote = MessageSquareQuote
      protected readonly TrendingUp = TrendingUp
    
      @Input('class') className?: string
    
      protected readonly metricTiles = metricTiles
      protected readonly assessmentPillars = assessmentPillars
      protected readonly competencyCategories = competencyCategories
      protected readonly okrItems = okrItems
      protected readonly peerFeedbacks = peerFeedbacks
      protected readonly openCompetencies = ['tech-excellence', 'collab-mentorship', 'strategic-impact']
    
      get rootClass(): string {
        return cn('w-full space-y-6', this.className)
      }
    
      asIcon(icon: unknown): never {
        return icon as never
      }
    }
    
  • src/app/components/blocks/performance-review-matrix/index.ts0.1 kB

Raw manifest:https://uipkge.dev/r/angular/performance-review-matrix.json