Recruitment Candidate Pipeline
blockhrAI-powered applicant tracking pipeline, candidate scorecard, resume match scoring, and interview stages board.
Installation
$pnpm dlx uipkge-ng@latest add recruitment-candidate-pipeline$npx uipkge-ng@latest add recruitment-candidate-pipeline$yarn dlx uipkge-ng@latest add recruitment-candidate-pipeline$bunx uipkge-ng@latest add recruitment-candidate-pipelineManifest:
https://uipkge.dev/r/angular/recruitment-candidate-pipeline.jsonInstalls to:src/app/components/blocks/recruitment-candidate-pipeline/Variants
Loading interactive previews…
Loading interactive previews…
Schema
Type aliases exported from this item's source. Use these to shape the data you pass in.
CandidateCompetencyinterface CandidateCompetency {
name: string
score: number
benchmark: string
}InterviewFeedbackinterface InterviewFeedback {
interviewer: string
role: string
score: number
verdict: string
feedback: string
}CompensationModelinterface CompensationModel {
targetBase: string
equity: string
signOn: string
bonus: string
totalYear1: string
bandStatus: string
noticePeriod: string
}Candidateinterface 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
}PipelineStageinterface 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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: '[email protected]', 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 & 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 & 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 & 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 & 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 & 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 & Package Model</h4> <p class="text-muted-foreground text-xs">Target offer & 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
export { UiRecruitmentCandidatePipelineComponent, type Candidate, type CandidateCompetency, type CompensationModel, type InterviewFeedback, type PipelineStage, } from './recruitment-candidate-pipeline.component'
Raw manifest:https://uipkge.dev/r/angular/recruitment-candidate-pipeline.json