/* ============================================================
dashboard.js โ Main dashboard view
============================================================ */
window.Views = window.Views || {};
Views.dashboard = function () {
const k = DB.kpis;
const kpiCards = [
{ label: 'Open Jobs', value: k.openJobs, icon: 'briefcase', cls: 'i-indigo', trend: '+8%', dir: 'up', foot: 'vs last month' },
{ label: 'Total Candidates', value: k.totalCandidates, icon: 'users', cls: 'i-blue', trend: '+12%', dir: 'up', foot: 'active in pipeline' },
{ label: 'Interviews Today', value: k.interviewsToday, icon: 'calendar', cls: 'i-purple', trend: '3 upcoming', dir: 'flat', foot: 'next at 2:00 PM' },
{ label: 'Offers Accepted', value: k.offersAccepted, icon: 'check-circle', cls: 'i-green', trend: '+5%', dir: 'up', foot: `of ${k.offersSent} sent` }
];
const kpiCards2 = [
{ label: 'Time to Hire', value: k.timeToHire + ' days', icon: 'clock', cls: 'i-teal', trend: '-3 days', dir: 'up', foot: 'faster than target' },
{ label: 'Time to Fill', value: k.timeToFill + ' days', icon: 'target', cls: 'i-amber', trend: '-2 days', dir: 'up', foot: '41 day average' },
{ label: 'Cost per Hire', value: DB.money(k.costPerHire), icon: 'dollar', cls: 'i-red', trend: '+4%', dir: 'down', foot: 'above budget' },
{ label: 'Closed Jobs', value: k.closedJobs + k.hires, icon: 'award', cls: 'i-purple', trend: '+15%', dir: 'up', foot: 'this quarter' }
];
function trendHtml(dir, txt) {
if (dir === 'flat') return `${txt}`;
const cls = dir === 'up' ? 'trend-up' : 'trend-down';
const ic = dir === 'up' ? 'trending-up' : 'trending-down';
return `${UI.icon(ic)}${txt}`;
}
const kpiHtml = arr => arr.map(c => `
${c.label}
${UI.icon(c.icon)}
${c.value}
`).join('');
// upcoming interviews
const upcoming = DB.interviews.filter(iv => iv.status === 'Scheduled').slice(0, 5);
const upcomingHtml = upcoming.length ? upcoming.map(iv => `
${UI.avatar(iv.candidate, iv.candInitials, iv.color)}
${iv.candidate}
${iv.type} ยท ${iv.jobTitle}
${DB.fmtShort(iv.when)}
${iv.when.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}
`).join('') : 'No upcoming interviews
';
// recent applications
const recentApps = [...DB.candidates].sort((a, b) => b.applied - a.applied).slice(0, 5);
const recentHtml = recentApps.map(c => `
${UI.avatar(c.name, c.initials, c.color)}
${UI.scoreChip(c.aiScore)}
`).join('');
// activity feed
const activityHtml = DB.activity.slice(0, 8).map(a => `
${UI.icon(a.icon)}
${a.html}
${DB.relTime(a.time)}
`).join('');
// recruiter performance
const topRecs = [...DB.recruiters].sort((a, b) => b.hires - a.hires).slice(0, 5);
const recPerf = topRecs.map(r => `
${UI.avatar(r.name, r.initials, r.color)}
${r.name}
${r.openReqs} open reqs ยท ${r.avgTimeToHire}d avg
`).join('');
const html = `
Good morning, Asfand ๐
Here's what's happening with your hiring today โ Thursday, July 9, 2026
${kpiHtml(kpiCards)}
${kpiHtml(kpiCards2)}
Hiring Trend
Hires vs applications over the last 7 months
7M1Y
${Charts.legend([{ label: 'Applications', color: Charts.PALETTE[4] }, { label: 'Hires', color: Charts.PALETTE[0] }])}
Candidate Pipeline
Active by stage
Upcoming Interviews
Next scheduled sessions
Source Analytics
Where candidates come from
Recent Applications
`;
return {
html,
onMount() {
const a = DB.analytics;
Charts.line(document.getElementById('chartTrend'), {
labels: a.hiringTrend.labels, area: true,
datasets: [
{ label: 'Applications', data: a.hiringTrend.applications, color: Charts.PALETTE[4] },
{ label: 'Hires', data: a.hiringTrend.hires, color: Charts.PALETTE[0] }
]
});
Charts.horizontalBar(document.getElementById('chartPipeline'), {
labels: a.pipeline.map(p => p.stage), data: a.pipeline.map(p => p.count),
colors: Charts.PALETTE
});
Charts.bar(document.getElementById('chartSource'), {
labels: a.sources.map(s => s.source), data: a.sources.map(s => s.count)
});
}
};
};