HR-ATS-Portal/js/misc.js

208 lines
14 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

/* ============================================================
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 (0100) 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)';
};