/* ============================================================ SURVAM — Survey Builder (builder.js) ============================================================ */ 'use strict'; const Builder = (() => { let state = { surveyId: null, pages: [], // [{id, page_number, title, questions:[...]}] activePageIdx: 0, activeQIdx: null, dirty: false, saving: false, }; // ── Init ─────────────────────────────────────────────────── function init(surveyId, surveyData) { state.surveyId = surveyId; if (surveyData && surveyData.pages) { state.pages = surveyData.pages; } else { state.pages = [newPage(1)]; } renderAll(); bindEvents(); autoSave(); } // ── Data helpers ────────────────────────────────────────── function newPage(num) { return { id: null, page_number: num, title: `Page ${num}`, description: '', questions: [] }; } function newQuestion(type) { const q = { id: null, type, title: 'Untitled question', description: '', required: false, options: defaultOptions(type), settings: defaultSettings(type), logic: [], loop_config: null, pipe_config: null, media_url: '', media_type: 'none', _uid: 'q_' + Math.random().toString(36).substr(2,9), }; return q; } function defaultOptions(type) { const choiceTypes = ['single_choice','multi_choice','dropdown','image_choice']; if (choiceTypes.includes(type)) return { choices: ['Option 1','Option 2','Option 3'] }; if (type === 'rating_scale') return { min:1, max:10, min_label:'Not at all', max_label:'Extremely' }; if (type === 'star_rating') return { stars:5 }; if (type === 'nps') return { min_label:'Not likely', max_label:'Extremely likely' }; if (type === 'slider') return { min:0, max:100, step:1, default:50 }; if (type === 'likert_scale') return { scale: ['Strongly Disagree','Disagree','Neutral','Agree','Strongly Agree'], statements: ['Statement 1'] }; if (type === 'matrix_single' || type === 'matrix_multi') return { rows:['Row 1','Row 2'], cols:['Col 1','Col 2','Col 3'] }; if (type === 'ranking') return { items: ['Item 1','Item 2','Item 3'] }; if (type === 'emoji_scale') return { emojis: [{icon:'😡',label:'Very Bad'},{icon:'😟',label:'Bad'},{icon:'😐',label:'Neutral'},{icon:'😊',label:'Good'},{icon:'😍',label:'Excellent'}] }; if (type === 'constant_sum') return { items:['Item 1','Item 2','Item 3'], total:100 }; if (type === 'max_diff') return { items:['Option A','Option B','Option C','Option D'], sets_count:3, per_set:3 }; if (type === 'demographic') return { fields: ['name','email','age','gender','location'] }; if (type === 'date_time') return { mode: 'date' }; // date|time|datetime if (type === 'number') return { min:'', max:'', decimal:false }; if (type === 'file_upload') return { max_mb:5, allowed:'image/*,application/pdf' }; if (type === 'heatmap') return { image_url:'', max_clicks:3 }; if (type === 'card_sort') return { cards:['Card 1','Card 2','Card 3'], categories:['Category A','Category B'] }; return {}; } function defaultSettings(type) { return { randomize: false, other_option: false, none_option: false, other_text: 'Other (please specify)' }; } // ── Render ──────────────────────────────────────────────── function renderAll() { renderCanvas(); renderPageTabs(); if (state.activeQIdx !== null) renderProps(); else renderNoSelection(); } function renderPageTabs() { const tabs = document.getElementById('pageTabs'); if (!tabs) return; tabs.innerHTML = state.pages.map((p,i) => ` `).join('') + ``; } function renderCanvas() { const canvas = document.getElementById('builderCanvas'); if (!canvas) return; const page = state.pages[state.activePageIdx]; if (!page) return; canvas.innerHTML = `
${state.pages.length > 1 ? `` : ''}
${page.questions.length === 0 ? `
📋

Drag a question type from the left panel,
or click a type to add it here.

` : ''} ${page.questions.map((q,qi) => renderQuestionBlock(q, qi)).join('')}
`; initQuestionDrag(); } function renderQuestionBlock(q, qi) { const isActive = qi === state.activeQIdx; const mediaPreview = (q.media_url && q.media_type && q.media_type !== 'none') ? `
${q.media_type === 'image' ? `` : ''} ${q.media_type === 'image_upload' ? `` : ''} ${q.media_type === 'video' ? `
🎬 Video stimulus
${escH(q.media_url.substring(0,50))}${q.media_url.length>50?'...':''}
` : ''}
` : ''; return `
${qi + 1}
${questionTypeLabel(q.type)}
${mediaPreview}
${escH(q.title || 'Untitled question')}${q.required ? ' *' : ''}
`; } function renderProps() { const panel = document.getElementById('propsPanel'); if (!panel) return; const page = state.pages[state.activePageIdx]; const q = page?.questions[state.activeQIdx]; if (!q) { renderNoSelection(); return; } panel.innerHTML = buildPropsHTML(q, state.activeQIdx); } function renderNoSelection() { const panel = document.getElementById('propsPanel'); if (!panel) return; panel.innerHTML = `
👈

Click a question to edit its properties

`; } function buildPropsHTML(q, qi) { const s = q.settings || {}; const o = q.options || {}; let optionsHTML = buildOptionsHTML(q); return `
Question Properties
${!['statement','section_break'].includes(q.type) ? `
Required
` : `
Display only — no response needed.
`} ${['single_choice','multi_choice','dropdown','ranking','card_sort'].includes(q.type) ? `
Randomize options
` : ''} ${['single_choice','multi_choice'].includes(q.type) ? `
Add "Other" option
Add "None" option
` : ''} ${q.type === 'multi_choice' ? ` ` : ''}
${optionsHTML}
Branching / Skip Logic
📎 Stimulus Media
${(q.media_type && q.media_type !== 'none') ? ` ${q.media_type === 'image_upload' ? `
${q.media_url ? `` : '
🖼
'} ${q.media_url ? `
✓ Image uploaded
` : '
JPG, PNG, GIF, WebP · Max 5MB
'}
` : ` ${q.media_url && q.media_type==='image' ? `` : ''} ${q.media_url && q.media_type==='video' ? `
✓ Video URL saved — will embed in survey
` : ''} `}
Show media above question
` : ''}
Piping
Pipe answer from earlier question
${q.pipe_config ? `` : ''}
`; } function buildOptionsHTML(q) { const o = q.options || {}; const qi = state.activeQIdx; if (['single_choice','multi_choice','dropdown','image_choice'].includes(q.type)) { const choices = o.choices || []; const screenouts = o.screenout_urls || {}; return `
Answer Options
Tip: Add a 🚫 Screenout URL per option to redirect disqualified respondents.
${choices.map((c,ci) => `
🚫 Screenout:
`).join('')}
`; } if (q.type === 'ranking') { const items = o.items || []; return `
Items to Rank
${items.map((item,ii) => `
`).join('')}
`; } if (q.type === 'likert_scale') { const stmts = o.statements || []; return `
Statements
${stmts.map((s,si) => `
`).join('')}
Scale Labels
${(o.scale||[]).map((lbl,li) => `
`).join('')}
`; } if (q.type === 'matrix_single' || q.type === 'matrix_multi') { return `
Rows
${(o.rows||[]).map((r,ri) => `
`).join('')}
Columns
${(o.cols||[]).map((c,ci) => `
`).join('')}
`; } if (q.type === 'rating_scale' || q.type === 'nps') { return `
Scale Settings
`; } if (q.type === 'slider') { return `
Slider Settings
`; } if (q.type === 'constant_sum') { return `
Items
${(o.items||[]).map((item,ii) => `
`).join('')}
`; } return ''; } // ── Logic Editor ────────────────────────────────────────── function openLogicEditor(qi) { const page = state.pages[state.activePageIdx]; const q = page.questions[qi]; const prevQs = page.questions.slice(0, qi); const el = document.getElementById('logicEditorModal'); if (!el) return; document.getElementById('logicEditorTitle').textContent = `Logic: ${q.title}`; document.getElementById('logicEditorBody').innerHTML = buildLogicEditorHTML(q, prevQs, qi); openModal('logicEditorModal'); } function buildLogicEditorHTML(q, prevQs, qi) { const rules = q.logic || []; if (prevQs.length === 0) return `

Add at least one question before this one to create logic rules.

`; return `

Show/skip this question based on answers to previous questions.

${rules.map((rule,ri) => renderLogicRule(rule, ri, prevQs)).join('')}
Respondent is sent here when screened out.
`; } function renderLogicRule(rule, ri, prevQs) { return `
`; } // ── Quota Editor ────────────────────────────────────────── function openQuotaEditor() { apiPost(window.SURVAM_API_URL, { action:'get_quotas', survey_id: state.surveyId }) .then(r => { const el = document.getElementById('quotaEditorModal'); if (!el) return; document.getElementById('quotaEditorBody').innerHTML = buildQuotaHTML(r.quotas || []); openModal('quotaEditorModal'); }); } function buildQuotaHTML(quotas) { return `
${quotas.length === 0 ? `

No quotas set. Add a quota to cap responses by demographic or answer.

` : ''} ${quotas.map((q,i) => `
${escH(q.name)} — Limit: ${q.limit_count} (Used: ${q.current_count})
`).join('')}

Add New Quota

`; } // ── Save ────────────────────────────────────────────────── async function save(showMsg = true) { if (state.saving) return; state.saving = true; const saveBtn = document.getElementById('saveBtn'); if (saveBtn) { saveBtn.disabled = true; saveBtn.innerHTML = ' Saving…'; } try { const res = await apiPost(window.SURVAM_API_URL, { action: 'save_structure', survey_id: state.surveyId, pages: state.pages, }); if (res.success) { state.dirty = false; // Update IDs from server response if (res.page_ids) res.page_ids.forEach((pid,i) => { if (state.pages[i]) state.pages[i].id = pid; }); if (res.question_ids) { let qi = 0; state.pages.forEach(p => { p.questions.forEach(q => { if (res.question_ids[qi]) q.id = res.question_ids[qi]; qi++; }); }); } if (showMsg) showToast('Survey saved!', 'success'); } else { showToast(res.error || 'Save failed', 'error'); } } catch(e) { showToast('Save failed. Check connection.', 'error'); } state.saving = false; if (saveBtn) { saveBtn.disabled = false; saveBtn.innerHTML = 'Save'; } } function autoSave() { setInterval(() => { if (state.dirty) save(false); }, 30000); window.addEventListener('beforeunload', (e) => { if (state.dirty) { e.preventDefault(); e.returnValue = ''; } }); } // ── Public API (prefixed with _ for internal use) ───────── function _addQ(type) { const page = state.pages[state.activePageIdx]; const q = newQuestion(type); page.questions.push(q); state.activeQIdx = page.questions.length - 1; state.dirty = true; renderAll(); document.querySelector(`[data-qi="${state.activeQIdx}"]`)?.scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } function _selectQ(qi) { state.activeQIdx = qi; renderCanvas(); renderProps(); } function _deleteQ(qi) { if (!confirm('Delete this question?')) return; state.pages[state.activePageIdx].questions.splice(qi, 1); state.activeQIdx = null; state.dirty = true; renderAll(); } function _duplicateQ(qi) { const q = JSON.parse(JSON.stringify(state.pages[state.activePageIdx].questions[qi])); q.id = null; q._uid = 'q_' + Math.random().toString(36).substr(2,9); q.title = q.title + ' (copy)'; state.pages[state.activePageIdx].questions.splice(qi + 1, 0, q); state.activeQIdx = qi + 1; state.dirty = true; renderAll(); } function _moveQ(qi, dir) { const qs = state.pages[state.activePageIdx].questions; const ni = qi + dir; if (ni < 0 || ni >= qs.length) return; [qs[qi], qs[ni]] = [qs[ni], qs[qi]]; state.activeQIdx = ni; state.dirty = true; renderAll(); } function _setField(qi, field, val) { state.pages[state.activePageIdx].questions[qi][field] = val; state.dirty = true; // Re-render only the block title const block = document.querySelector(`[data-qi="${qi}"] .question-title-display`); if (block && field === 'title') block.innerHTML = escH(val) + (state.pages[state.activePageIdx].questions[qi].required ? ' *' : ''); if (field === 'required') { const star = document.querySelector(`[data-qi="${qi}"] .question-title-display`); if(star) star.innerHTML = escH(state.pages[state.activePageIdx].questions[qi].title) + (val ? ' *' : ''); } } function _setSetting(qi, key, val) { if (!state.pages[state.activePageIdx].questions[qi].settings) state.pages[state.activePageIdx].questions[qi].settings = {}; state.pages[state.activePageIdx].questions[qi].settings[key] = val; state.dirty = true; } function _setOpt(qi, key, val) { if (!state.pages[state.activePageIdx].questions[qi].options) state.pages[state.activePageIdx].questions[qi].options = {}; state.pages[state.activePageIdx].questions[qi].options[key] = val; state.dirty = true; } function _setChoice(qi, ci, val) { state.pages[state.activePageIdx].questions[qi].options.choices[ci] = val; state.dirty = true; } function _addChoice(qi) { const choices = state.pages[state.activePageIdx].questions[qi].options.choices; choices.push('Option ' + (choices.length + 1)); state.dirty = true; renderProps(); } function _setScreenoutUrl(qi, ci, url) { const q = state.pages[state.activePageIdx].questions[qi]; if (!q.options.screenout_urls) q.options.screenout_urls = {}; q.options.screenout_urls[ci] = url; state.dirty = true; } function _removeChoice(qi, ci) { state.pages[state.activePageIdx].questions[qi].options.choices.splice(ci, 1); state.dirty = true; renderProps(); } function _setOption(qi, key, idx, val) { state.pages[state.activePageIdx].questions[qi].options[key][idx] = val; state.dirty = true; } function _addOptionItem(qi, key, defaultVal) { const arr = state.pages[state.activePageIdx].questions[qi].options[key] || []; arr.push(defaultVal + ' ' + (arr.length + 1)); state.pages[state.activePageIdx].questions[qi].options[key] = arr; state.dirty = true; renderProps(); } function _removeOptionItem(qi, key, idx) { state.pages[state.activePageIdx].questions[qi].options[key].splice(idx, 1); state.dirty = true; renderProps(); } function _changeType(qi, newType) { const q = state.pages[state.activePageIdx].questions[qi]; q.type = newType; q.options = defaultOptions(newType); q.settings = defaultSettings(newType); state.dirty = true; renderAll(); } function _bulkEditChoices(qi) { const choices = state.pages[state.activePageIdx].questions[qi].options.choices || []; const val = prompt('Enter one option per line:', choices.join('\n')); if (val !== null) { state.pages[state.activePageIdx].questions[qi].options.choices = val.split('\n').map(s => s.trim()).filter(Boolean); state.dirty = true; renderProps(); } } function _addPage() { state.pages.push(newPage(state.pages.length + 1)); state.activePageIdx = state.pages.length - 1; state.activeQIdx = null; state.dirty = true; renderAll(); } function _selectPage(idx) { state.activePageIdx = idx; state.activeQIdx = null; renderAll(); } function _deletePage(idx) { if (!confirm('Delete this page and all its questions?')) return; state.pages.splice(idx, 1); state.activePageIdx = Math.max(0, idx - 1); state.activeQIdx = null; state.dirty = true; renderAll(); } function _setPageTitle(idx, val) { state.pages[idx].title = val; state.dirty = true; renderPageTabs(); } function _setPageDesc(idx, val) { state.pages[idx].description = val; state.dirty = true; } function _addLogicRule(qi) { const q = state.pages[state.activePageIdx].questions[qi]; if (!q.logic) q.logic = []; q.logic.push({ q_uid: '', operator: 'equals', value: '' }); state.dirty = true; openLogicEditor(qi); } function _setLogicQ(ri, val) { const q = state.pages[state.activePageIdx].questions[state.activeQIdx]; if (q.logic[ri]) q.logic[ri].q_uid = val; state.dirty = true; } function _setLogicOp(ri, val) { const q = state.pages[state.activePageIdx].questions[state.activeQIdx]; if (q.logic[ri]) q.logic[ri].operator = val; state.dirty = true; } function _setLogicVal(ri, val) { const q = state.pages[state.activePageIdx].questions[state.activeQIdx]; if (q.logic[ri]) q.logic[ri].value = val; state.dirty = true; } function _removeLogicRule(ri) { const q = state.pages[state.activePageIdx].questions[state.activeQIdx]; q.logic.splice(ri, 1); state.dirty = true; openLogicEditor(state.activeQIdx); } function _saveLogic(qi) { const q = state.pages[state.activePageIdx].questions[qi]; q.logic_match = document.getElementById('logicMatchMode')?.value || 'all'; q.logic_action = document.getElementById('logicAction')?.value || 'show'; q.screenout_url = document.getElementById('screenoutUrl')?.value || ''; state.dirty = true; closeModal('logicEditorModal'); renderProps(); showToast('Logic saved', 'success'); } function _togglePipe(qi, enabled) { const q = state.pages[state.activePageIdx].questions[qi]; q.pipe_config = enabled ? { source: '' } : null; state.dirty = true; renderProps(); } function _setPipeSource(qi, uid) { state.pages[state.activePageIdx].questions[qi].pipe_config.source = uid; state.dirty = true; } function _addQuota() { const name = document.getElementById('newQuotaName')?.value?.trim(); const limit = parseInt(document.getElementById('newQuotaLimit')?.value || '0'); const action = document.getElementById('newQuotaAction')?.value; if (!name || !limit) { showToast('Enter quota name and limit', 'warning'); return; } apiPost(window.SURVAM_API_URL, { action:'add_quota', survey_id: state.surveyId, name, limit_count: limit, action, conditions: [] }) .then(r => { if (r.success) openQuotaEditor(); else showToast(r.error, 'error'); }); } function _previewQ(qi) { const page = state.pages[state.activePageIdx]; const q = page.questions[qi]; if (!q) return; const o = q.options || {}; const s = q.settings || {}; // Build preview HTML for this question const mediaUrl = q.media_url || s.media_url || ''; const mediaType = q.media_type || s.media_type || 'none'; const mediaPos = q.media_position || s.media_position || 'above'; let mediaHtml = ''; if (mediaUrl && mediaType !== 'none') { if (mediaType === 'image' || mediaType === 'image_upload') { mediaHtml = ``; } else if (mediaType === 'video') { let embed = escH(mediaUrl); const ytMatch = mediaUrl.match(/youtube\.com\/watch\?v=([^&]+)|youtu\.be\/([^?]+)/); const vimMatch = mediaUrl.match(/vimeo\.com\/(\d+)/); if (ytMatch) embed = `https://www.youtube.com/embed/${ytMatch[1]||ytMatch[2]}?rel=0`; else if (vimMatch) embed = `https://player.vimeo.com/video/${vimMatch[1]}`; mediaHtml = `
`; } } let answerHtml = ''; const type = q.type; if (['single_choice','multi_choice','dropdown'].includes(type)) { const choices = o.choices || []; if (type === 'dropdown') { answerHtml = ``; } else { const inputType = type === 'multi_choice' ? 'checkbox' : 'radio'; answerHtml = `
${choices.map(c=>` `).join('')}
`; } } else if (type === 'yes_no') { answerHtml = `
`; } else if (type === 'text_short') { answerHtml = ``; } else if (type === 'text_long') { answerHtml = ``; } else if (type === 'number') { answerHtml = ``; } else if (type === 'rating_scale' || type === 'nps') { const min = o.min ?? (type === 'nps' ? 0 : 1); const max = o.max ?? (type === 'nps' ? 10 : 10); const btns = []; for (let i = min; i <= max; i++) btns.push(``); answerHtml = `
${btns.join('')}
`; if (o.min_label || o.max_label) answerHtml += `
${escH(o.min_label||'')}${escH(o.max_label||'')}
`; } else if (type === 'star_rating') { const stars = o.stars || 5; answerHtml = `
${Array.from({length:stars},(_,i)=>``).join('')}
`; } else if (type === 'slider') { answerHtml = `
${o.default??50}
`; } else if (type === 'emoji_scale') { const emojis = o.emojis || [{icon:'😡',label:'Bad'},{icon:'😐',label:'Neutral'},{icon:'😊',label:'Good'}]; answerHtml = `
${emojis.map(e=>``).join('')}
`; } else if (type === 'date_time') { const mode = o.mode || 'date'; answerHtml = ``; } else if (type === 'statement') { answerHtml = `
${escH(q.description||q.title)}
`; } else if (type === 'section_break') { answerHtml = `
`; } else { answerHtml = `
[${questionTypeLabel(type)} — preview not available for this type]
`; } const titleHtml = `
${escH(q.title||'Untitled question')}${q.required?'*':''}
`; const descHtml = q.description ? `
${escH(q.description)}
` : ''; const aboveMedia = (mediaUrl && mediaPos === 'above') ? mediaHtml : ''; const belowMedia = (mediaUrl && mediaPos === 'below') ? mediaHtml : ''; const body = `${aboveMedia}${titleHtml}${descHtml}${belowMedia}${answerHtml}`; // Create/show the modal let overlay = document.getElementById('qPreviewOverlay'); if (!overlay) { overlay = document.createElement('div'); overlay.id = 'qPreviewOverlay'; overlay.style.cssText = 'position:fixed;inset:0;background:rgba(15,15,30,0.75);z-index:99999;display:flex;align-items:center;justify-content:center;padding:1.5rem;backdrop-filter:blur(4px);'; overlay.addEventListener('click', (e) => { if (e.target === overlay) overlay.style.display='none'; }); document.body.appendChild(overlay); } overlay.innerHTML = `
Question Preview ${questionTypeLabel(q.type)}
${body}
Preview only — no data saved
`; overlay.style.display = 'flex'; } async function _uploadMediaFile(qi, input) { const file = input.files[0]; if (!file) return; const formData = new FormData(); formData.append('file', file); formData.append('type', 'media'); // Show uploading state const btn = input.closest('[style*="dashed"]')?.querySelector('button'); const origText = btn ? btn.innerHTML : ''; if (btn) btn.innerHTML = ' Uploading…'; try { const res = await fetch(window.SURVAM_API_URL.replace('surveys.php', 'upload.php'), { method: 'POST', body: formData }); const data = await res.json(); if (data.success) { state.pages[state.activePageIdx].questions[qi].media_url = data.url; state.dirty = true; renderProps(); showToast('Image uploaded!', 'success'); } else { showToast(data.error || 'Upload failed', 'error'); if (btn) btn.innerHTML = origText; } } catch(e) { showToast('Upload failed. Check connection.', 'error'); if (btn) btn.innerHTML = origText; } } function _deleteQuota(id) { if (!confirm('Delete this quota?')) return; apiPost(window.SURVAM_API_URL, { action:'delete_quota', id }).then(r => { if (r.success) openQuotaEditor(); }); } function _openLogicEditor(qi) { openLogicEditor(qi); } // ── Drag & drop questions ───────────────────────────────── function initQuestionDrag() { const list = document.getElementById('questionList'); if (!list) return; let dragging = null; list.querySelectorAll('.question-block').forEach(block => { block.draggable = true; block.addEventListener('dragstart', () => { dragging = block; block.classList.add('dragging'); }); block.addEventListener('dragend', () => { dragging = null; block.classList.remove('dragging'); // Re-sync state order const newOrder = [...list.querySelectorAll('.question-block')].map(b => parseInt(b.dataset.qi)); const page = state.pages[state.activePageIdx]; const reordered = newOrder.map(i => page.questions[i]); page.questions = reordered; state.dirty = true; renderCanvas(); renderProps(); }); block.addEventListener('dragover', (e) => { e.preventDefault(); const after = getDragAfterEl(list, e.clientY); if (after) list.insertBefore(dragging, after); else list.appendChild(dragging); }); }); } function getDragAfterEl(list, y) { const els = [...list.querySelectorAll('.question-block:not(.dragging)')]; return els.reduce((closest, el) => { const box = el.getBoundingClientRect(); const offset = y - box.top - box.height / 2; if (offset < 0 && offset > (closest.offset || -Infinity)) return { offset, element: el }; return closest; }, {}).element; } // ── Events ──────────────────────────────────────────────── function bindEvents() { document.getElementById('saveBtn')?.addEventListener('click', () => save(true)); document.getElementById('previewBtn')?.addEventListener('click', () => { save(false).then(() => window.open(`/s/${state.surveyId}?preview=1`, '_blank')); }); document.getElementById('openQuotaBtn')?.addEventListener('click', openQuotaEditor); // Left panel: add question by clicking type document.querySelectorAll('.q-type-btn').forEach(btn => { btn.addEventListener('click', () => _addQ(btn.dataset.type)); }); } // ── Helpers ─────────────────────────────────────────────── function getPipeableQuestions(qi) { const page = state.pages[state.activePageIdx]; return page.questions.slice(0, qi).filter(q => !['statement','section_break'].includes(q.type)); } function escH(str) { return String(str || '').replace(/&/g,'&').replace(//g,'>').replace(/"/g,'"'); } return { init, save, _addQ, _selectQ, _deleteQ, _duplicateQ, _moveQ, _setField, _setSetting, _setOpt, _setChoice, _addChoice, _removeChoice, _setScreenoutUrl, _setOption, _addOptionItem, _removeOptionItem, _changeType, _bulkEditChoices, _addPage, _selectPage, _deletePage, _setPageTitle, _setPageDesc, _addLogicRule, _setLogicQ, _setLogicOp, _setLogicVal, _removeLogicRule, _saveLogic, _openLogicEditor, _togglePipe, _setPipeSource, _renderProps: renderProps, _addQuota, _deleteQuota, _previewQ, _uploadMediaFile, }; })(); // ── Question type registry ──────────────────────────────────── const ALL_QUESTION_TYPES = [ { value:'single_choice', label:'Single Choice' }, { value:'multi_choice', label:'Multiple Choice' }, { value:'dropdown', label:'Dropdown' }, { value:'text_short', label:'Short Text' }, { value:'text_long', label:'Long Text / Essay' }, { value:'rating_scale', label:'Rating Scale' }, { value:'star_rating', label:'Star Rating' }, { value:'likert_scale', label:'Likert Scale' }, { value:'matrix_single', label:'Matrix – Single' }, { value:'matrix_multi', label:'Matrix – Multiple' }, { value:'ranking', label:'Ranking' }, { value:'slider', label:'Slider' }, { value:'nps', label:'NPS Score' }, { value:'yes_no', label:'Yes / No' }, { value:'date_time', label:'Date / Time' }, { value:'number', label:'Number' }, { value:'emoji_scale', label:'Emoji Scale' }, { value:'image_choice', label:'Image Choice' }, { value:'file_upload', label:'File Upload' }, { value:'demographic', label:'Demographic' }, { value:'video_response', label:'Video Response' }, { value:'signature', label:'Signature' }, { value:'heatmap', label:'Heat Map' }, { value:'card_sort', label:'Card Sort' }, { value:'constant_sum', label:'Constant Sum' }, { value:'max_diff', label:'MaxDiff / Best-Worst' }, { value:'statement', label:'Statement / Display Text' }, { value:'section_break', label:'Section Break' }, ]; function questionTypeLabel(type) { return ALL_QUESTION_TYPES.find(t => t.value === type)?.label || type; }