registerPage('tests', async (content, params = {}) => { document.getElementById('topbar-title').textContent = 'Tests'; async function render(search = '') { const r = await api('tests.php', { action: 'list', search }); if (!r.success) { document.getElementById('tests-table').innerHTML = emptyHTML(); return; } const rows = r.tests; document.getElementById('tests-table').innerHTML = rows.length === 0 ? emptyHTML('No tests found') : `
${rows.map(t => ``).join('')}
NameDescriptionNQFCreditsPass%ExpiryActions
${t.test_name} ${t.test_description || '—'} ${t.nqf_level} ${t.credits} ${(+t.passmark * 100).toFixed(0)}% ${t.expiry} mo
`; } content.innerHTML = `
${loadingHTML()}
`; document.getElementById('test-search').addEventListener('input', e => render(e.target.value)); render(); function fillTestForm(t = null) { document.getElementById('test-modal-title').textContent = t ? 'Edit Test' : 'New Test'; document.getElementById('tf-id').value = t?.record_id || ''; document.getElementById('tf-name').value = t?.test_name || ''; document.getElementById('tf-desc').value = t?.test_description || ''; document.getElementById('tf-nqf').value = t?.nqf_level || 1; document.getElementById('tf-credits').value = t?.credits || 1; document.getElementById('tf-passmark').value = t?.passmark || '0.8'; document.getElementById('tf-expiry').value = t?.expiry || 36; } window.testAdd = () => { fillTestForm(); openModal('test-modal'); }; window.testEdit = async (id) => { const r = await api('tests.php', { action: 'get', id }); if (!r.success) { toast('Failed', 'error'); return; } fillTestForm(r.test); openModal('test-modal'); }; window.testSave = async () => { const id = document.getElementById('tf-id').value; const r = await api('tests.php', { action: id ? 'update' : 'create', id, test_name: document.getElementById('tf-name').value, test_description: document.getElementById('tf-desc').value, nqf_level: document.getElementById('tf-nqf').value, credits: document.getElementById('tf-credits').value, passmark: document.getElementById('tf-passmark').value, expiry: document.getElementById('tf-expiry').value, }, 'POST'); if (r.success) { toast(r.message, 'success'); closeModal('test-modal'); render(); } else toast(r.error, 'error'); }; window.testDelete = async (id) => { if (!confirm('Delete this test?')) return; const r = await api('tests.php', { action: 'delete', id }, 'POST'); if (r.success) { toast('Deleted', 'success'); render(); } else toast(r.error, 'error'); }; // ── Builder ──────────────────────────────────────────────────────────────── window.testBuilder = async (id) => { await renderTestBuilder(id); openModal('test-builder-modal'); }; async function renderTestBuilder(testId) { const body = document.getElementById('test-builder-body'); body.innerHTML = loadingHTML(); const r = await api('tests.php', { action: 'get', id: testId }); if (!r.success) { body.innerHTML = emptyHTML(); return; } const t = r.test; document.getElementById('test-builder-title').textContent = `Build: ${t.test_name}`; const questionsHtml = (t.questions || []).map(q => `
${q.section_name}

Options (tick = correct answer)

${(q.answers || []).map(a => `
${a.answer} ${+a.option === 1 ? 'CORRECT' : ''}
`).join('') || '

No options yet.

'}
`).join(''); body.innerHTML = `
${questionsHtml || '

No questions yet.

'}
`; } window.addTestQuestion = async (testId) => { const name = document.getElementById('new-tq-name').value.trim(); if (!name) { toast('Enter question text', 'error'); return; } const r = await api('tests.php', { action: 'add_question', test_id: testId, section_name: name }, 'POST'); if (r.success) { toast('Question added', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; window.editTestQuestion = async (qId, current, testId) => { const name = prompt('Edit question:', current); if (!name || name === current) return; const r = await api('tests.php', { action: 'update_question', question_id: qId, section_name: name }, 'POST'); if (r.success) { toast('Updated', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; window.deleteTestQuestion = async (qId, testId) => { if (!confirm('Delete question and all its options?')) return; const r = await api('tests.php', { action: 'delete_question', question_id: qId }, 'POST'); if (r.success) { toast('Deleted', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; window.addAnswer = async (qId, testId) => { const text = document.getElementById(`new-ans-${qId}`).value.trim(); const correct = document.getElementById(`new-ans-correct-${qId}`).checked ? 1 : 0; if (!text) { toast('Enter option text', 'error'); return; } const r = await api('tests.php', { action: 'add_answer', test_question_id: qId, answer: text, option: correct, comment: '' }, 'POST'); if (r.success) { toast('Option added', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; window.editAnswer = async (aId, qId, testId) => { const text = prompt('Edit option text:'); if (!text) return; const correct = confirm('Is this the correct answer?') ? 1 : 0; const r = await api('tests.php', { action: 'update_answer', answer_id: aId, answer: text, option: correct, comment: '' }, 'POST'); if (r.success) { toast('Option updated', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; window.deleteAnswer = async (aId, testId) => { if (!confirm('Delete option?')) return; const r = await api('tests.php', { action: 'delete_answer', answer_id: aId }, 'POST'); if (r.success) { toast('Option deleted', 'success'); await renderTestBuilder(testId); } else toast(r.error, 'error'); }; });