/* ============================================================
recruiterhub.js — Personalized recruiter dashboard + leaderboard
============================================================ */
window.Views = window.Views || {};
window.RecruiterHub = {};
Views.recruiterhub = function () {
const state = { recId: DB.recruiters[0].id };
RecruiterHub._state = state;
const recOpts = DB.recruiters.map(r => ``).join('');
const html = `
Recruiter Hub
Personalized performance dashboard & workload
`;
return {
html,
onMount() {
RecruiterHub._render();
document.getElementById('recSelect').onchange = e => { state.recId = e.target.value; RecruiterHub._render(); };
}
};
};
RecruiterHub._render = function () {
const r = DB.getRecruiter(RecruiterHub._state.recId);
const el = document.getElementById('recHubBody');
const slaCls = r.sla === 'On Track' ? 'b-green' : r.sla === 'At Risk' ? 'b-amber' : 'b-red';
const kpi = (label, val, icn, cls, sub) => `${label}${UI.icon(icn)}
${val}
${sub ? `` : ''}
`;
// leaderboard
const board = [...DB.recruiters].sort((a, b) => b.hires - a.hires).slice(0, 8);
const leaderHtml = board.map((rec, i) => {
const rankCls = i === 0 ? 'gold' : i === 1 ? 'silver' : i === 2 ? 'bronze' : '';
return `
${i + 1}
${UI.avatar(rec.name, rec.initials, rec.color)}
${rec.name}
${rec.efficiency}% efficiency · ${rec.avgTimeToHire}d avg
`;
}).join('');
// heatmap
const maxHeat = 5;
const heatColor = v => { const t = v / maxHeat; return t === 0 ? 'var(--bg-sunken)' : `rgba(79,70,229,${0.2 + t * 0.8})`; };
const days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
const heatHtml = `
${['W1', 'W2', 'W3', 'W4', 'W5'].map(w => `
${w}
`).join('')}
${days.map((d, di) => `
${d}
${r.heatmap[di].map(v => `
`).join('')}`).join('')}
Less ${[0, 1, 2, 3, 5].map(v => ``).join('')} More
`;
el.innerHTML = `
${UI.avatar(r.name, r.initials, 'rgba(255,255,255,.18)', 'avatar-lg')}
${r.name}
${r.department} Recruiter · ⭐ ${r.rating} rating
${r.efficiency}%
Efficiency
${UI.badge(r.sla, slaCls)}
${kpi('Open Positions', r.openPositions, 'briefcase', 'i-indigo', 'active reqs')}
${kpi('Closed Positions', r.closedPositions, 'check-circle', 'i-green', 'this year')}
${kpi('Avg Time to Hire', r.avgTimeToHire + 'd', 'clock', 'i-teal', 'target 30d')}
${kpi('Avg Time to Fill', r.avgTimeToFill + 'd', 'target', 'i-amber', 'req → offer')}
${kpi('Interviews Today', r.interviewsToday, 'calendar', 'i-purple')}
${kpi('Offers Pending', r.offersPending, 'file', 'i-blue')}
${kpi('Awaiting Approval', r.jobsAwaitingApproval, 'clock', 'i-amber')}
${kpi('Jobs Overdue', r.jobsOverdue, 'alert', 'i-red')}
${kpi('Conversion Rate', r.conversionRate + '%', 'trending-up', 'i-green', 'applicant → hire')}
${kpi('Interview Completion', r.interviewCompletion + '%', 'check-square', 'i-teal')}
${kpi('Avg Response Time', r.avgResponseTime + 'h', 'zap', 'i-purple', 'to candidates')}
${kpi('TAT Performance', r.tat + '%', 'award', 'i-indigo', 'turnaround')}
Monthly Hiring Trend
Hires per month
Workload Heatmap
Interview load
${heatHtml}
Recruiter Leaderboard
Top performers by hires
${leaderHtml}
Candidate Pipeline
This recruiter's active candidates
`;
Charts.line(document.getElementById('recTrend'), { labels: DB.analytics.hiringTrend.labels, area: true, datasets: [{ label: 'Hires', data: r.monthlyTrend, color: Charts.PALETTE[0] }] });
const stageCounts = ['Applied', 'Screening', 'Assessment', 'Interview', 'Offer', 'Hired'].map(() => DB.int(2, 14));
Charts.horizontalBar(document.getElementById('recPipeline'), {
labels: ['Applied', 'Screening', 'Assessment', 'Interview', 'Offer', 'Hired'], data: stageCounts,
colors: Charts.PALETTE
});
};