// Carbone Word Template Manager - Frontend // Use same host as the page, just different port const CARBONE_API = window.location.protocol + '//' + window.location.hostname + ':8771/api/carbone'; // ============================================================ // NAVIGATION // ============================================================ // Load carbone templates when the templates page is shown const carboneNavObserver = new MutationObserver(() => { if (document.getElementById('page-templates') && document.getElementById('page-templates').classList.contains('active')) { loadCarboneTemplates(); } }); document.addEventListener('DOMContentLoaded', () => { const templatesPage = document.getElementById('page-templates'); if (templatesPage) carboneNavObserver.observe(templatesPage, { attributes: true, attributeFilter: ['class'] }); }); // ============================================================ // DOWNLOAD MASTER TEMPLATE // ============================================================ function downloadMasterTemplate() { window.open(CARBONE_API + '/master-template', '_blank'); } // ============================================================ // UPLOAD CUSTOM TEMPLATE // ============================================================ const carboneUploadZone = document.getElementById('carbone-upload-zone'); if (carboneUploadZone) { carboneUploadZone.addEventListener('dragover', e => { e.preventDefault(); carboneUploadZone.classList.add('dragover'); }); carboneUploadZone.addEventListener('dragleave', () => carboneUploadZone.classList.remove('dragover')); carboneUploadZone.addEventListener('drop', e => { e.preventDefault(); carboneUploadZone.classList.remove('dragover'); if (e.dataTransfer.files.length) uploadCarboneTemplate(e.dataTransfer.files[0]); }); } async function uploadCarboneTemplate(file) { if (!file) return; const name = document.getElementById('carbone-template-name').value || file.name.replace(/\.[^.]+$/, ''); const status = document.getElementById('carbone-upload-status'); status.innerHTML = '
Uploading template...
'; const formData = new FormData(); formData.append('template', file); formData.append('name', name); try { const resp = await fetch(CARBONE_API + '/templates/upload', { method: 'POST', body: formData }); const data = await resp.json(); if (data.success) { status.innerHTML = '
Template uploaded successfully!
'; toast('Template uploaded: ' + name); document.getElementById('carbone-template-name').value = ''; loadCarboneTemplates(); } else { status.innerHTML = '
Upload failed: ' + (data.error || 'Unknown error') + '
'; } } catch (e) { status.innerHTML = '
Upload failed: ' + e.message + '
'; } } // ============================================================ // LIST TEMPLATES // ============================================================ async function loadCarboneTemplates() { const list = document.getElementById('carbone-templates-list'); if (!list) return; list.innerHTML = '
Loading...
'; try { const resp = await fetch(CARBONE_API + '/templates'); const data = await resp.json(); if (!data.templates || !data.templates.length) { list.innerHTML = '

No custom templates uploaded yet. Download the master template, customize it in Word, and upload it here.

'; return; } list.innerHTML = data.templates.map(t => `
${t.name}
Uploaded: ${new Date(t.uploadedAt).toLocaleDateString()} ยท ${t.originalName}
`).join(''); } catch (e) { list.innerHTML = '

Error loading templates: ' + e.message + '

'; } } // ============================================================ // RENDER SAMPLE CV // ============================================================ async function renderSampleCV(templateId) { toast('Generating sample CV PDF...'); // Get first candidate for sample data try { const candidatesResp = await fetch('/api/candidates?limit=1'); const candidatesData = await candidatesResp.json(); let cvData = null; if (candidatesData.candidates && candidatesData.candidates.length > 0) { const fullResp = await fetch('/api/candidates/' + candidatesData.candidates[0].id); cvData = await fullResp.json(); } if (!cvData) { toast('Upload a CV first to generate a sample', 'error'); return; } const resp = await fetch(CARBONE_API + '/templates/' + templateId + '/render', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ candidateData: cvData }) }); if (!resp.ok) { const err = await resp.json(); throw new Error(err.error || 'Render failed'); } const blob = await resp.blob(); const url = URL.createObjectURL(blob); window.open(url, '_blank'); toast('Sample CV generated'); } catch (e) { toast('Render failed: ' + e.message, 'error'); } } // ============================================================ // DELETE TEMPLATE // ============================================================ async function deleteCarboneTemplate(templateId) { if (!confirm('Delete this template?')) return; try { await fetch(CARBONE_API + '/templates/' + templateId, { method: 'DELETE' }); toast('Template deleted'); loadCarboneTemplates(); } catch (e) { toast('Delete failed: ' + e.message, 'error'); } } // ============================================================ // TAGS HELP - View in app // ============================================================ async function viewTagsHelp() { try { const resp = await fetch(CARBONE_API + '/help'); const data = await resp.json(); let html = '
'; for (const section of data.sections) { html += `

${section.title}

`; if (section.note) html += `

${section.note}

`; html += ''; for (const t of section.tags) { html += ``; } html += '
TagDescription
${escapeHtml(t.tag)}${escapeHtml(t.desc)}
'; } html += '
'; html += '
'; showModal(html, 'Carbone Tags Reference'); } catch (e) { toast('Failed to load help: ' + e.message, 'error'); } } // ============================================================ // TAGS HELP - Download .docx // ============================================================ function downloadHelpDocx() { window.open(CARBONE_API + '/help-docx', '_blank'); }