Menu

Change language

GitHub repo

Recruitment Candidate Pipeline

blockhr

AI-powered applicant tracking pipeline, candidate scorecard, resume match scoring, and interview stages board.

Installation

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

Variants

Loading interactive previews…

Schema

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

CandidateCompetency
interface CandidateCompetency {
  name: string
  score: number
  benchmark: string
}
InterviewFeedback
interface InterviewFeedback {
  interviewer: string
  role: string
  score: number
  verdict: string
  feedback: string
}
CompensationModel
interface CompensationModel {
  targetBase: string
  equity: string
  signOn: string
  bonus: string
  totalYear1: string
  bandStatus: string
  noticePeriod: string
}
Candidate
interface Candidate {
  id: string
  name: string
  role: string
  currentCompany: string
  location: string
  avatar: string
  initials: string
  email: string
  appliedDate: string
  stageId: 'screening' | 'technical' | 'culture' | 'offer'
  stageName: string
  stageDuration: string
  aiMatchScore: number
  overallRating: number
  reviewCount: number
  decision: 'Strong Hire' | 'Hire' | 'Pending Offer' | 'Offer Extended'
  decisionVariant: 'success' | 'default' | 'secondary' | 'warning' | 'info'
  keySkills: string[]
  aiSummary: string
  competencies: CandidateCompetency[]
  aiHighlights: string[]
  growthAreas: string[]
  interviews: InterviewFeedback[]
  compensation: CompensationModel
}
PipelineStage
interface PipelineStage {
  id: 'screening' | 'technical' | 'culture' | 'offer'
  title: string
  stageMetric: string
  accentColor: string
  badgeVariant: 'info' | 'default' | 'warning' | 'success'
}

