/* ============================================================ jobboard.js — Job Posting Center: publish + track performance ============================================================ */ window.Views = window.Views || {}; window.JobBoard = {}; Views.jobboard = function () { const totals = DB.publishings.reduce((a, p) => ({ views: a.views + p.views, clicks: a.clicks + p.clicks, apps: a.apps + p.apps }), { views: 0, clicks: 0, apps: 0 }); const conv = totals.views ? ((totals.apps / totals.views) * 100).toFixed(1) : 0; const statCard = (label, val, icn, cls, sub) => `
${label}${UI.icon(icn)}
${val}
${sub}
`; // per-platform aggregate const platAgg = {}; DB.publishings.forEach(p => { if (!platAgg[p.platform]) platAgg[p.platform] = { views: 0, clicks: 0, apps: 0, jobs: 0 }; platAgg[p.platform].views += p.views; platAgg[p.platform].clicks += p.clicks; platAgg[p.platform].apps += p.apps; platAgg[p.platform].jobs++; }); const platRows = Object.entries(platAgg).sort((a, b) => b[1].apps - a[1].apps); // publishing table const table = UI.dataTable({ pageSize: 8, rows: DB.publishings, columns: [ { key: 'jobTitle', label: 'Job', sortable: true, render: p => `
${p.jobTitle}
${p.jobId}
` }, { key: 'platform', label: 'Platform', sortable: true, render: p => { const pl = DB.publishPlatforms.find(x => x.name === p.platform) || {}; return `${p.platform}`; } }, { key: 'status', label: 'Status', sortable: true, render: p => UI.badge(p.status, p.status === 'Live' ? 'b-green' : p.status === 'Paused' ? 'b-amber' : 'b-blue') }, { key: 'views', label: 'Views', sortable: true, align: 'right', render: p => p.views.toLocaleString() }, { key: 'clicks', label: 'Clicks', sortable: true, align: 'right', render: p => p.clicks.toLocaleString() }, { key: 'apps', label: 'Applications', sortable: true, align: 'right', render: p => `${p.apps}` }, { key: '_conv', label: 'Conversion', sortable: true, sortValue: p => p.apps / p.views, render: p => `${((p.apps / p.views) * 100).toFixed(1)}%` }, { key: '_a', label: '', align: 'right', render: p => `` } ] }); const html = `

Job Board

Publish requisitions across channels and track performance

${statCard('Total Views', totals.views.toLocaleString(), 'eye', 'i-blue', 'across all platforms')} ${statCard('Total Clicks', totals.clicks.toLocaleString(), 'target', 'i-purple', ((totals.clicks / totals.views) * 100).toFixed(1) + '% CTR')} ${statCard('Applications', totals.apps.toLocaleString(), 'users', 'i-green', 'from job boards')} ${statCard('Conversion Rate', conv + '%', 'trending-up', 'i-teal', 'view → application')}

Platform Performance

Applications by channel

Connected Platforms

${DB.publishPlatforms.map(p => `
${p.name}
${p.cost === 'Free' ? 'Free posting' : 'Paid · ' + p.cost}
${p.connected ? UI.badge('Connected', 'b-green') : ``}
`).join('')}

Active Postings

${DB.publishings.length} live postings across ${platRows.length} platforms
${table.html}
`; return { html, onMount() { table.mount(); Charts.horizontalBar(document.getElementById('jbChart'), { labels: platRows.map(p => p[0]), data: platRows.map(p => p[1].apps) }); } }; }; // ---------------- Publish flow (stepper modal) ---------------- JobBoard.publishFlow = function (jobId) { const state = { step: 1, jobId: jobId || DB.jobs.filter(j => j.status === 'Open')[0].id, platforms: ['Career Portal'] }; JobBoard._state = state; JobBoard._renderFlow(); }; JobBoard._renderFlow = function () { const state = JobBoard._state; const steps = ['Select Job', 'Approval', 'Platforms', 'Publish']; const stepper = `
${steps.map((s, i) => { const n = i + 1; const cls = n < state.step ? 'done' : n === state.step ? 'active' : ''; return `
${n < state.step ? '✓' : n}
${s}
${i < steps.length - 1 ? `
` : ''}`; }).join('')}
`; let body = stepper; if (state.step === 1) { const opts = DB.jobs.filter(j => j.status !== 'Draft').map(j => ``).join(''); const job = DB.getJob(state.jobId); body += `
${UI.icon('briefcase')}
${job.title}
${job.department} · ${job.location} · ${job.type}
`; } else if (state.step === 2) { body += `
${UI.icon('check-circle')}
Approval granted
Approved by Department Head · Budget confirmed

Hiring Manager sign-off

${UI.badge('Approved', 'b-green')}

Finance budget approval

${UI.badge('Approved', 'b-green')}

Compliance review

${UI.badge('Approved', 'b-green')}
`; } else if (state.step === 3) { body += `

Select the platforms to publish this role to

${DB.publishPlatforms.map(p => `
${p.name}
${p.cost === 'Free' ? 'Free' : 'Paid · ' + p.cost}
${UI.icon('check')}
`).join('')}
`; } else if (state.step === 4) { body += `
${UI.icon('check-circle')}

Published Successfully

${DB.getJob(state.jobId).title} is now live on ${state.platforms.length} platform${state.platforms.length > 1 ? 's' : ''}

${state.platforms.map(p => { const pl = DB.publishPlatforms.find(x => x.name === p); return `${pl.name}`; }).join('')}
`; } let footer; if (state.step === 4) footer = ``; else footer = ` `; UI.modal({ title: 'Publish Job', subtitle: 'Distribute this requisition to job boards', body, footer, size: 'modal-lg' }); if (state.step === 3) { document.querySelectorAll('#platGrid .platform-card').forEach(card => card.onclick = () => { const name = card.dataset.plat; const idx = state.platforms.indexOf(name); if (idx > -1) state.platforms.splice(idx, 1); else state.platforms.push(name); card.classList.toggle('selected'); }); } }; JobBoard._next = function () { const state = JobBoard._state; if (state.step === 1) { const sel = document.getElementById('pubJob'); if (sel) state.jobId = sel.value; } if (state.step === 3 && !state.platforms.length) { UI.toast('Select at least one platform', 'warning'); return; } state.step++; if (state.step === 4) { // create publishing records const job = DB.getJob(state.jobId); state.platforms.forEach(p => { DB.publishings.unshift({ jobId: job.id, jobTitle: job.title, platform: p, status: 'Live', views: DB.int(0, 30), clicks: 0, apps: 0, published: new Date('2026-07-09') }); }); } JobBoard._renderFlow(); }; JobBoard._back = function () { JobBoard._state.step--; JobBoard._renderFlow(); };