204 lines
8.4 KiB
JavaScript
204 lines
8.4 KiB
JavaScript
export const LAYOUT_TYPES = [
|
|
{ value: 'radio_question', label: 'Radio (Single Choice)' },
|
|
{ value: 'multi_check_box_question', label: 'Checkbox (Multiple Choice)' },
|
|
{ value: 'glass_scale_question', label: 'Glass Scale' },
|
|
{ value: 'value_spinner', label: 'Value Spinner' },
|
|
{ value: 'date_spinner', label: 'Date Spinner' },
|
|
{ value: 'string_spinner', label: 'String Spinner' },
|
|
{ value: 'client_coach_code_question', label: 'Client / Coach Code' },
|
|
{ value: 'client_not_signed', label: 'Client Not Signed' },
|
|
{ value: 'last_page', label: 'Last Page' },
|
|
];
|
|
|
|
export const OPTION_TYPES = new Set(['radio_question', 'multi_check_box_question']);
|
|
|
|
export function layoutSelectHTML(selected = 'radio_question', id = '') {
|
|
return `<select ${id ? `id="${id}"` : ''} class="type-select">
|
|
${LAYOUT_TYPES.map(t =>
|
|
`<option value="${t.value}" ${selected === t.value ? 'selected' : ''}>${t.label}</option>`
|
|
).join('')}
|
|
</select>`;
|
|
}
|
|
|
|
export function layoutLabel(value) {
|
|
const t = LAYOUT_TYPES.find(t => t.value === value);
|
|
return t ? t.label : value || 'unknown';
|
|
}
|
|
|
|
export function parseConfig(q) {
|
|
try { return JSON.parse(q.configJson || '{}'); } catch { return {}; }
|
|
}
|
|
|
|
export function questionLocalIds(questions) {
|
|
return questions.map(q => {
|
|
const parts = q.questionID.split('__');
|
|
return { questionID: q.questionID, localId: parts[parts.length - 1], defaultText: q.defaultText };
|
|
});
|
|
}
|
|
|
|
export function esc(s) {
|
|
const d = document.createElement('div');
|
|
d.textContent = s ?? '';
|
|
return d.innerHTML;
|
|
}
|
|
|
|
export function configFormHTML(layout, config, prefix) {
|
|
let html = '';
|
|
switch (layout) {
|
|
case 'radio_question':
|
|
html = `
|
|
<div class="form-group">
|
|
<label>Extra text key (optional)</label>
|
|
<input type="text" id="${prefix}_textKey" value="${esc(config.textKey || '')}" placeholder="e.g. resilience_reflection_prompt">
|
|
</div>`;
|
|
break;
|
|
case 'multi_check_box_question':
|
|
html = `
|
|
<div class="form-group">
|
|
<label>Min selections</label>
|
|
<input type="number" id="${prefix}_minSelection" value="${config.minSelection || 0}" min="0">
|
|
</div>`;
|
|
break;
|
|
case 'glass_scale_question':
|
|
html = `
|
|
<div class="form-group">
|
|
<label>Symptom keys (one per line)</label>
|
|
<textarea id="${prefix}_symptoms" rows="5" style="font-family:monospace;font-size:.85rem">${(config.symptoms || []).join('\n')}</textarea>
|
|
</div>`;
|
|
break;
|
|
case 'value_spinner':
|
|
html = `
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Min</label>
|
|
<input type="number" id="${prefix}_rangeMin" value="${config.range?.min ?? 0}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Max</label>
|
|
<input type="number" id="${prefix}_rangeMax" value="${config.range?.max ?? 100}">
|
|
</div>
|
|
</div>`;
|
|
break;
|
|
case 'date_spinner': {
|
|
const notBefore = config.constraints?.notBefore || '';
|
|
const notAfter = config.constraints?.notAfter || '';
|
|
html = `
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Not before (question key)</label>
|
|
<input type="text" id="${prefix}_notBefore" value="${esc(notBefore)}" placeholder="e.g. departure_country">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Not after (question key)</label>
|
|
<input type="text" id="${prefix}_notAfter" value="${esc(notAfter)}" placeholder="e.g. since_in_germany">
|
|
</div>
|
|
</div>`;
|
|
break;
|
|
}
|
|
case 'string_spinner':
|
|
html = `
|
|
<div class="form-group">
|
|
<label>Static options (one per line)</label>
|
|
<textarea id="${prefix}_stringOptions" rows="4" style="font-family:monospace;font-size:.85rem">${(config.options || []).join('\n')}</textarea>
|
|
</div>`;
|
|
break;
|
|
case 'client_coach_code_question':
|
|
html = `
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Hint 1 key</label>
|
|
<input type="text" id="${prefix}_hint1" value="${esc(config.hint1 || '')}" placeholder="client_code">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Hint 2 key</label>
|
|
<input type="text" id="${prefix}_hint2" value="${esc(config.hint2 || '')}" placeholder="coach_code">
|
|
</div>
|
|
</div>`;
|
|
break;
|
|
case 'client_not_signed':
|
|
html = `
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Text key 1</label>
|
|
<input type="text" id="${prefix}_textKey1" value="${esc(config.textKey1 || '')}">
|
|
</div>
|
|
<div class="form-group">
|
|
<label>Text key 2</label>
|
|
<input type="text" id="${prefix}_textKey2" value="${esc(config.textKey2 || '')}">
|
|
</div>
|
|
</div>
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label>Hint key</label>
|
|
<input type="text" id="${prefix}_hint" value="${esc(config.hint || '')}">
|
|
</div>
|
|
</div>`;
|
|
break;
|
|
case 'last_page':
|
|
html = `
|
|
<div class="form-group">
|
|
<label>Text key</label>
|
|
<input type="text" id="${prefix}_textKey" value="${esc(config.textKey || '')}" placeholder="finish_data_entry">
|
|
</div>`;
|
|
break;
|
|
}
|
|
return html ? `<div class="config-section">${html}</div>` : '';
|
|
}
|
|
|
|
export function readConfigFromForm(layout, prefix) {
|
|
const config = {};
|
|
const val = id => document.getElementById(`${prefix}_${id}`)?.value?.trim() || '';
|
|
|
|
switch (layout) {
|
|
case 'radio_question':
|
|
if (val('textKey')) config.textKey = val('textKey');
|
|
break;
|
|
case 'multi_check_box_question': {
|
|
const ms = parseInt(val('minSelection') || '0', 10);
|
|
if (ms > 0) config.minSelection = ms;
|
|
break;
|
|
}
|
|
case 'glass_scale_question': {
|
|
const raw = val('symptoms');
|
|
const syms = raw.split('\n').map(s => s.trim()).filter(Boolean);
|
|
if (syms.length) config.symptoms = syms;
|
|
break;
|
|
}
|
|
case 'value_spinner':
|
|
config.range = {
|
|
min: parseInt(val('rangeMin') || '0', 10),
|
|
max: parseInt(val('rangeMax') || '100', 10),
|
|
};
|
|
break;
|
|
case 'date_spinner': {
|
|
const nb = val('notBefore');
|
|
const na = val('notAfter');
|
|
if (nb || na) {
|
|
config.constraints = {};
|
|
if (nb) config.constraints.notBefore = nb;
|
|
if (na) config.constraints.notAfter = na;
|
|
}
|
|
break;
|
|
}
|
|
case 'string_spinner': {
|
|
const raw = val('stringOptions');
|
|
const opts = raw.split('\n').map(s => s.trim()).filter(Boolean);
|
|
if (opts.length) config.options = opts;
|
|
break;
|
|
}
|
|
case 'client_coach_code_question':
|
|
if (val('hint1')) config.hint1 = val('hint1');
|
|
if (val('hint2')) config.hint2 = val('hint2');
|
|
break;
|
|
case 'client_not_signed':
|
|
if (val('textKey1')) config.textKey1 = val('textKey1');
|
|
if (val('textKey2')) config.textKey2 = val('textKey2');
|
|
if (val('hint')) config.hint = val('hint');
|
|
break;
|
|
case 'last_page':
|
|
if (val('textKey')) config.textKey = val('textKey');
|
|
break;
|
|
}
|
|
return JSON.stringify(config);
|
|
}
|