Files installed (2)

  • src/app/components/blocks/recruitment-candidate-pipeline/recruitment-candidate-pipeline.component.ts52.8 kB
    // AI-powered applicant tracking pipeline, candidate scorecard, resume match scoring,
    // and interview stages board. Port of the React block 1:1.
    import { ChangeDetectionStrategy, Component, Input, computed, signal } from '@angular/core'
    import {
      ArrowRight,
      Award,
      Bot,
      Briefcase,
      Building2,
      Calendar,
      Check,
      CheckCircle2,
      ChevronRight,
      Clock,
      FileDown,
      FileSpreadsheet,
      FileText,
      LucideAngularModule,
      Mail,
      MapPin,
      ShieldCheck,
      Star,
      Timer,
      UserCheck,
      UserPlus,
      Users,
    } from 'lucide-angular'
    import { cn } from '@/lib/utils'
    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,
      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,
      UiTableRowComponent,
    } from '@/ui/table/table.component'
    
    export interface CandidateCompetency {
      name: string
      score: number
      benchmark: string
    }
    
    export interface InterviewFeedback {
      interviewer: string
      role: string
      score: number
      verdict: string
      feedback: string
    }
    
    export interface CompensationModel {
      targetBase: string
      equity: string
      signOn: string
      bonus: string
      totalYear1: string
      bandStatus: string
      noticePeriod: string
    }
    
    export interface Candidate {
      id: string
      name: string
      role: string
      currentCompany: string
      location: string
      avatar: string
      initials: string
      email: string
      appliedDate: string
      stageId: 'screening' | 'technical' | 'culture' | 'offer'
      stageName: string
      stageDuration: string
      aiMatchScore: number
      overallRating: number
      reviewCount: number
      decision: 'Strong Hire' | 'Hire' | 'Pending Offer' | 'Offer Extended'
      decisionVariant: 'success' | 'default' | 'secondary' | 'warning' | 'info'
      keySkills: string[]
      aiSummary: string
      competencies: CandidateCompetency[]
      aiHighlights: string[]
      growthAreas: string[]
      interviews: InterviewFeedback[]
      compensation: CompensationModel
    }
    
    export interface PipelineStage {
      id: 'screening' | 'technical' | 'culture' | 'offer'
      title: string
      stageMetric: string
      accentColor: string
      badgeVariant: 'info' | 'default' | 'warning' | 'success'
    }
    
    const stages: PipelineStage[] = [
      {
        id: 'screening',
        title: 'Screening & AI Review',
        stageMetric: 'AI match 94%',
        accentColor: 'border-t-info',
        badgeVariant: 'info',
      },
      {
        id: 'technical',
        title: 'Technical Architecture Interview',
        stageMetric: 'Scorecard 4.8/5',
        accentColor: 'border-t-chart-1',
        badgeVariant: 'default',
      },
      {
        id: 'culture',
        title: 'Executive & Team Culture Sync',
        stageMetric: 'Strong Hire',
        accentColor: 'border-t-warning',
        badgeVariant: 'warning',
      },
      {
        id: 'offer',
        title: 'Offer Extended',
        stageMetric: '$240k Base + Equity · Pending',
        accentColor: 'border-t-success',
        badgeVariant: 'success',
      },
    ]
    
    const initialCandidates: Candidate[] = [
      {
        id: 'cand-1',
        name: 'Elena Rostova',
        role: 'Senior Staff Design Engineer',
        currentCompany: 'Former Staff at Vercel',
        location: 'San Francisco, CA (Remote)',
        avatar: 'https://images.unsplash.com/photo-1534528741775-53994a69daeb?w=150&auto=format&fit=crop&q=80',
        initials: 'ER',
        email: 'elena.rostova@designcraft.io',
        appliedDate: 'Aug 14, 2026',
        stageId: 'screening',
        stageName: 'Screening & AI Review',
        stageDuration: 'In stage for 2d',
        aiMatchScore: 96,
        overallRating: 4.9,
        reviewCount: 4,
        decision: 'Strong Hire',
        decisionVariant: 'success',
        keySkills: ['Vue 3', 'React', 'OKLCH', 'Reka UI'],
        aiSummary:
          'Exceptional system architect with proven history leading unbundled design system registries, dual-framework component parity, and headless UI primitives.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 98, benchmark: 'Top 1% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 96, benchmark: 'Top 3% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 95, benchmark: 'Top 5% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 92, benchmark: 'Top 8% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 94, benchmark: 'Top 4% candidate pool' },
        ],
        aiHighlights: [
          'Pioneered unbundled standalone component architecture serving 50k+ daily package invocations.',
          'Deep contributor to Reka UI and Radix headless accessibility layers with zero layout shifts.',
          'Championed OKLCH perceptually uniform palettes with zero contrast regressions across light & dark themes.',
        ],
        growthAreas: [
          'Prefers structured async written documentation over high-frequency sync huddles (well-aligned with remote-first culture).',
        ],
        interviews: [
          {
            interviewer: 'Alex Rivera',
            role: 'VP of Engineering',
            score: 5.0,
            verdict: 'Strong Hire',
            feedback:
              'Stunning grasp of reactivity internals, SFC compilation boundaries, and developer ergonomics. Instant technical pillar for our platform team.',
          },
          {
            interviewer: 'Sarah Chen',
            role: 'Principal Product Designer',
            score: 4.9,
            verdict: 'Strong Hire',
            feedback:
              'Pixel-perfect intuition. Deep appreciation for sub-16ms layout transitions, optical alignment, and semantic token hierarchy.',
          },
          {
            interviewer: 'Devon Vance',
            role: 'Staff Frontend Lead',
            score: 4.8,
            verdict: 'Strong Hire',
            feedback:
              'Clean API design philosophy. Strongly advocates for zero runtime bloat, tree-shakeable composables, and strict CVA variant patterns.',
          },
        ],
        compensation: {
          targetBase: '$240,000',
          equity: '$85,000 / yr (0.15% equity, 4-yr vest)',
          signOn: '$25,000',
          bonus: '$36,000 (15% target)',
          totalYear1: '$386,000',
          bandStatus: 'Within Level 7 Band ($225k – $255k)',
          noticePeriod: '2 Weeks',
        },
      },
      {
        id: 'cand-2',
        name: 'Marcus Chen',
        role: 'Senior Frontend Architect',
        currentCompany: 'Former Architect at Stripe',
        location: 'Seattle, WA (Hybrid)',
        avatar: 'https://images.unsplash.com/photo-1507003211169-0a1dd7228f2d?w=150&auto=format&fit=crop&q=80',
        initials: 'MC',
        email: 'marcus.chen@fintechcore.dev',
        appliedDate: 'Aug 12, 2026',
        stageId: 'screening',
        stageName: 'Screening & AI Review',
        stageDuration: 'In stage for 3d',
        aiMatchScore: 92,
        overallRating: 4.7,
        reviewCount: 3,
        decision: 'Hire',
        decisionVariant: 'default',
        keySkills: ['TypeScript', 'Tailwind v4', 'Accessibility', 'Turborepo'],
        aiSummary:
          'Expert in enterprise monorepo governance, automated build tooling optimization, and WCAG AA accessibility test infrastructure.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 94, benchmark: 'Top 5% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 90, benchmark: 'Top 10% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 91, benchmark: 'Top 9% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 88, benchmark: 'Top 12% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 95, benchmark: 'Top 3% candidate pool' },
        ],
        aiHighlights: [
          'Decreased monorepo CI build times by 54% through caching and Turborepo remote pipeline execution.',
          'Built automated accessibility testing suite with 99.8% regression catch rate across payment checkout flows.',
        ],
        growthAreas: [
          'Has spent less time with Vue 3.5 SFC script setup macros recently, mostly focused on React and Next.js.',
        ],
        interviews: [
          {
            interviewer: 'Alex Rivera',
            role: 'VP of Engineering',
            score: 4.8,
            verdict: 'Hire',
            feedback:
              'Very strong fundamentals on build systems, packaging, and high-reliability data tables under heavy load.',
          },
          {
            interviewer: 'Devon Vance',
            role: 'Staff Frontend Lead',
            score: 4.6,
            verdict: 'Hire',
            feedback: 'Solid architecture chops, great understanding of semantic token resolution and bundle footprint.',
          },
        ],
        compensation: {
          targetBase: '$230,000',
          equity: '$70,000 / yr',
          signOn: '$20,000',
          bonus: '$34,500 (15% target)',
          totalYear1: '$354,500',
          bandStatus: 'Within Level 7 Band ($225k – $255k)',
          noticePeriod: '3 Weeks',
        },
      },
      {
        id: 'cand-3',
        name: 'Siddharth Mehta',
        role: 'Principal Systems Engineer',
        currentCompany: 'Former Principal at Figma',
        location: 'New York, NY (Remote)',
        avatar: 'https://images.unsplash.com/photo-1500648767791-00dcc994a43e?w=150&auto=format&fit=crop&q=80',
        initials: 'SM',
        email: 'siddharth.m@figma-alumni.net',
        appliedDate: 'Aug 10, 2026',
        stageId: 'technical',
        stageName: 'Technical Architecture Interview',
        stageDuration: 'In stage for 4d',
        aiMatchScore: 95,
        overallRating: 4.9,
        reviewCount: 5,
        decision: 'Strong Hire',
        decisionVariant: 'success',
        keySkills: ['Design Systems', 'Canvas/WebGL', 'Reka UI', 'Radix'],
        aiSummary:
          'Visionary in design token compilation engines, dual-engine canvas rendering, and cross-platform design-to-code pipelines.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 97, benchmark: 'Top 2% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 93, benchmark: 'Top 6% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 98, benchmark: 'Top 1% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 95, benchmark: 'Top 3% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 96, benchmark: 'Top 2% candidate pool' },
        ],
        aiHighlights: [
          'Designed token transformation schema syncing design primitives directly to 6 distinct production platforms.',
          'Spearheaded low-latency viewport virtualization rendering 10k+ nodes at a constant 120 FPS.',
        ],
        growthAreas: ['Prefers deep architecture sprint focus over rapid day-to-day experimental marketing product churn.'],
        interviews: [
          {
            interviewer: 'Sarah Chen',
            role: 'Principal Product Designer',
            score: 5.0,
            verdict: 'Strong Hire',
            feedback: 'World-class understanding of designer-developer handoff, sub-layer nesting, and token semantics.',
          },
          {
            interviewer: 'Devon Vance',
            role: 'Staff Frontend Lead',
            score: 4.8,
            verdict: 'Strong Hire',
            feedback: 'Masterful architectural whiteboard session on headless state machines and zero-lockin registries.',
          },
        ],
        compensation: {
          targetBase: '$245,000',
          equity: '$90,000 / yr',
          signOn: '$30,000',
          bonus: '$36,750 (15% target)',
          totalYear1: '$401,750',
          bandStatus: 'Top of Level 7 Band ($225k – $255k)',
          noticePeriod: '4 Weeks',
        },
      },
      {
        id: 'cand-4',
        name: 'Amara Okafor',
        role: 'Lead UI Engineer',
        currentCompany: 'Former Lead UI at Linear',
        location: 'Austin, TX (Remote)',
        avatar: 'https://images.unsplash.com/photo-1573496359142-b8d87734a5a2?w=150&auto=format&fit=crop&q=80',
        initials: 'AO',
        email: 'amara.okafor@craftwork.io',
        appliedDate: 'Aug 08, 2026',
        stageId: 'technical',
        stageName: 'Technical Architecture Interview',
        stageDuration: 'In stage for 1d',
        aiMatchScore: 91,
        overallRating: 4.8,
        reviewCount: 4,
        decision: 'Strong Hire',
        decisionVariant: 'success',
        keySkills: ['Micro-interactions', 'Vue 3.5', 'Tailwind CSS', 'Performance'],
        aiSummary:
          'Specialist in sub-16ms keyboard-first workflows, fluid spring physics, and ultra-dense desktop data layouts.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 92, benchmark: 'Top 8% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 94, benchmark: 'Top 5% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 93, benchmark: 'Top 7% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 99, benchmark: 'Top 1% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 90, benchmark: 'Top 10% candidate pool' },
        ],
        aiHighlights: [
          'Built gesture & spring motion system with 0 runtime jank and native 120Hz responsiveness.',
          'Created command palette keyboard navigation engine benchmarked across 2M daily power-user queries.',
        ],
        growthAreas: ['Looking to broaden direct experience in large-scale compiler and AST code generation pipelines.'],
        interviews: [
          {
            interviewer: 'Alex Rivera',
            role: 'VP of Engineering',
            score: 4.8,
            verdict: 'Strong Hire',
            feedback: 'Exceptional craft and keyboard ergonomics. Perfect fit for our design engineering culture.',
          },
          {
            interviewer: 'Sarah Chen',
            role: 'Principal Product Designer',
            score: 4.9,
            verdict: 'Strong Hire',
            feedback: 'Delightful eye for interaction states, hover intents, and subtle spring tension calibrations.',
          },
        ],
        compensation: {
          targetBase: '$235,000',
          equity: '$80,000 / yr',
          signOn: '$20,000',
          bonus: '$35,250 (15% target)',
          totalYear1: '$370,250',
          bandStatus: 'Within Level 7 Band ($225k – $255k)',
          noticePeriod: '2 Weeks',
        },
      },
      {
        id: 'cand-5',
        name: 'Lucas Vance',
        role: 'Head of Design Technology',
        currentCompany: 'Former Head of Design Tech at Ramp',
        location: 'San Francisco, CA (On-site)',
        avatar: 'https://images.unsplash.com/photo-1522075469751-3a6694fb2f61?w=150&auto=format&fit=crop&q=80',
        initials: 'LV',
        email: 'lucas.vance@fintechcraft.co',
        appliedDate: 'Aug 05, 2026',
        stageId: 'culture',
        stageName: 'Executive & Team Culture Sync',
        stageDuration: 'In stage for 5d',
        aiMatchScore: 94,
        overallRating: 4.9,
        reviewCount: 6,
        decision: 'Strong Hire',
        decisionVariant: 'success',
        keySkills: ['OKLCH Tokens', 'Vue 3', 'Mentorship', 'API Design'],
        aiSummary:
          'Proven team multiplier who scaled design tech teams from 0 to 18 engineers with high trust, craft rigor, and speed.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 95, benchmark: 'Top 4% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 92, benchmark: 'Top 7% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 97, benchmark: 'Top 2% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 91, benchmark: 'Top 9% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 98, benchmark: 'Top 1% candidate pool' },
        ],
        aiHighlights: [
          'Established engineering mentorship tracks that accelerated senior promotion velocity by 40%.',
          'Co-authored foundational RFC for unified design tokens across web, iOS, and Android clients.',
        ],
        growthAreas: [
          'Best partnered with an experienced product manager for high-velocity customer roadmap prioritization.',
        ],
        interviews: [
          {
            interviewer: 'Alex Rivera',
            role: 'VP of Engineering',
            score: 5.0,
            verdict: 'Strong Hire',
            feedback: 'Incredible leader with high EQ and deep technical chops. Immediate culture and craft multiplier.',
          },
          {
            interviewer: 'Devon Vance',
            role: 'Staff Frontend Lead',
            score: 4.8,
            verdict: 'Strong Hire',
            feedback: 'Inspiring communicator with razor-sharp code review standards and pragmatic architectural empathy.',
          },
        ],
        compensation: {
          targetBase: '$240,000',
          equity: '$85,000 / yr',
          signOn: '$25,000',
          bonus: '$36,000 (15% target)',
          totalYear1: '$386,000',
          bandStatus: 'Within Level 7 Band ($225k – $255k)',
          noticePeriod: 'Immediate',
        },
      },
      {
        id: 'cand-6',
        name: 'Sofia Lindqvist',
        role: 'Staff Design Technologist',
        currentCompany: 'Former Staff at Spotify',
        location: 'Stockholm / Remote US',
        avatar: 'https://images.unsplash.com/photo-1544005313-94ddf0286df2?w=150&auto=format&fit=crop&q=80',
        initials: 'SL',
        email: 'sofia.lindqvist@audiocraft.dev',
        appliedDate: 'Aug 01, 2026',
        stageId: 'offer',
        stageName: 'Offer Extended',
        stageDuration: 'In stage for 2d (Offer valid 5d)',
        aiMatchScore: 97,
        overallRating: 5.0,
        reviewCount: 7,
        decision: 'Offer Extended',
        decisionVariant: 'success',
        keySkills: ['Vue 3', 'React', 'Design Systems', 'Craft & Motion'],
        aiSummary:
          'Flawless all-around engineering craft leader with unanimous consensus Strong Hire across 7 cross-functional panels.',
        competencies: [
          { name: 'Component Architecture & Modularity', score: 99, benchmark: 'Top 1% candidate pool' },
          { name: 'Cross-Framework Parity (Vue 3 & React)', score: 97, benchmark: 'Top 2% candidate pool' },
          { name: 'Design Tokens & OKLCH Color Systems', score: 98, benchmark: 'Top 1% candidate pool' },
          { name: 'Micro-interactions & Spring Physics', score: 96, benchmark: 'Top 2% candidate pool' },
          { name: 'RFC Authoring & Team Mentorship', score: 97, benchmark: 'Top 2% candidate pool' },
        ],
        aiHighlights: [
          'Unanimous 5.0 consensus from all 7 hiring committee and executive interviewers.',
          'Architected component registry ecosystem servicing 600M monthly active music listeners.',
        ],
        growthAreas: ['Requires CET / US-East overlap working agreement for synchronous core team design reviews.'],
        interviews: [
          {
            interviewer: 'Alex Rivera',
            role: 'VP of Engineering',
            score: 5.0,
            verdict: 'Offer Extended',
            feedback: 'One of the finest design engineering candidates we have ever evaluated. Flawless technical review.',
          },
          {
            interviewer: 'Sarah Chen',
            role: 'Principal Product Designer',
            score: 5.0,
            verdict: 'Offer Extended',
            feedback:
              'Phenomenal partner for design teams. Completely dissolves the barrier between Figma and production code.',
          },
        ],
        compensation: {
          targetBase: '$240,000',
          equity: '$85,000 / yr (4-yr vest)',
          signOn: '$25,000',
          bonus: '$36,000 (15% target)',
          totalYear1: '$386,000',
          bandStatus: 'Offer Extended ($240k Base + Equity)',
          noticePeriod: '2 Weeks',
        },
      },
    ]
    
    @Component({
      changeDetection: ChangeDetectionStrategy.Eager,
      selector: 'ui-recruitment-candidate-pipeline, [ui-recruitment-candidate-pipeline]',
      standalone: true,
      // The block renders its own root <div>: keep this wrapper out of the layout.
      host: { '[attr.class]': '"contents"' },
      imports: [
        LucideAngularModule,
        UiAvatarComponent,
        UiAvatarFallbackComponent,
        UiAvatarImageComponent,
        UiBadgeComponent,
        UiButtonComponent,
        UiCardComponent,
        UiCardContentComponent,
        UiCardHeaderComponent,
        UiCardTitleComponent,
        UiProgressComponent,
        UiSeparatorComponent,
        UiTableBodyComponent,
        UiTableCellComponent,
        UiTableComponent,
        UiTableRowComponent,
      ],
      template: `
        <div data-slot="recruitment-candidate-pipeline" [class]="rootClass">
          <!-- Header Section -->
          <div
            class="bg-card text-card-foreground border-border/80 flex flex-col justify-between gap-4 rounded-xl border p-5 shadow-xs sm:p-6 lg:flex-row lg:items-center"
          >
            <div class="space-y-1.5">
              <div class="flex flex-wrap items-center gap-2.5">
                <h1 class="text-foreground text-xl font-bold tracking-tight sm:text-2xl">Senior Staff Design Engineer</h1>
                <span ui-badge variant="secondary" class="gap-1 font-medium">
                  <lucide-icon [img]="Building2" class="text-muted-foreground size-3" />
                  Engineering &amp; Product
                </span>
                <span ui-badge variant="outline" class="border-success/30 bg-success/10 text-success gap-1 font-medium">
                  <span class="bg-success size-1.5 animate-pulse rounded-full"></span>
                  1 Open Headcount
                </span>
                <span ui-badge variant="outline" class="text-muted-foreground hidden sm:inline-flex">
                  Req: #ENG-2026-08
                </span>
              </div>
              <p class="text-muted-foreground flex flex-wrap items-center gap-x-4 gap-y-1 text-xs sm:text-sm">
                <span>AI Candidate Pipeline &amp; Interactive Talent Scorecard</span>
                <span class="text-foreground font-medium tabular-nums">48 Total Applicants</span>
                <span class="text-success text-success font-medium tabular-nums">6 Active in Pipeline</span>
                <span>Hiring Lead: Alex Rivera</span>
              </p>
            </div>
    
            <div class="flex flex-wrap items-center gap-2.5">
              <button ui-button variant="outline" size="sm" class="gap-1.5 shadow-xs">
                <lucide-icon [img]="FileDown" class="size-3.5" />
                <span>Export ATS CSV</span>
              </button>
              <button ui-button size="sm" class="gap-1.5 shadow-xs">
                <lucide-icon [img]="UserPlus" class="size-3.5" />
                <span>Add Candidate</span>
              </button>
            </div>
          </div>
    
          <!-- 4 Recruiting KPI Cards -->
          <div class="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4">
            <!-- Total Applicants -->
            <div ui-card class="border-border/80 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">Total Applicants</h3>
                <div class="bg-primary/10 text-primary flex size-8 items-center justify-center rounded-lg">
                  <lucide-icon [img]="Users" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">48</div>
                  <span
                    ui-badge
                    variant="outline"
                    class="border-success/30 bg-success/10 text-success text-success text-xs font-medium tabular-nums"
                  >
                    +28% WoW
                  </span>
                </div>
                <p class="text-muted-foreground text-xs">+14 this week · 82% qualified threshold</p>
              </div>
            </div>
    
            <!-- AI Resume Match Rate -->
            <div ui-card class="border-border/80 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">AI Resume Match Rate</h3>
                <div class="bg-success/10 text-success flex size-8 items-center justify-center rounded-lg">
                  <lucide-icon [img]="Award" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">88.4%</div>
                  <span
                    ui-badge
                    variant="outline"
                    class="border-info/30 bg-info/10 text-info text-info text-xs font-medium tabular-nums"
                  >
                    Top Quartile
                  </span>
                </div>
                <p class="text-muted-foreground text-xs">Avg match score · Claude 3.7 Sonnet analysis</p>
              </div>
            </div>
    
            <!-- Time to Hire Velocity -->
            <div ui-card class="border-border/80 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">Time to Hire Velocity</h3>
                <div class="bg-chart-1/10 text-chart-1 flex size-8 items-center justify-center rounded-lg">
                  <lucide-icon [img]="Timer" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">18 Days</div>
                  <span
                    ui-badge
                    variant="outline"
                    class="border-success/30 bg-success/10 text-success text-success text-xs font-medium tabular-nums"
                  >
                    -6d vs Q2
                  </span>
                </div>
                <p class="text-muted-foreground text-xs">Industry benchmark: 32d avg cycle</p>
              </div>
            </div>
    
            <!-- Offers Extended -->
            <div ui-card class="border-border/80 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">Offers Extended</h3>
                <div class="bg-warning/10 text-warning flex size-8 items-center justify-center rounded-lg">
                  <lucide-icon [img]="Award" class="size-4" />
                </div>
              </div>
              <div ui-card-content class="space-y-1">
                <div class="flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5">
                  <div class="text-foreground text-2xl font-bold tracking-tight tabular-nums">1 Pending</div>
                  <span
                    ui-badge
                    variant="outline"
                    class="border-warning/30 bg-warning/10 text-warning text-warning text-xs font-medium tabular-nums"
                  >
                    95% Close Prob
                  </span>
                </div>
                <p class="text-muted-foreground text-xs">$240k base + equity package under review</p>
              </div>
            </div>
          </div>
    
          <!-- 4-Stage Candidate Pipeline Board -->
          <div class="space-y-3">
            <div class="flex items-center justify-between gap-x-2">
              <div class="flex flex-wrap items-center gap-2">
                <h2 class="text-foreground text-base font-semibold">Active Interview Pipeline</h2>
                <span ui-badge variant="outline" class="text-xs tabular-nums"> 6 in Progress </span>
              </div>
              <p class="text-muted-foreground hidden text-xs sm:block">
                Select any candidate to load their detailed AI Scorecard below
              </p>
            </div>
    
            <div class="grid grid-cols-1 gap-4 md:grid-cols-2 xl:grid-cols-4">
              @for (stage of stages; track stage.id) {
                <div class="border-border/80 bg-muted/20 flex flex-col gap-3 rounded-xl border p-3.5">
                  <!-- Stage Column Header -->
                  <div class="border-border/70 border-b pb-2.5">
                    <div class="flex items-center justify-between gap-1.5">
                      <div class="flex min-w-0 items-center gap-2">
                        <span [class]="stageDotClass(stage.id)"></span>
                        <h3 class="text-foreground truncate text-xs font-semibold" [attr.title]="stage.title">
                          {{ stage.title }}
                        </h3>
                      </div>
                      <span ui-badge variant="secondary" class="shrink-0 px-1.5 py-0 text-xs tabular-nums">
                        {{ getCandidatesForStage(stage.id).length }}
                      </span>
                    </div>
                    <div class="text-muted-foreground mt-1 flex items-center justify-between gap-x-2 text-xs">
                      <span class="truncate">{{ stage.stageMetric }}</span>
                    </div>
                  </div>
    
                  <!-- Candidates list in Stage -->
                  <div class="flex flex-col gap-3">
                    @for (cand of getCandidatesForStage(stage.id); track cand.id) {
                      <div [class]="candidateCardClass(cand.id)" (click)="selectCandidate(cand.id)">
                        <!-- Candidate Top Row: Avatar + Name + AI Match Pill -->
                        <div class="flex items-start justify-between gap-2">
                          <div class="flex min-w-0 items-center gap-2.5">
                            <ui-avatar class="border-border/80 size-9 shrink-0 border">
                              <ui-avatar-image [src]="cand.avatar" [alt]="cand.name" />
                              <ui-avatar-fallback class="text-xs font-semibold">{{ cand.initials }}</ui-avatar-fallback>
                            </ui-avatar>
                            <div class="min-w-0">
                              <h4
                                class="text-foreground group-hover:text-primary truncate text-xs font-semibold transition-colors"
                              >
                                {{ cand.name }}
                              </h4>
                              <p class="text-muted-foreground truncate text-xs">
                                {{ cand.currentCompany }}
                              </p>
                            </div>
                          </div>
    
                          <!-- AI Resume Match pill -->
                          <span
                            ui-badge
                            variant="outline"
                            class="border-success/30 bg-success/10 text-success text-success shrink-0 gap-1 text-xs font-medium tabular-nums"
                          >
                            <lucide-icon [img]="CheckCircle2" class="text-success size-3" />
                            <span>{{ cand.aiMatchScore }}% Match</span>
                          </span>
                        </div>
    
                        <!-- Candidate Rating & Stage Duration -->
                        <div class="flex items-center justify-between gap-2 text-xs">
                          <div class="text-warning flex items-center gap-1 font-medium tabular-nums">
                            <lucide-icon [img]="Star" class="fill-warning text-warning size-3" />
                            <span class="text-foreground font-semibold">{{ cand.overallRating.toFixed(1) }}</span>
                            <span class="text-muted-foreground font-normal">({{ cand.reviewCount }} interviewers)</span>
                          </div>
                          <div class="text-muted-foreground flex items-center gap-1 text-xs">
                            <lucide-icon [img]="Clock" class="text-muted-foreground/80 size-3 shrink-0" />
                            <span class="truncate">{{ cand.stageDuration }}</span>
                          </div>
                        </div>
    
                        <!-- Key Skills badges -->
                        <div class="flex flex-wrap gap-1">
                          @for (skill of cand.keySkills; track skill) {
                            <span ui-badge variant="secondary" class="px-1.5 py-0 text-xs font-normal">
                              {{ skill }}
                            </span>
                          }
                        </div>
    
                        <!-- Card Action Buttons -->
                        <div class="border-border/60 flex flex-wrap items-center gap-2 border-t pt-1">
                          <button
                            ui-button
                            variant="ghost"
                            size="sm"
                            class="text-muted-foreground hover:text-foreground h-7 flex-1 justify-start gap-1 px-2 text-xs"
                            (click)="$event.stopPropagation(); selectCandidate(cand.id)"
                          >
                            <lucide-icon [img]="FileText" class="size-3" />
                            <span>View Scorecard</span>
                          </button>
    
                          <button
                            ui-button
                            variant="outline"
                            size="sm"
                            class="hover:bg-primary hover:text-primary-foreground hover:border-primary h-7 gap-1 px-2 text-xs"
                            (click)="$event.stopPropagation(); advanceCandidate(cand.id)"
                          >
                            <span>Advance</span>
                            <lucide-icon [img]="ChevronRight" class="size-3" />
                          </button>
                        </div>
                      </div>
                    }
    
                    <!-- Empty stage placeholder if all candidates moved -->
                    @if (getCandidatesForStage(stage.id).length === 0) {
                      <div
                        class="border-border/80 text-muted-foreground flex flex-col items-center justify-center rounded-lg border border-dashed p-6 text-center"
                      >
                        <lucide-icon [img]="UserCheck" class="text-muted-foreground/60 mb-1 size-5" />
                        <p class="text-xs font-medium">No candidates in this stage</p>
                      </div>
                    }
                  </div>
                </div>
              }
            </div>
          </div>
    
          <!-- Candidate Detailed AI Scorecard Card / Drawer Section -->
          <div ui-card class="border-border/80 overflow-hidden shadow-sm">
            <!-- Top Scorecard Header Bar -->
            <div class="border-border bg-muted/30 border-b p-4 sm:p-5">
              <!-- Candidate Quick Selector Bar -->
              <div class="mb-4 flex flex-wrap items-center justify-between gap-3">
                <div class="flex flex-wrap items-center gap-2">
                  <lucide-icon [img]="UserCheck" class="text-success size-4" />
                  <span class="text-muted-foreground text-xs font-semibold tracking-wider uppercase">
                    Candidate AI Scorecard &amp; Interview Matrix
                  </span>
                </div>
    
                <!-- Switcher pills for all 6 candidates -->
                <div class="flex flex-wrap items-center gap-1.5">
                  @for (c of candidates(); track c.id) {
                    <button type="button" [class]="switcherClass(c.id)" (click)="selectCandidate(c.id)">
                      <span>{{ c.name }}</span>
                      <span [class]="switcherScoreClass(c.id)">{{ c.aiMatchScore }}%</span>
                    </button>
                  }
                </div>
              </div>
    
              <!-- Selected Candidate Bio Header -->
              <div class="flex flex-col justify-between gap-4 lg:flex-row lg:items-center">
                <div class="flex items-start gap-3.5 sm:items-center">
                  <ui-avatar class="border-background size-12 shrink-0 border-2 shadow-xs">
                    <ui-avatar-image [src]="selectedCandidate().avatar" [alt]="selectedCandidate().name" />
                    <ui-avatar-fallback class="text-sm font-semibold">{{
                      selectedCandidate().initials
                    }}</ui-avatar-fallback>
                  </ui-avatar>
    
                  <div class="space-y-1">
                    <div class="flex flex-wrap items-center gap-2">
                      <h3 class="text-foreground text-lg font-bold">
                        {{ selectedCandidate().name }}
                      </h3>
                      <span
                        ui-badge
                        variant="outline"
                        class="border-success/30 bg-success/10 text-success text-success gap-1 text-xs font-medium tabular-nums"
                      >
                        <lucide-icon [img]="CheckCircle2" class="text-success size-3" />
                        {{ selectedCandidate().aiMatchScore }}% AI Fit
                      </span>
                      <span ui-badge variant="default" class="text-xs font-medium">
                        {{ selectedCandidate().decision }}
                      </span>
                    </div>
    
                    <div class="text-muted-foreground flex flex-wrap items-center gap-x-3 gap-y-1 text-xs">
                      <span class="flex items-center gap-1">
                        <lucide-icon [img]="Briefcase" class="size-3" />
                        {{ selectedCandidate().currentCompany }}
                      </span>
                      <span class="flex items-center gap-1">
                        <lucide-icon [img]="MapPin" class="size-3" />
                        {{ selectedCandidate().location }}
                      </span>
                      <span class="flex items-center gap-1">
                        <lucide-icon [img]="Mail" class="size-3" />
                        {{ selectedCandidate().email }}
                      </span>
                      <span class="flex items-center gap-1">
                        <lucide-icon [img]="Calendar" class="size-3" />
                        Applied {{ selectedCandidate().appliedDate }}
                      </span>
                    </div>
                  </div>
                </div>
    
                <!-- Header Action Buttons -->
                <div class="flex flex-wrap items-center gap-2">
                  <button
                    ui-button
                    variant="outline"
                    size="sm"
                    class="gap-1.5 text-xs shadow-xs"
                    (click)="advanceCandidate(selectedCandidate().id)"
                  >
                    <span>Advance to Next Round</span>
                    <lucide-icon [img]="ArrowRight" class="size-3.5" />
                  </button>
                  <button ui-button size="sm" class="gap-1.5 text-xs shadow-xs">
                    <lucide-icon [img]="CheckCircle2" class="size-3.5" />
                    <span>Extend Offer Letter</span>
                  </button>
                </div>
              </div>
            </div>
    
            <!-- Scorecard Content: 3-Column Grid -->
            <div ui-card-content class="space-y-6 p-5">
              <!-- AI Executive Summary Banner -->
              <div class="border-success/20 bg-success/5 flex flex-col items-start gap-3 rounded-lg border p-4 sm:flex-row">
                <div class="bg-success/10 text-success flex size-8 shrink-0 items-center justify-center rounded-md">
                  <lucide-icon [img]="Bot" class="size-4.5" />
                </div>
                <div class="space-y-1">
                  <h4 class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                    <span>AI Synthesized Resume &amp; Interview Consensus</span>
                    <span ui-badge variant="outline" class="border-success/30 text-success py-0 text-xs">
                      High Confidence (98.2%)
                    </span>
                  </h4>
                  <p class="text-muted-foreground text-xs leading-relaxed">
                    {{ selectedCandidate().aiSummary }}
                  </p>
                </div>
              </div>
    
              <div class="grid grid-cols-1 gap-6 lg:grid-cols-3">
                <!-- Column 1: AI Competency & Skill Radar Breakdown -->
                <div class="border-border/80 bg-card space-y-4 rounded-xl border p-4">
                  <div class="flex items-center justify-between gap-x-2">
                    <div>
                      <h4 class="text-foreground text-xs font-semibold">Competency Scorecard</h4>
                      <p class="text-muted-foreground text-xs">AI parsed technical signals</p>
                    </div>
                    <span ui-badge variant="secondary" class="text-xs tabular-nums">
                      {{ selectedCandidate().competencies.length }} Pillars
                    </span>
                  </div>
    
                  <div class="space-y-3.5">
                    @for (comp of selectedCandidate().competencies; track comp.name) {
                      <div class="space-y-1.5">
                        <div class="flex items-center justify-between gap-x-2 text-xs">
                          <span class="text-foreground truncate pr-2 font-medium" [attr.title]="comp.name">
                            {{ comp.name }}
                          </span>
                          <span class="text-foreground shrink-0 font-bold tabular-nums"> {{ comp.score }}% </span>
                        </div>
                        <div ui-progress [value]="comp.score" class="h-1.5"></div>
                        <div class="text-muted-foreground flex items-center justify-between gap-x-2 text-xs">
                          <span class="text-xs">{{ comp.benchmark }}</span>
                        </div>
                      </div>
                    }
                  </div>
    
                  <ui-separator class="my-3" />
    
                  <!-- AI Highlights list -->
                  <div class="space-y-2">
                    <h5 class="text-foreground flex items-center gap-1.5 text-xs font-semibold">
                      <lucide-icon [img]="Check" class="text-success size-3.5" />
                      <span>Key AI Resume Signals</span>
                    </h5>
                    <ul class="text-muted-foreground space-y-1.5 text-xs">
                      @for (highlight of selectedCandidate().aiHighlights; track highlight) {
                        <li class="flex items-start gap-1.5">
                          <span class="text-success font-bold">•</span>
                          <span>{{ highlight }}</span>
                        </li>
                      }
                    </ul>
                  </div>
    
                  <!-- Growth Areas / Calibration -->
                  <div class="bg-muted/40 border-border/60 space-y-1.5 rounded-lg border p-3 text-xs">
                    <div class="text-foreground flex items-center gap-1.5 font-medium">
                      <lucide-icon [img]="ShieldCheck" class="text-chart-1 size-3.5" />
                      <span>Interviewer Calibration Note</span>
                    </div>
                    <p class="text-muted-foreground leading-relaxed">
                      {{ selectedCandidate().growthAreas[0] }}
                    </p>
                  </div>
                </div>
    
                <!-- Column 2: Interviewer Scorecards & Qualitative Notes -->
                <div class="border-border/80 bg-card space-y-4 rounded-xl border p-4">
                  <div class="flex items-center justify-between gap-x-2">
                    <div>
                      <h4 class="text-foreground text-xs font-semibold">Interviewer Scorecards</h4>
                      <p class="text-muted-foreground text-xs">Direct debrief ratings &amp; feedback</p>
                    </div>
                    <div class="text-warning flex items-center gap-1 text-xs font-bold tabular-nums">
                      <lucide-icon [img]="Star" class="fill-warning text-warning size-3.5" />
                      <span>{{ selectedCandidate().overallRating.toFixed(1) }} / 5.0</span>
                    </div>
                  </div>
    
                  <div class="space-y-3">
                    @for (review of selectedCandidate().interviews; track review.interviewer) {
                      <div class="border-border/70 bg-muted/20 space-y-2 rounded-lg border p-3">
                        <div class="flex items-start justify-between gap-2">
                          <div>
                            <p class="text-foreground text-xs font-semibold">{{ review.interviewer }}</p>
                            <p class="text-muted-foreground text-xs">{{ review.role }}</p>
                          </div>
                          <div class="flex shrink-0 items-center gap-1.5">
                            <span
                              ui-badge
                              variant="outline"
                              class="border-success/30 bg-success/10 text-success text-xs font-medium"
                            >
                              {{ review.verdict }}
                            </span>
                            <span ui-badge variant="secondary" class="text-xs font-bold tabular-nums">
                              {{ review.score.toFixed(1) }} ★
                            </span>
                          </div>
                        </div>
                        <p class="text-muted-foreground text-xs leading-relaxed italic">“{{ review.feedback }}”</p>
                      </div>
                    }
                  </div>
    
                  <ui-separator class="my-3" />
    
                  <!-- Debrief Consensus Banner -->
                  <div
                    class="border-success/20 bg-success/10 flex items-center justify-between gap-x-2 rounded-lg border p-3 text-xs"
                  >
                    <div class="flex flex-wrap items-center gap-2">
                      <lucide-icon [img]="UserCheck" class="text-success size-4" />
                      <span class="text-foreground font-medium">Hiring Committee Consensus</span>
                    </div>
                    <span ui-badge variant="outline" class="border-success/30 text-success font-semibold">
                      Unanimous Strong Hire
                    </span>
                  </div>
                </div>
    
                <!-- Column 3: Compensation Package & Offer Structuring -->
                <div class="border-border/80 bg-card space-y-4 rounded-xl border p-4">
                  <div class="flex items-center justify-between gap-x-2">
                    <div>
                      <h4 class="text-foreground text-xs font-semibold">Compensation &amp; Package Model</h4>
                      <p class="text-muted-foreground text-xs">Target offer &amp; benchmarking</p>
                    </div>
                    <span ui-badge variant="outline" class="border-chart-1/30 bg-chart-1/10 text-chart-1 text-xs">
                      Level 7 Band
                    </span>
                  </div>
    
                  <div class="border-border/70 overflow-hidden rounded-lg border">
                    <div class="overflow-x-auto">
                      <div ui-table>
                        <tbody ui-table-body>
                          <tr ui-table-row class="hover:bg-transparent">
                            <td ui-table-cell class="text-muted-foreground py-2.5 text-xs font-medium">
                              Target Base Salary
                            </td>
                            <td ui-table-cell class="text-foreground py-2.5 text-right text-xs font-bold tabular-nums">
                              {{ selectedCandidate().compensation.targetBase }}
                            </td>
                          </tr>
                          <tr ui-table-row class="hover:bg-transparent">
                            <td ui-table-cell class="text-muted-foreground py-2.5 text-xs font-medium">
                              Equity (RSUs / Grant)
                            </td>
                            <td ui-table-cell class="text-foreground py-2.5 text-right text-xs font-bold tabular-nums">
                              {{ selectedCandidate().compensation.equity }}
                            </td>
                          </tr>
                          <tr ui-table-row class="hover:bg-transparent">
                            <td ui-table-cell class="text-muted-foreground py-2.5 text-xs font-medium">
                              Target Annual Bonus
                            </td>
                            <td ui-table-cell class="text-foreground py-2.5 text-right text-xs font-bold tabular-nums">
                              {{ selectedCandidate().compensation.bonus }}
                            </td>
                          </tr>
                          <tr ui-table-row class="hover:bg-transparent">
                            <td ui-table-cell class="text-muted-foreground py-2.5 text-xs font-medium">
                              Sign-on Incentive
                            </td>
                            <td ui-table-cell class="text-foreground py-2.5 text-right text-xs font-bold tabular-nums">
                              {{ selectedCandidate().compensation.signOn }}
                            </td>
                          </tr>
                          <tr ui-table-row class="bg-muted/40 hover:bg-muted/40 font-semibold">
                            <td ui-table-cell class="text-foreground py-2.5 text-xs">Target Year 1 OTE</td>
                            <td
                              ui-table-cell
                              class="text-success text-success py-2.5 text-right text-xs font-bold tabular-nums"
                            >
                              {{ selectedCandidate().compensation.totalYear1 }}
                            </td>
                          </tr>
                        </tbody>
                      </div>
                    </div>
                  </div>
    
                  <div class="space-y-2">
                    <div class="flex items-center justify-between gap-x-2 text-xs">
                      <span class="text-muted-foreground">Notice Period</span>
                      <span class="text-foreground font-medium tabular-nums">{{
                        selectedCandidate().compensation.noticePeriod
                      }}</span>
                    </div>
                    <div class="flex items-center justify-between gap-x-2 text-xs">
                      <span class="text-muted-foreground">Band Status</span>
                      <span ui-badge variant="secondary" class="text-xs font-normal">
                        {{ selectedCandidate().compensation.bandStatus }}
                      </span>
                    </div>
                  </div>
    
                  <ui-separator class="my-3" />
    
                  <div class="flex flex-col gap-2">
                    <button ui-button size="sm" class="w-full gap-1.5 text-xs shadow-xs">
                      <lucide-icon [img]="FileSpreadsheet" class="size-3.5" />
                      <span>Generate Official Offer Doc</span>
                    </button>
                    <button ui-button variant="outline" size="sm" class="w-full gap-1.5 text-xs shadow-xs">
                      <lucide-icon [img]="Calendar" class="size-3.5" />
                      <span>Schedule Executive Alignment</span>
                    </button>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      `,
    })
    export class UiRecruitmentCandidatePipelineComponent {
      protected readonly ArrowRight = ArrowRight
      protected readonly Award = Award
      protected readonly Bot = Bot
      protected readonly Briefcase = Briefcase
      protected readonly Building2 = Building2
      protected readonly Calendar = Calendar
      protected readonly Check = Check
      protected readonly CheckCircle2 = CheckCircle2
      protected readonly ChevronRight = ChevronRight
      protected readonly Clock = Clock
      protected readonly FileDown = FileDown
      protected readonly FileSpreadsheet = FileSpreadsheet
      protected readonly FileText = FileText
      protected readonly Mail = Mail
      protected readonly MapPin = MapPin
      protected readonly ShieldCheck = ShieldCheck
      protected readonly Star = Star
      protected readonly Timer = Timer
      protected readonly UserCheck = UserCheck
      protected readonly UserPlus = UserPlus
      protected readonly Users = Users
    
      protected readonly stages = stages
    
      @Input('class') className?: string
      @Input() set initialCandidateId(v: string | undefined) {
        if (v) this.selectedCandidateId.set(v)
      }
    
      readonly candidates = signal<Candidate[]>(
        initialCandidates.map((c) => ({
          ...c,
          keySkills: [...c.keySkills],
          competencies: c.competencies.map((k) => ({ ...k })),
          aiHighlights: [...c.aiHighlights],
          growthAreas: [...c.growthAreas],
          interviews: c.interviews.map((i) => ({ ...i })),
          compensation: { ...c.compensation },
        })),
      )
    
      readonly selectedCandidateId = signal<string>('cand-1')
    
      readonly selectedCandidate = computed(() => {
        return this.candidates().find((c) => c.id === this.selectedCandidateId()) ?? this.candidates()[0]!
      })
    
      get rootClass(): string {
        return cn('w-full space-y-6 font-sans', this.className)
      }
    
      stageDotClass(stageId: PipelineStage['id']): string {
        return cn(
          'size-2 shrink-0 rounded-full',
          stageId === 'screening'
            ? 'bg-info'
            : stageId === 'technical'
              ? 'bg-chart-1'
              : stageId === 'culture'
                ? 'bg-warning'
                : 'bg-success',
        )
      }
    
      candidateCardClass(candidateId: string): string {
        return cn(
          'group bg-card relative flex cursor-pointer flex-col gap-3 rounded-lg border p-3.5 shadow-xs transition-colors duration-200',
          this.selectedCandidateId() === candidateId
            ? 'border-primary ring-primary/20 bg-card shadow-sm ring-2'
            : 'border-border/80 hover:border-border hover:shadow-sm',
        )
      }
    
      switcherClass(candidateId: string): string {
        return cn(
          'focus-visible:ring-ring inline-flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:outline-none',
          this.selectedCandidateId() === candidateId
            ? 'bg-primary text-primary-foreground shadow-xs'
            : 'bg-background hover:bg-muted text-muted-foreground hover:text-foreground border-border/70 border',
        )
      }
    
      switcherScoreClass(candidateId: string): string {
        return cn(
          'py-0.2 rounded-full px-1 text-xs font-semibold tabular-nums',
          this.selectedCandidateId() === candidateId
            ? 'bg-primary-foreground/20 text-primary-foreground'
            : 'bg-muted text-muted-foreground',
        )
      }
    
      selectCandidate(id: string): void {
        this.selectedCandidateId.set(id)
      }
    
      advanceCandidate(candidateId: string): void {
        this.candidates.update((prev) =>
          prev.map((c) => {
            if (c.id !== candidateId) return c
            if (c.stageId === 'screening') {
              return {
                ...c,
                stageId: 'technical',
                stageName: 'Technical Architecture Interview',
                stageDuration: 'Just advanced',
              }
            }
            if (c.stageId === 'technical') {
              return {
                ...c,
                stageId: 'culture',
                stageName: 'Executive & Team Culture Sync',
                stageDuration: 'Just advanced',
              }
            }
            if (c.stageId === 'culture') {
              return { ...c, stageId: 'offer', stageName: 'Offer Extended', stageDuration: 'Offer stage initiated' }
            }
            return c
          }),
        )
      }
    
      getCandidatesForStage(stageId: PipelineStage['id']): Candidate[] {
        return this.candidates().filter((c) => c.stageId === stageId)
      }
    }
    
  • src/app/components/blocks/recruitment-candidate-pipeline/index.ts0.2 kB

Raw manifest:https://uipkge.dev/r/angular/recruitment-candidate-pipeline.json