import { apiPost } from '../api.js'; import { getRole, showToast } from '../app.js'; import { navigate } from '../router.js'; export function devPage() { if (getRole() !== 'admin') { navigate('#/'); return; } const app = document.getElementById('app'); app.innerHTML = `

Local / test only. Imports prefixed dev users (dev_*), clients (DEV-CL-*), and completed questionnaire runs. Existing real users are not modified. Conflicts are skipped (usernames, client codes, completions). Default password: socialvrlab. Questionnaires must already exist in the database.

Use dev-test-fixture.json from the project root (generate via scripts/generate_dev_fixture.py).

Reset database

Destructive. Deletes every client, completion, coach, and supervisor (including non-dev accounts). Admin logins and questionnaire definitions are kept.

`; let parsedFixture = null; document.getElementById('devFixtureFile').addEventListener('change', async (e) => { const file = e.target.files?.[0]; const summary = document.getElementById('devFixtureSummary'); const btn = document.getElementById('devImportBtn'); parsedFixture = null; btn.disabled = true; summary.style.display = 'none'; if (!file) return; try { const text = await file.text(); parsedFixture = JSON.parse(text); const st = parsedFixture.stats || {}; summary.innerHTML = ` Preview: ${parsedFixture.admins?.length ?? st.admins ?? 0} admins, ${parsedFixture.supervisors?.length ?? st.supervisors ?? 0} supervisors, ${parsedFixture.coaches?.length ?? st.coaches ?? 0} coaches, ${parsedFixture.clients?.length ?? st.clients ?? 0} clients, ${parsedFixture.completions?.length ?? st.completions ?? 0} completions `; summary.style.display = ''; btn.disabled = false; } catch (err) { showToast('Invalid JSON: ' + err.message, 'error'); } }); document.getElementById('devRemoveBtn').addEventListener('click', async () => { if (!confirm( 'Remove all dev test data?\n\n' + 'Deletes users matching dev_*, clients DEV-CL-*, and their answers/completions.\n' + 'Real accounts are not affected.' )) { return; } const btn = document.getElementById('devRemoveBtn'); const pre = document.getElementById('devImportResult'); btn.disabled = true; btn.textContent = 'Removing…'; pre.style.display = 'none'; try { const res = await apiPost('dev', { action: 'remove', usernamePrefix: 'dev_', clientCodePrefix: 'DEV-CL-', }); if (!res.success) { showToast(res.error || res.message || 'Remove failed', 'error'); return; } pre.textContent = JSON.stringify(res, null, 2); pre.style.display = ''; const d = res.deleted || {}; showToast( `Removed ${d.clients ?? 0} clients, ${d.users ?? 0} users, ` + `${d.completed_questionnaires ?? 0} completions`, 'success' ); } catch (e) { showToast(e.message, 'error'); } finally { btn.disabled = false; btn.textContent = 'Remove test data'; } }); document.getElementById('devWipeBtn').addEventListener('click', async () => { if (!confirm( 'Remove ALL clients, completions, coaches, and supervisors?\n\n' + 'Admin accounts and questionnaires will NOT be deleted.\n' + 'This cannot be undone.' )) { return; } const btn = document.getElementById('devWipeBtn'); const pre = document.getElementById('devImportResult'); btn.disabled = true; btn.textContent = 'Removing…'; pre.style.display = 'none'; try { const res = await apiPost('dev', { action: 'wipeExceptAdmins' }); if (!res.success) { showToast(res.error || res.message || 'Wipe failed', 'error'); return; } pre.textContent = JSON.stringify(res, null, 2); pre.style.display = ''; const d = res.deleted || {}; const kept = res.kept?.admins ?? '?'; showToast( `Wiped ${d.clients ?? 0} clients, ${d.users ?? 0} users, ` + `${d.completed_questionnaires ?? 0} completions (${kept} admins kept)`, 'success' ); } catch (e) { showToast(e.message, 'error'); } finally { btn.disabled = false; btn.textContent = 'Remove all data (keep admins)'; } }); document.getElementById('devImportBtn').addEventListener('click', async () => { if (!parsedFixture) return; const btn = document.getElementById('devImportBtn'); const pre = document.getElementById('devImportResult'); btn.disabled = true; btn.textContent = 'Importing…'; pre.style.display = 'none'; try { const res = await apiPost('dev', { fixture: parsedFixture }); if (!res.success) { showToast(res.error || 'Import failed', 'error'); return; } pre.textContent = JSON.stringify(res, null, 2); pre.style.display = ''; const imp = res.imported || {}; const sk = res.skipped || {}; showToast( `Imported: ${imp.completions ?? 0} completions, ${imp.clients ?? 0} clients, ` + `${imp.coaches ?? 0} coaches (skipped ${sk.completions ?? 0} existing)`, 'success' ); if (res.errors?.length) { showToast(`${res.errors.length} warning(s) — see log below`, 'error'); } } catch (e) { showToast(e.message, 'error'); } finally { btn.disabled = false; btn.textContent = 'Import fixture'; } }); }