/** * RankingTable.jsx * Displays the ranked list of candidates with scores, badges, and selection status. * Each row is clickable to show the full detail panel. */ import { Trophy, ChevronRight, CheckCircle, XCircle, TrendingUp } from 'lucide-react'; import ScoreRing from './ScoreRing'; function SkillBadge({ skill, variant = 'green' }) { return ( {skill} ); } function RankIcon({ rank }) { if (rank === 1) return 🥇; if (rank === 2) return 🥈; if (rank === 3) return 🥉; return ( {rank} ); } export default function RankingTable({ candidates, selectedId, onSelectCandidate, jobTitle }) { if (!candidates || candidates.length === 0) return null; return (
{/* Table header */}

Ranked Candidates

{candidates.length} candidates screened for {jobTitle}

Selected (≥50) Rejected
{/* Desktop table */}
{candidates.map((c) => { const isSelected = selectedId === c.rank; const totalReq = c.matched_skills.length + c.missing_skills.length; const skillPct = totalReq > 0 ? Math.round((c.matched_skills.length / totalReq) * 100) : 100; return ( onSelectCandidate(c)} className={`cursor-pointer transition-all duration-150 ${ isSelected ? 'bg-primary-900/20 border-l-2 border-l-primary-500' : 'hover:bg-slate-800/40' }`} > {/* Rank */} {/* Candidate Info */} {/* Score Ring */} {/* Skills Match */} {/* Experience Fit */} {/* Education Fit */} {/* Status Badge */} {/* Arrow */} ); })}
Rank Candidate Score Skills Match Exp Fit Edu Fit Status
{c.name}
{c.filename}
{c.match_percent}%
= 70 ? '#10b981' : skillPct >= 40 ? '#f59e0b' : '#ef4444' }} />
{skillPct}%
{c.matched_skills.slice(0, 3).map(s => ( ))} {c.matched_skills.length > 3 && ( +{c.matched_skills.length - 3} )} {c.matched_skills.length === 0 && ( None matched )}
{c.education_degree}
{c.status === 'Selected' ? ( Selected ) : ( Rejected )}
); } function ScorePill({ score }) { const color = score >= 80 ? 'text-emerald-400' : score >= 50 ? 'text-amber-400' : 'text-red-400'; return {score.toFixed(0)}; }