208 lines
14 KiB
JavaScript
208 lines
14 KiB
JavaScript
/* ============================================================
|
||
misc.js — Hiring Managers, Calendar, Notifications, Help
|
||
============================================================ */
|
||
window.Views = window.Views || {};
|
||
|
||
// ---------------- Hiring Managers ----------------
|
||
Views.managers = function () {
|
||
function card(m) {
|
||
const jobs = DB.jobs.filter(j => j.manager === m.name && j.status === 'Open');
|
||
return `<div class="card">
|
||
<div class="card-body">
|
||
<div class="flex items-center gap-12" style="margin-bottom:14px">
|
||
${UI.avatar(m.name, m.initials, m.color, 'avatar-lg')}
|
||
<div style="flex:1"><div class="lr-title">${m.name}</div><div class="lr-sub">${m.title}</div></div>
|
||
</div>
|
||
<div class="grid g-2" style="gap:10px;margin-bottom:14px">
|
||
<div class="stat-mini"><span class="stat-mini-val">${m.openReqs}</span><span class="stat-mini-lbl">Open Reqs</span></div>
|
||
<div class="stat-mini"><span class="stat-mini-val">${m.teamSize}</span><span class="stat-mini-lbl">Team Size</span></div>
|
||
</div>
|
||
<div class="divider" style="margin:12px 0"></div>
|
||
<div class="flex items-center" style="justify-content:space-between">
|
||
<span class="cell-sub">${UI.icon('mail')} ${m.email.split('@')[0]}</span>
|
||
<button class="btn btn-ghost btn-sm" onclick="Views._mgrDetail('${m.id}')">View</button>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
}
|
||
const html = `
|
||
<div class="page">
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">Hiring Managers</h1><p class="page-sub">${DB.managers.length} managers · ${DB.managers.reduce((s, m) => s + m.openReqs, 0)} active requisitions</p></div>
|
||
<div class="page-head-actions"><button class="btn btn-primary" onclick="UI.toast('Invite manager','info')">${UI.icon('plus')} Add Manager</button></div>
|
||
</div>
|
||
<div class="grid g-3">${DB.managers.map(card).join('')}</div>
|
||
</div>`;
|
||
return { html };
|
||
};
|
||
Views._mgrDetail = function (id) {
|
||
const m = DB.getManager(id);
|
||
const jobs = DB.jobs.filter(j => j.manager === m.name);
|
||
const body = `
|
||
<div class="profile-hero" style="margin-bottom:18px">${UI.avatar(m.name, m.initials, m.color, 'avatar-lg')}
|
||
<div><div class="ph-name">${m.name}</div><div class="ph-role">${m.title}</div>
|
||
<div class="ph-tags">${UI.badge(m.department, 'b-indigo')}<span class="badge b-gray badge-plain">${m.teamSize} reports</span></div></div></div>
|
||
<div class="grid g-3" style="margin-bottom:18px">
|
||
<div class="stat-mini"><span class="stat-mini-val">${m.openReqs}</span><span class="stat-mini-lbl">Open Reqs</span></div>
|
||
<div class="stat-mini"><span class="stat-mini-val">${jobs.length}</span><span class="stat-mini-lbl">Total Jobs</span></div>
|
||
<div class="stat-mini"><span class="stat-mini-val">${jobs.reduce((s, j) => s + j.applications, 0)}</span><span class="stat-mini-lbl">Applications</span></div>
|
||
</div>
|
||
<div class="form-section-title" style="margin-top:0">Hiring Manager Portal</div>
|
||
<div class="grid g-2" style="gap:10px;margin-bottom:16px">
|
||
<button class="btn btn-secondary" onclick="UI.closeModal();Jobs.openCreate()">${UI.icon('plus')} Raise Requisition</button>
|
||
<button class="btn btn-secondary" onclick="UI.closeModal();Router.go('candidates')">${UI.icon('users')} Review Candidates</button>
|
||
<button class="btn btn-secondary" onclick="UI.closeModal();Interviews.schedule()">${UI.icon('calendar')} Schedule Interview</button>
|
||
<button class="btn btn-secondary" onclick="UI.closeModal();Router.go('offers')">${UI.icon('check-circle')} Approve Offers</button>
|
||
</div>
|
||
<div class="form-section-title">Requisitions</div>
|
||
<div class="list-tight">${jobs.length ? jobs.map(j => `<div class="list-row" style="cursor:pointer" onclick="UI.closeModal();Jobs.view('${j.id}')">
|
||
<span class="kpi-icn i-indigo" style="width:36px;height:36px;border-radius:9px">${UI.icon('briefcase')}</span>
|
||
<div class="lr-main"><div class="lr-title">${j.title}</div><div class="lr-sub">${j.applications} applications</div></div>${UI.badge(j.status)}</div>`).join('') : '<p class="text-muted">No requisitions</p>'}</div>`;
|
||
UI.modal({ title: 'Hiring Manager', subtitle: m.id, body, size: 'modal-lg', footer: `<button class="btn btn-secondary" onclick="UI.closeModal()">Close</button><button class="btn btn-primary" onclick="UI.toast('Message sent','success')">${UI.icon('mail')} Message</button>` });
|
||
};
|
||
|
||
// ---------------- Calendar ----------------
|
||
Views.calendar = function () {
|
||
const state = { month: 6, year: 2026 }; // July 2026 (0-indexed)
|
||
const evColors = { 'Phone Screen': 'b-blue', 'Technical': 'b-indigo', 'System Design': 'b-purple', 'Onsite Loop': 'b-teal', 'Hiring Manager': 'b-amber', 'Culture Fit': 'b-green', 'Final Round': 'b-red' };
|
||
|
||
function build() {
|
||
const first = new Date(state.year, state.month, 1);
|
||
const startDow = first.getDay();
|
||
const daysInMonth = new Date(state.year, state.month + 1, 0).getDate();
|
||
const prevDays = new Date(state.year, state.month, 0).getDate();
|
||
const cells = [];
|
||
for (let i = startDow - 1; i >= 0; i--) cells.push({ day: prevDays - i, other: true });
|
||
for (let d = 1; d <= daysInMonth; d++) cells.push({ day: d, other: false, date: new Date(state.year, state.month, d) });
|
||
while (cells.length % 7 !== 0 || cells.length < 42) cells.push({ day: cells.length - daysInMonth - startDow + 1, other: true });
|
||
|
||
const today = new Date('2026-07-09');
|
||
const dow = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
|
||
let html = dow.map(d => `<div class="cal-dow">${d}</div>`).join('');
|
||
cells.slice(0, 42).forEach(c => {
|
||
let evs = '';
|
||
if (!c.other && c.date) {
|
||
const dayEvents = DB.interviews.filter(iv => iv.when.toDateString() === c.date.toDateString());
|
||
evs = dayEvents.slice(0, 3).map(iv => `<div class="cal-event ${evColors[iv.type] || 'b-blue'}" onclick="Candidates.openProfile('${iv.candidateId}')" title="${iv.candidate} · ${iv.type}">${iv.when.toLocaleTimeString('en-US', { hour: 'numeric' })} ${iv.candidate.split(' ')[0]}</div>`).join('');
|
||
if (dayEvents.length > 3) evs += `<div class="cal-event b-gray">+${dayEvents.length - 3} more</div>`;
|
||
}
|
||
const isToday = !c.other && c.date && c.date.toDateString() === today.toDateString();
|
||
html += `<div class="cal-cell ${c.other ? 'other' : ''} ${isToday ? 'today' : ''}"><div class="cal-date">${c.day}</div>${evs}</div>`;
|
||
});
|
||
return html;
|
||
}
|
||
|
||
const monthName = new Date(state.year, state.month).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
||
const todayIvs = DB.interviews.filter(iv => iv.when.toDateString() === new Date('2026-07-09').toDateString());
|
||
|
||
const html = `
|
||
<div class="page">
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">Calendar</h1><p class="page-sub">Interview schedule at a glance</p></div>
|
||
<div class="page-head-actions">
|
||
<div class="flex items-center gap-8">
|
||
<button class="btn btn-icon btn-secondary" id="calPrev">${UI.icon('chevron-left')}</button>
|
||
<span class="fw-600" id="calMonth" style="min-width:140px;text-align:center">${monthName}</span>
|
||
<button class="btn btn-icon btn-secondary" id="calNext">${UI.icon('chevron-right')}</button>
|
||
</div>
|
||
<button class="btn btn-primary" onclick="Interviews.schedule()">${UI.icon('plus')} Schedule</button>
|
||
</div>
|
||
</div>
|
||
<div class="grid g-2-1">
|
||
<div class="card"><div class="card-body"><div class="cal-grid" id="calGrid">${build()}</div></div></div>
|
||
<div class="card" style="align-self:start"><div class="card-head"><div><h3>Today</h3><span class="ch-sub">July 9, 2026</span></div></div>
|
||
<div class="card-body"><div class="list-tight">${todayIvs.length ? todayIvs.map(iv => `
|
||
<div class="list-row" style="cursor:pointer" onclick="Candidates.openProfile('${iv.candidateId}')">${UI.avatar(iv.candidate, iv.candInitials, iv.color)}
|
||
<div class="lr-main"><div class="lr-title">${iv.candidate}</div><div class="lr-sub">${iv.type}</div></div>
|
||
<div class="lr-right"><div class="fw-600 text-sm">${iv.when.toLocaleTimeString('en-US', { hour: 'numeric', minute: '2-digit' })}</div></div></div>`).join('') : '<p class="text-muted">No interviews today</p>'}</div></div>
|
||
</div>
|
||
</div>
|
||
</div>`;
|
||
|
||
return {
|
||
html,
|
||
onMount() {
|
||
const upd = () => {
|
||
document.getElementById('calGrid').innerHTML = build();
|
||
document.getElementById('calMonth').textContent = new Date(state.year, state.month).toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
||
};
|
||
document.getElementById('calPrev').onclick = () => { state.month--; if (state.month < 0) { state.month = 11; state.year--; } upd(); };
|
||
document.getElementById('calNext').onclick = () => { state.month++; if (state.month > 11) { state.month = 0; state.year++; } upd(); };
|
||
}
|
||
};
|
||
};
|
||
|
||
// ---------------- Notifications ----------------
|
||
Views.notifications = function () {
|
||
const rows = DB.notifications.map((n, i) => `
|
||
<div class="notif-row ${n.unread ? 'unread' : ''}" onclick="this.classList.remove('unread')">
|
||
<span class="notif-icn ${n.color}">${UI.icon(n.icon)}</span>
|
||
<div class="notif-body"><div class="notif-title">${n.title}</div><div class="notif-text">${n.text}</div><div class="notif-time">${n.time}</div></div>
|
||
${n.unread ? '<span class="dot dot-blue" style="position:static;border:none;align-self:center"></span>' : ''}
|
||
</div>`).join('');
|
||
const html = `
|
||
<div class="page">
|
||
<div class="page-head">
|
||
<div><h1 class="page-title">Notifications</h1><p class="page-sub">Stay on top of hiring activity</p></div>
|
||
<div class="page-head-actions">
|
||
<button class="btn btn-secondary" onclick="App.markAllNotifsRead();Router.reload()">${UI.icon('check')} Mark all read</button>
|
||
<button class="btn btn-ghost" onclick="UI.toast('Notification settings','info')">${UI.icon('more')}</button>
|
||
</div>
|
||
</div>
|
||
<div class="card"><div class="list-tight" style="padding:0">${rows}</div></div>
|
||
</div>`;
|
||
return { html };
|
||
};
|
||
|
||
// ---------------- Help ----------------
|
||
Views.help = function () {
|
||
const faqs = [
|
||
{ q: 'How do I create a new job requisition?', a: 'Navigate to Jobs and click "Create Job". Fill in the required fields marked with an asterisk and click Save. The job will immediately appear in your listings.' },
|
||
{ q: 'How does the AI candidate score work?', a: 'The AI score (0–100) evaluates how well a candidate matches the job requirements based on skills, experience, and education. Higher scores indicate stronger matches.' },
|
||
{ q: 'Can I move candidates between pipeline stages?', a: 'Yes. Open the Pipeline view and simply drag any candidate card between stage columns. The candidate\'s status updates automatically.' },
|
||
{ q: 'How do I schedule an interview?', a: 'Go to Interviews or Calendar and click "Schedule Interview". Select the candidate, round, date, time, and interviewers.' },
|
||
{ q: 'How do I export reports?', a: 'On the Reports page, use the "Export Report" button for a full PDF, or the CSV buttons on individual tables.' }
|
||
];
|
||
const resources = [
|
||
{ icn: 'file', t: 'Documentation', d: 'Complete product guides', cls: 'i-indigo' },
|
||
{ icn: 'video', t: 'Video Tutorials', d: 'Watch step-by-step walkthroughs', cls: 'i-red' },
|
||
{ icn: 'message', t: 'Live Chat', d: 'Chat with our support team', cls: 'i-green' },
|
||
{ icn: 'users', t: 'Community', d: 'Connect with other recruiters', cls: 'i-purple' }
|
||
];
|
||
const html = `
|
||
<div class="page">
|
||
<div class="page-head"><div><h1 class="page-title">Help Center</h1><p class="page-sub">Find answers and get support</p></div></div>
|
||
<div class="card brand-hero mb-18">
|
||
<div class="card-body" style="padding:32px;text-align:center">
|
||
<h2 style="font-size:22px;margin-bottom:8px">How can we help you?</h2>
|
||
<p style="opacity:.85;margin-bottom:18px">Search our knowledge base or browse the topics below</p>
|
||
<div class="topbar-search" style="max-width:480px;margin:0 auto">
|
||
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
|
||
<input placeholder="Search help articles…" />
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="grid g-kpi mb-18">
|
||
${resources.map(r => `<div class="card" style="cursor:pointer" onclick="UI.toast('Opening ${r.t}','info')"><div class="card-body" style="text-align:center">
|
||
<span class="kpi-icn ${r.cls}" style="margin:0 auto 12px;width:48px;height:48px;border-radius:14px">${UI.icon(r.icn)}</span>
|
||
<div class="fw-600">${r.t}</div><div class="lr-sub" style="margin-top:4px">${r.d}</div></div></div>`).join('')}
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-head"><div><h3>Frequently Asked Questions</h3></div></div>
|
||
<div class="card-body"><div id="faqList">
|
||
${faqs.map((f, i) => `<div class="setting-row" style="cursor:pointer;flex-direction:column;align-items:stretch" onclick="Views._toggleFaq(${i})">
|
||
<div class="flex items-center" style="justify-content:space-between"><h4>${f.q}</h4><span id="faqChev${i}" style="color:var(--text-3);transition:.2s">${UI.icon('chevron-right')}</span></div>
|
||
<p id="faqA${i}" style="display:none;margin-top:10px">${f.a}</p></div>`).join('')}
|
||
</div></div>
|
||
</div>
|
||
</div>`;
|
||
return { html };
|
||
};
|
||
Views._toggleFaq = function (i) {
|
||
const a = document.getElementById('faqA' + i);
|
||
const chev = document.getElementById('faqChev' + i);
|
||
const open = a.style.display === 'block';
|
||
a.style.display = open ? 'none' : 'block';
|
||
chev.style.transform = open ? 'rotate(0deg)' : 'rotate(90deg)';
|
||
};
|