HR-ATS-Portal/js/recruiterhub.js

122 lines
6.7 KiB
JavaScript

/* ============================================================
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 => `<option value="${r.id}">${r.name}</option>`).join('');
const html = `
<div class="page">
<div class="page-head">
<div><h1 class="page-title">Recruiter Hub</h1><p class="page-sub">Personalized performance dashboard & workload</p></div>
<div class="page-head-actions">
<select class="select" id="recSelect">${recOpts}</select>
<button class="btn btn-secondary" onclick="UI.toast('Report exported','success')">${UI.icon('download')} Export</button>
</div>
</div>
<div id="recHubBody"></div>
</div>`;
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) => `<div class="kpi"><div class="kpi-top"><span class="kpi-label">${label}</span><span class="kpi-icn ${cls}">${UI.icon(icn)}</span></div><div class="kpi-value">${val}</div>${sub ? `<div class="kpi-foot"><span class="kpi-foot-text">${sub}</span></div>` : ''}</div>`;
// 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 `<div class="leader-row" style="${rec.id === r.id ? 'background:var(--primary-soft);border-radius:10px;padding-left:8px;padding-right:8px' : ''}">
<span class="leader-rank ${rankCls}">${i + 1}</span>
${UI.avatar(rec.name, rec.initials, rec.color)}
<div class="lr-main"><div class="lr-title">${rec.name}</div><div class="lr-sub">${rec.efficiency}% efficiency · ${rec.avgTimeToHire}d avg</div></div>
<div class="lr-right"><div class="fw-600">${rec.hires}</div><div class="lr-sub">hires</div></div>
</div>`;
}).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 = `<div class="heatmap">
<div class="hm-label"></div>${['W1', 'W2', 'W3', 'W4', 'W5'].map(w => `<div class="hm-label" style="justify-content:center">${w}</div>`).join('')}
${days.map((d, di) => `<div class="hm-label">${d}</div>${r.heatmap[di].map(v => `<div class="hm-cell" style="background:${heatColor(v)}" data-tip="${v} interviews"></div>`).join('')}`).join('')}
</div>
<div class="hm-legend">Less ${[0, 1, 2, 3, 5].map(v => `<span class="hm-box" style="background:${heatColor(v)}"></span>`).join('')} More</div>`;
el.innerHTML = `
<div class="card brand-hero mb-18">
<div class="card-body" style="display:flex;align-items:center;gap:18px">
${UI.avatar(r.name, r.initials, 'rgba(255,255,255,.18)', 'avatar-lg')}
<div style="flex:1"><div style="font-size:20px;font-weight:700">${r.name}</div><div style="opacity:.85">${r.department} Recruiter · ⭐ ${r.rating} rating</div></div>
<div style="text-align:center"><div style="font-size:26px;font-weight:800">${r.workload}%</div><div style="opacity:.85;font-size:12px">Workload</div></div>
<div style="text-align:center"><div style="font-size:26px;font-weight:800">${r.efficiency}%</div><div style="opacity:.85;font-size:12px">Efficiency</div></div>
<div style="text-align:center">${UI.badge(r.sla, slaCls)}</div>
</div>
</div>
<div class="grid g-kpi mb-18">
${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')}
</div>
<div class="grid g-kpi mb-18">
${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')}
</div>
<div class="grid g-kpi mb-18">
${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')}
</div>
<div class="grid g-2-1 mb-18">
<div class="card">
<div class="card-head"><div><h3>Monthly Hiring Trend</h3><span class="ch-sub">Hires per month</span></div></div>
<div class="card-body"><div class="chart-wrap"><canvas id="recTrend" height="260"></canvas></div></div>
</div>
<div class="card">
<div class="card-head"><div><h3>Workload Heatmap</h3><span class="ch-sub">Interview load</span></div></div>
<div class="card-body">${heatHtml}</div>
</div>
</div>
<div class="grid g-2">
<div class="card">
<div class="card-head"><div><h3>Recruiter Leaderboard</h3><span class="ch-sub">Top performers by hires</span></div></div>
<div class="card-body">${leaderHtml}</div>
</div>
<div class="card">
<div class="card-head"><div><h3>Candidate Pipeline</h3><span class="ch-sub">This recruiter's active candidates</span></div></div>
<div class="card-body"><div class="chart-wrap"><canvas id="recPipeline" height="260"></canvas></div></div>
</div>
</div>`;
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
});
};