added submission history, data insights, download all functionality

This commit is contained in:
2026-06-01 12:54:56 +02:00
parent 9a6fa22d84
commit bee7b74e53
19 changed files with 1863 additions and 110 deletions

View File

@ -11,9 +11,22 @@ export function devPage() {
const app = document.getElementById('app');
app.innerHTML = `
<div class="page-header">
<h1>Dev import</h1>
<h1>Admin Settings</h1>
</div>
<div class="card" style="max-width:720px;margin-bottom:16px">
<h2 style="margin:0 0 8px;font-size:1.1rem">Export all response data (ZIP)</h2>
<p class="field-hint" style="margin:0 0 14px">
Download one CSV per questionnaire in a single ZIP (full server data).
<strong>Current data</strong> uses live answers;
<strong>All versions</strong> includes every upload (one row per submission).
</p>
<div style="display:flex;flex-wrap:wrap;gap:10px">
<button type="button" class="btn btn-primary" id="exportAllCurrentZipBtn">Export all — current data (ZIP)</button>
<button type="button" class="btn" id="exportAllVersionsZipBtn">Export all — all versions (ZIP)</button>
</div>
</div>
<div class="card" style="max-width:720px">
<h2 style="margin:0 0 8px;font-size:1.1rem">Dev import</h2>
<p class="field-hint callout-warn">
<strong>Local / test only.</strong> Imports prefixed dev users (<code>dev_*</code>),
clients (<code>DEV-CL-*</code>), and completed questionnaire runs.
@ -150,6 +163,8 @@ export function devPage() {
}
});
wireAdminZipExport();
document.getElementById('devImportBtn').addEventListener('click', async () => {
if (!parsedFixture) return;
const btn = document.getElementById('devImportBtn');
@ -184,3 +199,62 @@ export function devPage() {
}
});
}
function filenameFromContentDisposition(res, fallback) {
const disp = res.headers.get('Content-Disposition');
if (!disp) return fallback;
const m = /filename="([^"]+)"/i.exec(disp);
return m ? m[1] : fallback;
}
async function downloadExportZip(url, defaultFilename) {
const token = localStorage.getItem('qdb_token');
const res = await fetch(url, {
headers: { Authorization: `Bearer ${token}` },
});
if (!res.ok) {
const err = await res.json().catch(() => ({ error: 'Download failed' }));
throw new Error(err.error?.message || err.error || 'Download failed');
}
const blob = await res.blob();
const blobUrl = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = blobUrl;
a.download = filenameFromContentDisposition(res, defaultFilename);
a.click();
URL.revokeObjectURL(blobUrl);
}
function wireAdminZipExport() {
document.getElementById('exportAllCurrentZipBtn')?.addEventListener('click', async () => {
const btn = document.getElementById('exportAllCurrentZipBtn');
btn.disabled = true;
const prev = btn.textContent;
btn.textContent = 'Preparing ZIP…';
try {
await downloadExportZip('../api/export?exportAll=1', 'responses_export_current.zip');
showToast('ZIP download started (current data)', 'success');
} catch (e) {
showToast(e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = prev;
}
});
document.getElementById('exportAllVersionsZipBtn')?.addEventListener('click', async () => {
const btn = document.getElementById('exportAllVersionsZipBtn');
btn.disabled = true;
const prev = btn.textContent;
btn.textContent = 'Preparing ZIP…';
try {
await downloadExportZip('../api/export?exportAll=1&allVersions=1', 'responses_export_all_versions.zip');
showToast('ZIP download started (all versions)', 'success');
} catch (e) {
showToast(e.message, 'error');
} finally {
btn.disabled = false;
btn.textContent = prev;
}
});
}