Initial commit
This commit is contained in:
68
src/components/krow/PipelineFunnel.jsx
Normal file
68
src/components/krow/PipelineFunnel.jsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const STAGES = [
|
||||
{ key: 'applied', label: 'Applied', color: 'bg-[#D1E0D7]', text: 'text-[#333F48]' },
|
||||
{ key: 'ai_screened', label: 'AI Screened', color: 'bg-[#F8E08E]', text: 'text-[#333F48]' },
|
||||
{ key: 'shortlisted', label: 'Shortlisted', color: 'bg-[#F9E547]', text: 'text-[#0A39DF]' },
|
||||
{ key: 'interview', label: 'Interviewed', color: 'bg-[#333F48]', text: 'text-white' },
|
||||
{ key: 'hired', label: 'Hired', color: 'bg-[#0A39DF]', text: 'text-white' },
|
||||
];
|
||||
|
||||
export default function PipelineFunnel({ applications }) {
|
||||
const counts = STAGES.map((s) => {
|
||||
if (s.key === 'ai_screened') {
|
||||
return applications.filter(a => ['ai_screened', 'shortlisted', 'interview', 'hired'].includes(a.status)).length;
|
||||
}
|
||||
if (s.key === 'shortlisted') {
|
||||
return applications.filter(a => ['shortlisted', 'interview', 'hired'].includes(a.status)).length;
|
||||
}
|
||||
if (s.key === 'interview') {
|
||||
return applications.filter(a => ['interview', 'hired'].includes(a.status)).length;
|
||||
}
|
||||
return applications.filter(a => a.status === s.key).length;
|
||||
});
|
||||
|
||||
const max = Math.max(...counts, 1);
|
||||
const total = counts[0] || 1;
|
||||
|
||||
return (
|
||||
<div className="glass-card border border-white/60 rounded-2xl p-6 shadow-sm">
|
||||
<h3 className="font-heading font-semibold text-[#111827] text-[16px] mb-1">Pipeline Stages</h3>
|
||||
<p className="text-[12px] text-[#6B7280] mb-5">Candidate progression through hiring funnel</p>
|
||||
|
||||
<div className="space-y-4">
|
||||
{STAGES.map((stage, i) => {
|
||||
const count = counts[i];
|
||||
const width = (count / max) * 100;
|
||||
const convRate = i === 0 ? 100 : Math.round((count / total) * 100);
|
||||
const stepRate = i === 0 ? 100 : (counts[i - 1] ? Math.round((count / counts[i - 1]) * 100) : 0);
|
||||
return (
|
||||
<div key={stage.key}>
|
||||
<div className="flex items-center justify-between mb-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-[13px] font-medium text-[#374151]">{stage.label}</span>
|
||||
{i > 0 && stepRate <= 100 && (
|
||||
<span className="text-[10px] text-[#9CA3AF]">→ {stepRate}% from prev</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="text-[11px] text-[#9CA3AF]">{convRate}% of total</span>
|
||||
<span className="text-[15px] font-bold text-[#111827] w-8 text-right">{count}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="h-7 bg-[#F3F4F6] rounded-lg overflow-hidden">
|
||||
<div
|
||||
className={cn('h-full rounded-lg flex items-center px-3 transition-all duration-700', stage.color)}
|
||||
style={{ width: `${Math.max(width, count > 0 ? 6 : 0)}%` }}
|
||||
>
|
||||
<span className={cn('text-[11px] font-semibold', stage.text)}>{count > 0 ? count : ''}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user