/* ============================================================
inbox.js — Central Recruitment Inbox + Outlook Email tab
============================================================ */
window.Views = window.Views || {};
window.Inbox = {};
Views.inbox = function () {
const state = { tab: 'All Applications', selected: null, emailSelected: null, q: '' };
const tabs = ['All Applications', 'Unread', 'Imported', 'Processed', 'Rejected', 'Duplicates', 'Email'];
function filtered() {
let list = DB.inbox;
if (state.tab === 'Unread') list = list.filter(i => i.processing === 'Unread');
else if (state.tab === 'Imported') list = list.filter(i => i.processing === 'Imported');
else if (state.tab === 'Processed') list = list.filter(i => i.processing === 'Processed');
else if (state.tab === 'Rejected') list = list.filter(i => i.processing === 'Rejected');
else if (state.tab === 'Duplicates') list = list.filter(i => i.duplicate);
if (state.q) list = list.filter(i => (i.name + i.position + i.source).toLowerCase().includes(state.q.toLowerCase()));
return list;
}
function counts() {
return {
'All Applications': DB.inbox.length,
'Unread': DB.inbox.filter(i => i.processing === 'Unread').length,
'Imported': DB.inbox.filter(i => i.processing === 'Imported').length,
'Processed': DB.inbox.filter(i => i.processing === 'Processed').length,
'Rejected': DB.inbox.filter(i => i.processing === 'Rejected').length,
'Duplicates': DB.inbox.filter(i => i.duplicate).length,
'Email': (Inbox._emails || []).filter(e => e.unread).length
};
}
function sourceChip(item) {
const m = item.sourceMeta;
// The dot carries the partner's brand colour; the label uses theme text.
// Rendering 11px labels in the partner colour failed AA in both themes.
// `--chip` carries the source colour; CSS mixes the tint. String-concat
// alpha ("#0a66c214") breaks for the tokenised sources (var(--c1)14).
return ` ${item.source} `;
}
function renderList() {
const el = document.getElementById('inboxList');
if (!el) return;
const list = filtered();
if (!list.length) { el.innerHTML = `
${UI.icon('inbox')}
Nothing here No applications in this view.
`; return; }
el.innerHTML = list.map(i => `
${UI.avatar(i.name, i.initials, i.color)}
${i.name} ${i.duplicate ? 'DUP ' : ''}
${i.position}
${sourceChip(i)} ${UI.badge(i.processing)}
${DB.relTime(Math.round((new Date('2026-07-09T20:00') - i.received) / 60000))}
${UI.scoreChip(i.atsScore)}
`).join('');
el.querySelectorAll('.inbox-item').forEach(row => row.onclick = () => {
state.selected = row.dataset.id;
const it = DB.inbox.find(x => x.id === state.selected); if (it) it.unread = false;
renderList(); renderDetail(); App.updateBadges();
});
}
function renderDetail() {
const el = document.getElementById('inboxDetail');
if (!el) return;
const i = DB.inbox.find(x => x.id === state.selected);
if (!i) { el.innerHTML = `${UI.icon('inbox')}
Select an application Choose an item from the list to view details and take action.
`; return; }
const rec = DB.atsRecommendationClass;
const recLabel = i.atsScore >= 82 ? 'Strong Match' : i.atsScore >= 65 ? 'Potential Match' : 'Weak Match';
el.innerHTML = `
${UI.avatar(i.name, i.initials, i.color, 'avatar-lg')}
${i.name}
${i.position}
${sourceChip(i)} ${UI.badge(i.processing)} ${UI.badge(i.resumeStatus, i.resumeStatus === 'Parsed' ? 'b-green' : i.resumeStatus === 'Failed' ? 'b-red' : 'b-amber')}
Experience
${i.experience} years
Assigned Recruiter
${i.recruiter}
Received
${DB.fmtDate(i.received)}
Match
${UI.badge(recLabel, rec(recLabel))}
${UI.icon('paperclip')} ${i.attachment}
${UI.icon('eye')} Preview
${Inbox._resumeText(i)}
${UI.icon('user-plus')} Import Candidate
${UI.icon('sparkles')} Parse Resume
${UI.icon('users')} Assign Recruiter
${UI.icon('layers')} Move to Pipeline
${UI.icon('edit')} Add Note
${UI.icon('x')} Reject
`;
}
function renderBody() {
const body = document.getElementById('inboxBody');
if (state.tab === 'Email') { body.innerHTML = Inbox._emailView(); Inbox._bindEmail(state); return; }
body.innerHTML = `
`;
renderList(); renderDetail();
const s = document.getElementById('inboxSearch');
s.oninput = () => { state.q = s.value; renderList(); };
}
Inbox._render = { list: renderList, detail: renderDetail, body: renderBody };
Inbox._state = state;
const c = counts();
const tabHtml = tabs.map(t => `${t} ${c[t]}
`).join('');
const html = `
Recruitment Inbox Every candidate, every source — one unified queue
Microsoft Graph API · Connected
${UI.icon('refresh')} Sync
${UI.icon('upload')} Upload CVs
`;
return {
html,
onMount() {
renderBody();
document.querySelectorAll('#inboxTabs .tab').forEach(tab => tab.onclick = () => {
state.tab = tab.dataset.tab; state.selected = null;
document.querySelectorAll('#inboxTabs .tab').forEach(t => t.classList.remove('active'));
tab.classList.add('active');
renderBody();
});
}
};
};
Inbox._resumeText = function (i) {
return `${i.name.toUpperCase()}\n${i.email} · ${i.phone}\n${'—'.repeat(30)}\nPROFESSIONAL SUMMARY\n${i.experience} years of experience. Applied for ${i.position} via ${i.source}.\n\nEXPERIENCE\n• ${DB.pick(DB.companies)} — Senior role (2021–Present)\n• ${DB.pick(DB.companies)} — Associate (2018–2021)\n\nEDUCATION\n• Bachelor's Degree, Computer Science\n\nSKILLS\n• ${DB.pick(DB.skillsPool)}, ${DB.pick(DB.skillsPool)}, ${DB.pick(DB.skillsPool)}, ${DB.pick(DB.skillsPool)}`;
};
Inbox.viewResume = function (id) {
const i = DB.inbox.find(x => x.id === id);
UI.modal({
title: i.attachment, subtitle: 'Resume preview · ' + i.name,
body: `${Inbox._resumeText(i)}
`,
footer: `Close ${UI.icon('user-plus')} Import Candidate `,
size: 'modal-lg'
});
};
Inbox.parse = function (id) {
const i = DB.inbox.find(x => x.id === id);
i.resumeStatus = 'Parsing';
Inbox._render.detail();
UI.toast('Parsing resume with AI…', 'info');
setTimeout(() => { i.resumeStatus = 'Parsed'; i.atsScore = DB.int(60, 96); Inbox._render.list(); Inbox._render.detail(); UI.toast('Resume parsed — profile fields extracted', 'success'); }, 1100);
};
Inbox.import = function (id) {
const i = DB.inbox.find(x => x.id === id);
const job = DB.getJob(i.jobId) || DB.jobs[0];
// create candidate
const newC = {
id: 'CAN-' + (5001 + DB.candidates.length), name: i.name, initials: i.initials, color: i.color,
email: i.email, phone: i.phone, jobId: job.id, jobTitle: job.title, department: job.department,
experience: i.experience, currentCompany: DB.pick(DB.companies), currentTitle: job.title, location: DB.pick(DB.locations),
stage: 'Applied', status: 'Applied', aiScore: i.atsScore, source: i.source, recruiter: i.recruiter, recruiterId: '',
applied: new Date('2026-07-09'), education: "Bachelor's Degree", skills: job.skills.slice(0, 4), rating: '4.0', salary: DB.int(90, 180) * 1000,
matchedSkills: job.skills.slice(0, 3), missingSkills: job.skills.slice(3), recommendation: i.atsScore >= 82 ? 'Strong Match' : 'Potential Match',
subScores: { skills: i.atsScore, experience: i.atsScore, education: 80, keywords: i.atsScore, location: 100, salary: 90 },
noticePeriod: '1 month', availability: '2 weeks', certifications: [], favorite: false, interviewStatus: 'Not Scheduled'
};
DB.candidates.unshift(newC);
i.processing = 'Imported'; i.unread = false;
Inbox._render.list(); Inbox._render.detail(); App.updateBadges();
UI.toast(`${i.name} imported → Applied stage of ${job.title}`, 'success');
};
Inbox.moveToPipeline = function (id) {
const i = DB.inbox.find(x => x.id === id);
if (i.processing !== 'Imported' && i.processing !== 'Processed') Inbox.import(id);
i.processing = 'Processed';
Inbox._render.list(); Inbox._render.detail();
UI.toast(`${i.name} moved to pipeline`, 'success');
setTimeout(() => Router.go('pipeline'), 700);
};
Inbox.assign = function (id) {
const i = DB.inbox.find(x => x.id === id);
const opts = DB.recruiters.map(r => `${r.name} `).join('');
UI.modal({
title: 'Assign Recruiter', subtitle: i.name,
body: `Recruiter ${opts}
Current workload is factored automatically. This recruiter has ${DB.getRecruiterByName(i.recruiter) ? DB.getRecruiterByName(i.recruiter).openReqs : 5} open reqs.
`,
footer: `Cancel Assign `
});
};
Inbox._doAssign = function (id) {
const i = DB.inbox.find(x => x.id === id);
i.recruiter = document.getElementById('assignRec').value;
UI.closeModal(); Inbox._render.detail();
UI.toast('Recruiter assigned to ' + i.name, 'success');
};
Inbox.note = function (id) {
const i = DB.inbox.find(x => x.id === id);
UI.modal({
title: 'Add Note', subtitle: i.name,
body: `Note
`,
footer: `Cancel ${UI.icon('check')} Save Note `
});
};
Inbox.reject = function (id) {
const i = DB.inbox.find(x => x.id === id);
i.processing = 'Rejected'; i.unread = false;
Inbox._render.list(); Inbox._render.detail(); App.updateBadges();
UI.toast(`${i.name} rejected`, 'warning');
};
// ---------------- Email (Outlook) tab ----------------
Inbox._emails = [];
Inbox._lastSync = null;
Inbox._mapApiEmail = function (row) {
const from = row.sender_name || row.fromEmail || 'Unknown';
return {
id: String(row.id),
from,
fromEmail: row.fromEmail || '',
subject: row.subject || '',
body: row.body || '',
when: row.when ? new Date(row.when) : new Date(),
unread: !!row.unread,
attachment: row.attachment_name || 'Resume.pdf',
attachmentSize: '—',
atsScore: 70,
imported: false,
jobId: null,
jobTitle: ''
};
};
Inbox._syncLabel = function () {
if (!Inbox._lastSync) return 'Not synced yet';
const mins = Math.max(0, Math.round((Date.now() - Inbox._lastSync.getTime()) / 60000));
if (mins < 1) return 'Just now';
return DB.relTime(mins);
};
Inbox._loadEmails = async function () {
const res = await Api.get('/inbox/fetch');
const rows = Array.isArray(res.data) ? res.data : [];
Inbox._emails = rows.map(Inbox._mapApiEmail);
Inbox._lastSync = new Date();
return Inbox._emails;
};
Inbox._refreshEmailCounts = function () {
const tab = document.querySelector('#inboxTabs .tab[data-tab="Email"]');
if (tab) {
const countEl = tab.querySelector('.k-count');
if (countEl) countEl.textContent = Inbox._emails.filter(e => e.unread).length;
}
if (window.App && App.updateBadges) App.updateBadges();
};
Inbox._emailView = function () {
return `
Outlook · Microsoft Graph API
Loading…
${UI.icon('refresh')} Sync Mailbox
${UI.icon('mail')}
Loading… Fetching mailbox from the server.
`;
};
Inbox._bindEmail = function (state) {
const detail = document.getElementById('emailDetail');
function renderDetail() {
const e = Inbox._emails.find(x => x.id === state.emailSelected);
if (!e) { detail.innerHTML = `${UI.icon('mail')}
Select an email Preview email body and resume attachments here.
`; return; }
detail.innerHTML = `
${e.subject} ${e.imported ? UI.badge('Imported', 'b-green') : UI.badge('New', 'b-blue')}
${UI.avatar(e.from, e.initials, e.color)}
${e.from}
${e.fromEmail} · ${DB.fmtDate(e.when)}
${e.body}
${UI.icon('file')}
${e.attachment}
${e.attachmentSize} · PDF
${UI.scoreChip(e.atsScore)}
${UI.icon('eye')} Preview
${e.imported ? `${UI.icon('check')} Already Imported ` :
`${UI.icon('user-plus')} Import Candidate `}
${UI.icon('mail')} Reply
${UI.icon('trash')} Archive
`;
}
function paintList() {
const list = document.getElementById('emailList');
const meta = document.getElementById('emailSyncMeta');
if (!list) return;
if (!Inbox._emails.length) {
list.innerHTML = `${UI.icon('mail')}
Nothing here No emails in the mailbox.
`;
} else {
list.innerHTML = Inbox._emails.map(e => `
${UI.avatar(e.from, e.initials, e.color)}
${DB.fmtShort(e.when)}
`).join('');
list.querySelectorAll('.inbox-item').forEach(row => row.onclick = () => {
state.emailSelected = row.dataset.email;
const e = Inbox._emails.find(x => x.id === state.emailSelected); if (e) e.unread = false;
list.querySelectorAll('.inbox-item').forEach(r => r.classList.remove('active', 'unread'));
row.classList.add('active');
renderDetail(); Inbox._refreshEmailCounts();
});
}
if (meta) meta.textContent = `Last sync: ${Inbox._syncLabel()} · ${Inbox._emails.filter(e => e.unread).length} unread`;
renderDetail();
Inbox._refreshEmailCounts();
}
Inbox._paintEmail = paintList;
renderDetail();
Inbox._loadEmails()
.then(() => paintList())
.catch(err => {
const list = document.getElementById('emailList');
const meta = document.getElementById('emailSyncMeta');
if (list) list.innerHTML = `${UI.icon('mail')}
Couldn't load mailbox ${err.message || 'Request failed'}
`;
if (meta) meta.textContent = 'Sync failed';
UI.toast(err.message || 'Failed to load mailbox', 'error');
renderDetail();
});
};
Inbox.syncMailbox = async function () {
UI.toast('Fetching from Outlook…', 'info');
try {
await Inbox._loadEmails();
if (typeof Inbox._paintEmail === 'function') Inbox._paintEmail();
UI.toast('Mailbox synced', 'success');
} catch (err) {
UI.toast(err.message || 'Sync failed', 'error');
}
};
Inbox._importEmail = function (id) {
const e = Inbox._emails.find(x => x.id === id);
if (!e) return;
const job = DB.getJob(e.jobId) || DB.jobs[0];
DB.candidates.unshift({
id: 'CAN-' + (5001 + DB.candidates.length), name: e.from, initials: e.initials || DB.initials(e.from), color: e.color || DB.avatarColor(e.from),
email: e.fromEmail, phone: '+1 (555) 000-0000', jobId: job.id, jobTitle: job.title, department: job.department,
experience: DB.int(2, 10), currentCompany: DB.pick(DB.companies), currentTitle: job.title, location: DB.pick(DB.locations),
stage: 'Applied', status: 'Applied', aiScore: e.atsScore, source: 'Microsoft Outlook', recruiter: DB.pick(DB.recruiters).name, recruiterId: '',
applied: new Date('2026-07-09'), education: "Bachelor's Degree", skills: job.skills.slice(0, 4), rating: '4.0', salary: 130000,
matchedSkills: job.skills.slice(0, 3), missingSkills: job.skills.slice(3), recommendation: 'Potential Match',
subScores: { skills: e.atsScore, experience: 80, education: 80, keywords: e.atsScore, location: 100, salary: 90 },
noticePeriod: '1 month', availability: '2 weeks', certifications: [], favorite: false, interviewStatus: 'Not Scheduled'
});
e.imported = true; e.unread = false;
if (typeof Inbox._paintEmail === 'function') Inbox._paintEmail();
App.updateBadges();
UI.toast(`${e.from} imported from Outlook → ${job.title}`, 'success');
};