/* ============================================================ 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 ? `
${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
${rec.hires}
hires
`; }).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.workload}%
Workload
${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 }); };