110 lines
6.3 KiB
JavaScript
110 lines
6.3 KiB
JavaScript
/* ============================================================
|
|
reports.js — Reports page (cards, charts, table, export)
|
|
============================================================ */
|
|
window.Views = window.Views || {};
|
|
|
|
Views.reports = function () {
|
|
const a = DB.analytics;
|
|
const reportCards = [
|
|
{ title: 'Total Hires (YTD)', val: a.hiringTrend.hires.reduce((s, v) => s + v, 0), icn: 'award', cls: 'i-green', sub: '+18% vs last year' },
|
|
{ title: 'Total Applications', val: a.hiringTrend.applications.reduce((s, v) => s + v, 0).toLocaleString(), icn: 'users', cls: 'i-blue', sub: 'across all channels' },
|
|
{ title: 'Avg. Time to Hire', val: '27 days', icn: 'clock', cls: 'i-teal', sub: '3 days faster' },
|
|
{ title: 'Avg. Cost per Hire', val: '$4,280', icn: 'dollar', cls: 'i-amber', sub: 'within budget' }
|
|
];
|
|
|
|
const deptRows = a.departments.map(d => {
|
|
const rate = Math.round((d.open ? d.apps / (d.open * 40) : 0.5) * 100);
|
|
return { dept: d.dept, open: d.open, apps: d.apps, hires: DB.int(1, 8), ttf: DB.int(28, 52), rate: Math.min(rate, 98) };
|
|
});
|
|
|
|
const table = UI.dataTable({
|
|
pageSize: 10,
|
|
rows: deptRows,
|
|
columns: [
|
|
{ key: 'dept', label: 'Department', sortable: true, render: r => `<span class="cell-primary">${r.dept}</span>` },
|
|
{ key: 'open', label: 'Open Roles', sortable: true, align: 'center' },
|
|
{ key: 'apps', label: 'Applications', sortable: true, align: 'center', render: r => `<b>${r.apps}</b>` },
|
|
{ key: 'hires', label: 'Hires', sortable: true, align: 'center' },
|
|
{ key: 'ttf', label: 'Time to Fill', sortable: true, align: 'center', render: r => r.ttf + ' days' },
|
|
{ key: 'rate', label: 'Fill Rate', sortable: true, render: r => `<div class="flex items-center gap-8"><div style="flex:1">${UI.pbar(r.rate)}</div><b style="width:38px;text-align:right">${r.rate}%</b></div>` }
|
|
]
|
|
});
|
|
|
|
const reportTypes = [
|
|
{ name: 'Hiring Funnel Report', desc: 'Conversion rates across each pipeline stage', icn: 'filter', cls: 'i-indigo' },
|
|
{ name: 'Source Effectiveness', desc: 'ROI and quality by sourcing channel', icn: 'target', cls: 'i-teal' },
|
|
{ name: 'Diversity & Inclusion', desc: 'Demographic breakdown of the pipeline', icn: 'users', cls: 'i-purple' },
|
|
{ name: 'Recruiter Scorecard', desc: 'Individual performance metrics', icn: 'award', cls: 'i-amber' },
|
|
{ name: 'Offer Analysis', desc: 'Acceptance rates and compensation trends', icn: 'file', cls: 'i-green' },
|
|
{ name: 'Interview Analytics', desc: 'Interviewer load and feedback quality', icn: 'calendar', cls: 'i-blue' }
|
|
];
|
|
|
|
const html = `
|
|
<div class="page">
|
|
<div class="page-head">
|
|
<div><h1 class="page-title">Reports</h1><p class="page-sub">Recruitment metrics and downloadable insights</p></div>
|
|
<div class="page-head-actions">
|
|
<select class="select"><option>Last 7 months</option><option>This quarter</option><option>This year</option></select>
|
|
<button class="btn btn-primary" onclick="UI.toast('Full report exported to PDF','success')">${UI.icon('download')} Export Report</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="grid g-kpi mb-18">
|
|
${reportCards.map(c => `<div class="kpi"><div class="kpi-top"><span class="kpi-label">${c.title}</span><span class="kpi-icn ${c.cls}">${UI.icon(c.icn)}</span></div><div class="kpi-value">${c.val}</div><div class="kpi-foot"><span class="kpi-foot-text">${c.sub}</span></div></div>`).join('')}
|
|
</div>
|
|
|
|
<div class="grid g-2 mb-18">
|
|
<div class="card">
|
|
<div class="card-head"><div><h3>Hiring Funnel</h3><span class="ch-sub">Stage-by-stage conversion</span></div>
|
|
<button class="btn btn-ghost btn-sm" onclick="UI.toast('Chart exported','info')">${UI.icon('download')}</button></div>
|
|
<div class="card-body"><div class="chart-wrap"><canvas id="rptFunnel" height="280"></canvas></div></div>
|
|
</div>
|
|
<div class="card">
|
|
<div class="card-head"><div><h3>Time to Hire vs Fill</h3><span class="ch-sub">Monthly trend (days)</span></div></div>
|
|
<div class="card-body"><div class="chart-wrap"><canvas id="rptTime" height="280"></canvas></div>
|
|
${Charts.legend([{ label: 'Time to Hire', color: Charts.PALETTE[0] }, { label: 'Time to Fill', color: Charts.PALETTE[2] }])}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card mb-18">
|
|
<div class="card-head"><div><h3>Department Performance</h3><span class="ch-sub">Hiring breakdown by team</span></div>
|
|
<button class="btn btn-secondary btn-sm" onclick="UI.toast('Table exported to CSV','success')">${UI.icon('download')} CSV</button></div>
|
|
${table.html}
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-head"><div><h3>Report Library</h3><span class="ch-sub">Generate a detailed report</span></div></div>
|
|
<div class="card-body"><div class="grid g-3">
|
|
${reportTypes.map(r => `
|
|
<div class="card" style="box-shadow:none;background:var(--bg-sunken);cursor:pointer" onclick="UI.toast('Generating: ${r.name}','info')">
|
|
<div class="card-body">
|
|
<span class="kpi-icn ${r.cls}" style="margin-bottom:12px">${UI.icon(r.icn)}</span>
|
|
<div class="lr-title">${r.name}</div>
|
|
<div class="lr-sub" style="margin-top:4px">${r.desc}</div>
|
|
<div style="margin-top:12px;color:var(--primary);font-weight:600;font-size:13px">Generate ${UI.icon('chevron-right')}</div>
|
|
</div>
|
|
</div>`).join('')}
|
|
</div></div>
|
|
</div>
|
|
</div>`;
|
|
|
|
return {
|
|
html,
|
|
onMount() {
|
|
table.mount();
|
|
const funnel = [{ stage: 'Applied', v: 100 }, { stage: 'Screened', v: 62 }, { stage: 'Assessed', v: 41 }, { stage: 'Interviewed', v: 28 }, { stage: 'Offered', v: 14 }, { stage: 'Hired', v: 9 }];
|
|
Charts.bar(document.getElementById('rptFunnel'), {
|
|
labels: funnel.map(f => f.stage), data: funnel.map(f => f.v),
|
|
colors: Charts.PALETTE, yFmt: v => v + '%'
|
|
});
|
|
Charts.groupedBar(document.getElementById('rptTime'), {
|
|
labels: DB.analytics.hiringTrend.labels,
|
|
datasets: [
|
|
{ label: 'Time to Hire', data: DB.analytics.timeToHire, color: Charts.PALETTE[0] },
|
|
{ label: 'Time to Fill', data: DB.analytics.timeToFill, color: Charts.PALETTE[2] }
|
|
], yFmt: v => v + 'd'
|
|
});
|
|
}
|
|
};
|
|
};
|