adapt vue app to new data model (wip)

This commit is contained in:
Mira Weller
2026-03-19 21:45:32 +01:00
parent c3d6fb1bd6
commit 63d9f7cea8
6 changed files with 171 additions and 69 deletions

View File

@@ -1,30 +1,32 @@
<script>
import Question from './Question.vue';
import {get_questions, get_items} from './api';
import Questionnaire from './Questionnaire.vue';
import {get_datafields, get_items, get_questionnaires} from './api';
import { i18n_any, QUESTION_TYPE } from './helper';
import { ref } from 'vue';
const questions_response = await get_questions();
const datafields_response = await get_datafields();
const questionnaires_response = await get_questionnaires();
const items_response = await get_items();
const questions = ref(questions_response.results);
const questionnaires = ref(questionnaires_response.results);
const datafields = ref(datafields_response.results);
export default {
components: {
Question
Questionnaire
},
methods: {
i18n_any,
addQuestion: function() {
questions.value.push({
items: [], question: {en:"Untitled question"},
type: QUESTION_TYPE.TEXT, help_text: {en:"Help text"},
})
console.log(questions.value)
addQuestionnaire: function() {
questionnaires.value.push({
items: [], internal_name: "Unnamed questionnaire",
type: 'PS',
});
}
},
data() {
return {
questions,
questionnaires,
datafields,
items: items_response.results,
selected_product: ref(""),
}
@@ -44,31 +46,25 @@ export default {
</style>
<template>
<p>
<button class="btn btn-default" @click="addQuestion()"><i class="fa fa-plus"></i> Neue Frage erstellen</button>
</p>
<div class="panel panel-default question-editor">
<div class="panel-heading">
Edit questions for product:
<select v-model="selected_product">
<option value="">(all)</option>
<option v-for="item in items" :value="item.id">
{{ i18n_any(item.name) }}
</option>
</select>
</div>
<div class="panel-body">
<div class="form-horizontal">
<Question
v-for="question in questions"
:question="question"
<p>
Questionnaires for product:
<select v-model="selected_product">
<option value="">(all)</option>
<option v-for="item in items" :value="item.id">
{{ i18n_any(item.name) }}
</option>
</select>
</p>
<div class="question-editor">
<Questionnaire
v-for="questionnaire in questionnaires"
:questionnaire="questionnaire"
:datafields="datafields"
:items="items"
:selected_product="selected_product" />
</div>
</div>
</div>
</div>
<p>
<button class="btn btn-default" @click="addQuestionnaire()"><i class="fa fa-plus"></i> Neuen Fragebogen erstellen</button>
</p>
</template>

View File

@@ -1,8 +1,9 @@
<script setup lang="ts">
import { ref, useId, defineProps } from 'vue';
defineProps(['value', 'id'])
const props = defineProps(['value', 'id']);
if (!props.value) props.value = {};
</script>
<template>

View File

@@ -1,47 +1,47 @@
<script setup>
import { i18n_any, QUESTION_TYPE, QUESTION_TYPE_LABEL } from './helper';
import { i18n_any, QUESTION_TYPE, QUESTION_TYPE_LABEL, SYSTEM_DATAFIELDS } from './helper';
import NativeDialog from './NativeDialog.vue';
import I18nTextField from './I18nTextField.vue';
import { useId, ref } from 'vue'
const id = useId();
const props = defineProps(['question', 'selected_product'])
const props = defineProps(['question', 'datafields', 'editable'])
const emit = defineEmits(['removeSelf']);
const df = typeof props.question.question === 'number' ?
props.datafields.find(el => el.id === props.question.question) :
typeof props.question.question === 'string' ?
SYSTEM_DATAFIELDS[props.question.question] :
{};
if (!props.question.label) props.question.label = {};
if (!props.question.help_text) props.question.help_text = {};
function toggleItem() {
const i = props.question.items.indexOf(props.selected_product);
if (i === -1) {
props.question.items.push(props.selected_product);
} else {
props.question.items.splice(i, 1);
}
}
const editor = ref();
</script>
<template>
<div class="form-group"
:class="{ 'hidden-question': selected_product && question.items.indexOf(selected_product) === -1 }">
<div class="question-edit-buttons"><div>
<div class="form-group">
<div class="question-edit-buttons" v-if="editable"><div>
<button class="btn btn-default"><i class="fa fa-arrows"></i></button>
<button class="btn btn-default" @click="editor.show()"><i class="fa fa-edit"></i></button>
<button class="btn btn-default" @click="toggleItem()" v-if="selected_product"><i :class="`fa fa-eye${(question.items.indexOf(selected_product) === -1) ? '-slash':''}`"></i></button>
</div></div>
<label class="col-md-3 control-label" :for="id" v-if="question.type != QUESTION_TYPE.BOOLEAN">
{{ i18n_any(question.question) }}
<label class="col-md-3 control-label" :for="id" v-if="df.type !== QUESTION_TYPE.BOOLEAN">
{{ i18n_any(question.label) }}
</label>
<div v-else class="col-md-3 control-label label-empty"></div>
<div class="col-md-9">
<input :id="id" type="text" v-if="question.type == QUESTION_TYPE.TEXT" class="form-control">
<div class="checkbox" v-if="question.type == QUESTION_TYPE.BOOLEAN">
<input :id="id" type="text" v-if="df.type === QUESTION_TYPE.STRING" class="form-control">
<textarea :id="id" v-if="df.type === QUESTION_TYPE.TEXT" class="form-control"></textarea>
<div class="checkbox" v-if="df.type === QUESTION_TYPE.BOOLEAN">
<label :for="id">
<input :id="id" type="checkbox"> {{ i18n_any(question.question) }}
<input :id="id" type="checkbox"> {{ i18n_any(question.label) }}
</label>
</div>
<input :id="id" type="number" v-if="question.type == QUESTION_TYPE.NUMBER" class="form-control">
<input :id="id" type="file" v-if="question.type == QUESTION_TYPE.FILE" class="form-control">
<input :id="id" type="number" v-if="df.type === QUESTION_TYPE.NUMBER" class="form-control">
<input :id="id" type="file" v-if="df.type === QUESTION_TYPE.FILE" class="form-control">
<select :id="id"
v-if="question.type == QUESTION_TYPE.CHOICE || question.type == QUESTION_TYPE.CHOICE_MULTIPLE"
:multiple="question.type == QUESTION_TYPE.CHOICE_MULTIPLE" class="form-control">
v-if="df.type === QUESTION_TYPE.CHOICE || df.type === QUESTION_TYPE.CHOICE_MULTIPLE"
:multiple="df.type === QUESTION_TYPE.CHOICE_MULTIPLE" class="form-control">
<option></option>
<option v-for="opt in question.options">{{ i18n_any(opt.answer) }}</option>
</select>
@@ -58,15 +58,15 @@ const editor = ref();
Question
</label>
<div class="col-md-9">
<I18nTextField :value="question.question"/>
<I18nTextField :value="question.label"/>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">
Question type
Data field type
</label>
<div class="col-md-9">
<select v-model="question.type" class="form-control">
<select v-model="df.type" class="form-control">
<option v-for="(label, type) in QUESTION_TYPE_LABEL" :value="QUESTION_TYPE[type]">{{ label }}</option>
</select>
</div>
@@ -80,7 +80,8 @@ const editor = ref();
<div class="help-block">Wenn diese Frage noch weitere Erklärung braucht, können Sie sie hier eintragen.</div>
</div>
</div>
<button @click="editor.close()">Close</button>
<button @click="editor.close()" class="btn btn-primary pull-right"><span class="fa fa-check"></span> Save and close</button>
<button @click="emit('removeSelf')" class="btn btn-default">Remove from questionnaire</button>
</NativeDialog>
</Teleport>
</template>

View File

@@ -0,0 +1,86 @@
<script setup lang="ts">
import {useId, ref, computed} from 'vue'
import Question from "./Question.vue";
import {i18n_any, QUESTION_TYPE, QUESTION_TYPE_LABEL} from "./helper";
import I18nTextField from "./I18nTextField.vue";
import NativeDialog from "./NativeDialog.vue";
const id = useId();
const props = defineProps(['questionnaire', 'datafields', 'selected_product', 'items'])
function toggleItem() {
const i = props.questionnaire.items.indexOf(props.selected_product);
if (i === -1) {
props.questionnaire.items.push(props.selected_product);
} else {
props.questionnaire.items.splice(i, 1);
}
}
const isHidden = computed(() => props.selected_product && props.questionnaire.items.indexOf(props.selected_product) === -1);
const isEditable = computed(() => props.selected_product && props.questionnaire.items.indexOf(props.selected_product) !== -1);
const editor = ref();
</script>
<template>
<div class="question-edit-buttons"><div>
<button class="btn btn-default"><i class="fa fa-arrows"></i></button>
<button class="btn btn-default" @click="editor.show()"><i class="fa fa-edit"></i></button>
<button class="btn btn-default" @click="toggleItem()" v-if="selected_product"><i :class="`fa fa-eye${isHidden ? '-slash':''}`"></i></button>
</div></div>
<details class="panel panel-default " :open="!!isEditable"
:class="{ 'hidden-question': isHidden }">
<summary class="panel-heading">
{{ props.questionnaire.internal_name }}
</summary>
<div class="panel-body" v-if="!isHidden">
<div class="form-horizontal">
<Question
v-for="(child, index) in props.questionnaire.children" :key="index"
:datafields="props.datafields"
:question="child"
:editable="true"
@remove-self="questionnaire.children.splice(index, 1)" />
</div>
<p v-if="true">
<button class="btn btn-default" @click="addExistingDatafield()"><i class="fa fa-plus"></i> Bestehendes Datenfeld hinzufügen</button>
<button class="btn btn-default" @click="newDatafield()"><i class="fa fa-plus"></i> Neues Datenfeld</button>
<button class="btn btn-default" @click="addSubtitle()"><i class="fa fa-plus"></i> Zwischenüberschrift</button>
<button class="btn btn-default" @click="addTextBlock()"><i class="fa fa-plus"></i> Text</button>
</p>
</div>
</details>
<Teleport to="body">
<NativeDialog ref="editor" class="modal-card"
title="Edit questionnaire">
<div class="form-group">
<label class="col-md-3 control-label">
Internal name
</label>
<div class="col-md-9">
<input type="text" class="form-control" v-model="questionnaire.internal_name"/>
</div>
</div>
<div class="form-group">
<label class="col-md-3 control-label">
Visible on products
</label>
<div class="col-md-9">
<div class="checkbox" v-for="item in items">
<label :for="id + '_' + item.id">
<input :id="id + '_' + item.id" type="checkbox" :checked="questionnaire.items.indexOf(item.id) !== -1"> {{ item.internal_name || i18n_any(item.name) }}
</label>
</div>
</div>
</div>
<button @click="editor.close()" class="btn btn-primary pull-right"><span class="fa fa-check"></span> Save and close</button>
<button class="btn btn-default">Delete</button>
</NativeDialog>
</Teleport>
</template>

View File

@@ -1,10 +1,14 @@
const organizer_slug = document.body.getAttribute('data-organizer'),
event_slug = document.body.getAttribute('data-event');
export async function get_questions() {
return await (await fetch(`/api/v1/organizers/${organizer_slug}/events/${event_slug}/questions`)).json();
export async function get_datafields() {
return await (await fetch(`/api/v1/organizers/${organizer_slug}/events/${event_slug}/datafields/`)).json();
}
export async function get_questionnaires() {
return await (await fetch(`/api/v1/organizers/${organizer_slug}/events/${event_slug}/questionnaires/`)).json();
}
export async function get_items() {
return await (await fetch(`/api/v1/organizers/${organizer_slug}/events/${event_slug}/items`)).json();
return await (await fetch(`/api/v1/organizers/${organizer_slug}/events/${event_slug}/items/`)).json();
}

View File

@@ -1,8 +1,12 @@
export function i18n_any(data) {
if (!data) return null;
return Object.values(data)[0];
}
function freezeRec(o) {
return Object.freeze(Object.fromEntries(Object.entries(o).map(([k, v]) => [k, v && Object.getPrototypeOf(v) === Object.prototype ? freezeRec(v) : v])))
}
export const QUESTION_TYPE = {
NUMBER: "N",
@@ -35,3 +39,13 @@ export const QUESTION_TYPE_LABEL = {
COUNTRYCODE: _("Country code (ISO 3166-1 alpha-2)"),
PHONENUMBER: _("Phone number"),
};
export const SYSTEM_DATAFIELDS = freezeRec({
'attendee_name_parts': { label: _('Attendee name'), type: QUESTION_TYPE.STRING },
'attendee_email': { label: _('Attendee email'), type: QUESTION_TYPE.STRING },
'company': { label: _('Company'), type: QUESTION_TYPE.STRING },
'street': { label: _('Street'), type: QUESTION_TYPE.STRING },
'zipcode': { label: _('ZIP code'), type: QUESTION_TYPE.STRING },
'city': { label: _('City'), type: QUESTION_TYPE.STRING },
'country': { label: _('Country'), type: QUESTION_TYPE.COUNTRYCODE },
});