improved condition json to have UI integration

This commit is contained in:
2026-05-28 12:26:45 +02:00
parent 9cf67e765f
commit 9a6fa22d84
5 changed files with 932 additions and 27 deletions

View File

@ -12,6 +12,10 @@ import {
renderGroupedTranslationsHTML,
escHtml,
} from '../translations-helpers.js';
import {
mountConditionEditor,
parseConditionForm,
} from '../condition-editor.js';
const LAYOUT_TYPES = [
{ value: 'radio_question', label: 'Radio (Single Choice)' },
@ -33,6 +37,9 @@ let questionnaire = null;
let questions = [];
let isNew = false;
let activeTab = 'questions';
let allQuestionnaires = [];
let questionsByQuestionnaire = {};
let conditionEditorApi = null;
// ── Entry point ──────────────────────────────────────────────────────────
@ -52,6 +59,13 @@ export async function editorPage(params) {
<div id="editorContent"><div class="spinner"></div></div>
`;
try {
const qnList = await apiGet('questionnaires.php');
allQuestionnaires = qnList.questionnaires || [];
} catch {
allQuestionnaires = [];
}
if (isNew) {
questionnaire = { questionnaireID: null, name: '', version: '1.0', state: 'draft',
orderIndex: 0, showPoints: 0, conditionJson: '{}' };
@ -59,11 +73,8 @@ export async function editorPage(params) {
renderEditor();
} else {
try {
const [qnData, quData] = await Promise.all([
apiGet('questionnaires.php'),
apiGet(`questions.php?questionnaireID=${encodeURIComponent(params.id)}`)
]);
questionnaire = (qnData.questionnaires || []).find(q => q.questionnaireID === params.id);
const quData = await apiGet(`questions.php?questionnaireID=${encodeURIComponent(params.id)}`);
questionnaire = allQuestionnaires.find(q => q.questionnaireID === params.id);
if (!questionnaire) {
showToast('Questionnaire not found', 'error');
navigate('#/');
@ -547,16 +558,7 @@ function renderEditor() {
<input type="text" id="metaCategoryKey" value="${esc(questionnaire.categoryKey || '')}" placeholder="e.g. health_interview" ${editable ? '' : 'disabled'}>
</div>
</div>
${editable ? `
<details class="condition-details" style="margin-bottom:16px">
<summary style="cursor:pointer;font-size:.85rem;font-weight:600;color:var(--text-secondary)">
Availability Condition (JSON)
</summary>
<textarea id="metaCondition" rows="6"
style="width:100%;margin-top:8px;font-family:monospace;font-size:.85rem;padding:8px 12px;border:1px solid var(--border);border-radius:6px"
>${formatJson(condJson)}</textarea>
</details>
` : ''}
<div id="conditionEditorMount" style="margin:16px 0"></div>
${editable ? `<button class="btn btn-primary" id="saveMetaBtn">${isNew ? 'Create Questionnaire' : 'Save Changes'}</button>` : ''}
</div>
@ -586,6 +588,7 @@ function renderEditor() {
if (editable) {
document.getElementById('saveMetaBtn').addEventListener('click', saveMeta);
}
initConditionEditor(condJson);
if (!isNew) {
initTabs();
@ -613,14 +616,60 @@ function initTabs() {
});
}
function formatJson(str) {
async function fetchAppStringGerman(messageKey) {
if (!messageKey) return '';
try {
return JSON.stringify(JSON.parse(str), null, 2);
const data = await apiGet(
`translations.php?type=app_string&id=${encodeURIComponent(messageKey)}`
);
const row = (data.translations || []).find(
tr => tr.languageCode === SOURCE_LANG
);
return row?.text?.trim() || '';
} catch {
return str;
return '';
}
}
async function loadQuestionsByQuestionnaire() {
const ids = allQuestionnaires.map(q => q.questionnaireID).filter(Boolean);
if (!ids.length) {
questionsByQuestionnaire = {};
return;
}
const pairs = await Promise.all(ids.map(async id => {
try {
const data = await apiGet(
`questions.php?questionnaireID=${encodeURIComponent(id)}`
);
return [id, data.questions || []];
} catch {
return [id, []];
}
}));
questionsByQuestionnaire = Object.fromEntries(pairs);
}
async function initConditionEditor(condJson) {
const mount = document.getElementById('conditionEditorMount');
if (!mount) return;
mount.innerHTML = '<div class="spinner"></div>';
await loadQuestionsByQuestionnaire();
const qid = questionnaire?.questionnaireID || 'new';
const parsed = parseConditionForm(condJson, qid);
const germanLabel = parsed.messageKey
? await fetchAppStringGerman(parsed.messageKey)
: '';
conditionEditorApi = mountConditionEditor(mount, {
questionnaireId: qid,
allQuestionnaires,
questionsByQuestionnaire,
conditionJson: condJson,
germanLabel,
editable: canEdit(),
});
}
// ── Save questionnaire meta ──────────────────────────────────────────────
async function saveMeta() {
@ -631,13 +680,13 @@ async function saveMeta() {
const showPoints = document.getElementById('metaShowPoints').checked ? 1 : 0;
const categoryKey = (document.getElementById('metaCategoryKey')?.value || '').trim();
let conditionJson = '{}';
const condEl = document.getElementById('metaCondition');
if (condEl) {
let conditionForm = null;
if (conditionEditorApi) {
try {
JSON.parse(condEl.value);
conditionJson = condEl.value.trim();
} catch {
showToast('Condition JSON is invalid', 'error');
conditionJson = conditionEditorApi.buildJson();
conditionForm = conditionEditorApi.readForm();
} catch (e) {
showToast(e.message || 'Invalid availability settings', 'error');
return;
}
}
@ -649,6 +698,7 @@ async function saveMeta() {
if (data.success) {
questionnaire = data.questionnaire;
isNew = false;
await saveConditionMessageLabel(conditionForm);
showToast('Questionnaire created', 'success');
navigate(`#/questionnaire/${questionnaire.questionnaireID}`);
}
@ -660,6 +710,7 @@ async function saveMeta() {
if (data.success) {
questionnaire = { ...questionnaire, name, version, state, orderIndex, showPoints, conditionJson, categoryKey };
document.getElementById('editorTitle').textContent = name;
await saveConditionMessageLabel(conditionForm);
showToast('Saved', 'success');
}
}
@ -668,6 +719,22 @@ async function saveMeta() {
}
}
async function saveConditionMessageLabel(form) {
if (!form || form.mode === 'always') return;
const messageKey = (form.messageKey || '').trim();
const germanLabel = (form.germanLabel || '').trim();
if (!messageKey || !germanLabel) return;
try {
await apiPost('questionnaires.php', {
action: 'updateConditionMessageLabel',
messageKey,
germanLabel,
});
} catch (e) {
showToast(`Saved questionnaire; message label failed: ${e.message}`, 'error');
}
}
// ── Add question form ────────────────────────────────────────────────────
function showAddQuestionForm() {