Move App -> Editor

This commit is contained in:
Mira Weller
2026-09-16 15:08:17 +02:00
parent d8f444d51d
commit e6f1ee1126
2 changed files with 190 additions and 142 deletions
@@ -1,146 +1,24 @@
<script lang="ts">
import QuestionnaireElement from './QuestionnaireElement.vue';
import * as api from './api';
import { Questionnaire } from './model';
import { i18n_any, QUESTION_TYPE, sort, localeComp, numericComp, groupBy, _ } from './helper';
import {Ref, ref} from 'vue';
import { SlickList, SlickItem } from 'vue-slicksort';
const items_list = await api.getItems();
const categories_list = await api.getCategories();
const categories = Object.fromEntries(categories_list.map(cat => [cat.id, cat]));
categories['null'] = { position: -1, internal_name: _('Uncategorized') };
sort(items_list, numericComp(item => categories[item.category]?.position), numericComp(item => item.position));
console.log("items_list", items_list);
const grouped_items = [...groupBy(items_list, item => categories[item.category])];
console.log("grouped_items", grouped_items);
const all_questionnaires: (Omit<Questionnaire, 'id'> & { _new_id?: number, id?: number })[] = await api.getQuestionnaires();
const order_questionnaires = ref(all_questionnaires.filter(q => q.type.startsWith('O')));
const position_questionnaires = ref(all_questionnaires.filter(q => q.type.startsWith('P')));
const datafields = ref(await api.getDatafields());
function saveQuestionnaire(questionnaire) {
if (questionnaire.id) {
api.updateQuestionnaire(questionnaire.id, questionnaire);
} else {
api.createQuestionnaire(questionnaire);
}
}
export default {
components: {
QuestionnaireElement, SlickList, SlickItem,
},
methods: {
i18n_any,
addPositionQuestionnaire() {
position_questionnaires.value.push({
all_sales_channels: false, children: [], limit_sales_channels: [], position: 0,
items: [], internal_name: "Unnamed questionnaire", type: "PS",
_new_id: Date.now(),
});
},
addOrderQuestionnaire() {
order_questionnaires.value.push({
all_sales_channels: false, children: [], limit_sales_channels: [], position: 0,
items: [], internal_name: "Unnamed questionnaire", type: "OS",
_new_id: Date.now(),
});
},
saveData() {
for (const questionnaire of order_questionnaires.value) {
saveQuestionnaire(questionnaire);
}
for (const questionnaire of position_questionnaires.value) {
saveQuestionnaire(questionnaire);
}
},
},
data() {
return {
order_questionnaires,
position_questionnaires,
datafields,
items: items_list,
selected_product: ref(""),
grouped_items,
categories,
}
}
}
<script setup lang="ts">
import Editor from './Editor.vue';
</script>
<style>
.hidden-questionnaire { opacity: 0.3; }
.questionnaires-list { margin-right: 180px; }
.question-edit-buttons { float:right; }
.question-edit-buttons div { position: absolute; margin-left: 10px; min-width: 100px; }
.question-edit-buttons button { }
.form-group { margin-bottom: 30px }
.questionnaires-editor.product-selected .panel .panel-heading {}
.questionnaires-editor.product-selected .panel { margin-bottom: 0; border: 0 none; border-bottom: 1px solid #ddd; box-shadow: none; border-radius: 0; }
.questionnaires-editor.product-selected .panel .panel-heading { font-style: italic; background: white; border: 0 none; }
.filter-row { background: #f8e6ff; border: 1px solid #e3cbed; padding: 10px; }
.debuginfo { font-size: 70%; background: rgba(200, 200, 200, 0.5); }
.dependency-info { position: absolute; }
.dependency-info > span { }
.category-header { margin: 8px 0 -5px 0; font-weight: bold; color: #737373; }
</style>
<template>
<div class="questionnaires-editor">
<p class="filter-row">
Order questionnaires
</p>
<div class="questionnaires-list">
<SlickList axis="y" v-model:list="order_questionnaires" useDragHandle appendTo="#orderQuestionnaireListParent" id="orderQuestionnaireListParent">
<SlickItem v-for="(questionnaire, index) in order_questionnaires" :key="questionnaire.id || questionnaire._new_id" :index="index">
<QuestionnaireElement
:questionnaire="questionnaire"
:datafields="datafields"
:grouped_items="null"
:selected_product="null" />
</SlickItem>
</SlickList>
</div>
<p>
<button class="btn btn-default" @click="addOrderQuestionnaire()"><i class="fa fa-plus"></i> Neuen Fragebogen erstellen</button>
<button class="btn btn-default" @click="saveData()"><i class="fa fa-save"></i> Speichern</button>
</p>
</div>
<div :class="`questionnaires-editor ${selected_product ? 'product-selected':''}`">
<p class="filter-row">
Questionnaires for product:
<select v-model="selected_product">
<option value="">(all)</option>
<optgroup v-for="[category, items] in grouped_items" :label="category.internal_name || i18n_any(category.name)">
<option v-for="item in items" :value="item.id">
{{ item.internal_name || i18n_any(item.name) }}
</option>
</optgroup>
</select>
</p>
<div class="questionnaires-list">
<SlickList axis="y" v-model:list="position_questionnaires" useDragHandle appendTo="#questionnaireListParent" id="questionnaireListParent">
<SlickItem v-for="(questionnaire, index) in position_questionnaires" :key="questionnaire.id || questionnaire._new_id" :index="index">
<QuestionnaireElement
:questionnaire="questionnaire"
:datafields="datafields"
:grouped_items="grouped_items"
:selected_product="selected_product" />
</SlickItem>
</SlickList>
</div>
<p>
<button class="btn btn-default" @click="addPositionQuestionnaire()"><i class="fa fa-plus"></i> Neuen Fragebogen erstellen</button>
<button class="btn btn-default" @click="saveData()"><i class="fa fa-save"></i> Speichern</button>
</p>
</div>
<Suspense>
<Editor/>
</Suspense>
</template>
<style>
.progressBar { position: fixed; top: 0; left: 0; right: 0; z-index:1000000; pointer-events: none; }
.progressBar.local { position: absolute; }
.progressBar .progressBar_progress { height: 5px; background: #0091EA; border-bottom: #026099; }
.progressBar .progressBar_progress.indeterminate {
width: 30%; animation: slide 5s forwards;
}
.progressBarText { width: 100%; color: white; text-shadow: 1px 1px 1px black, -1px -1px 1px black;
font-weight: bold; padding-left: 10px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@keyframes slide {
0% { width: 20%; }
30% { width: 50%; }
100% { width: 60%; }
}
</style>
@@ -0,0 +1,170 @@
<script setup lang="ts">
import QuestionnaireElement from './QuestionnaireElement.vue';
import * as api from './api';
import { Questionnaire } from './model';
import { i18n_any, sort, numericComp, groupBy } from './helper';
import { gettext } from './gettextstub';
import { ref } from 'vue';
import { SlickList, SlickItem } from 'vue-slicksort';
import { ProgressBar } from "./ProgressBar";
const items_list = await api.getItems();
const categories_list = await api.getCategories();
const categories = Object.fromEntries(categories_list.map(cat => [cat.id, cat]));
categories['null'] = { position: -1, internal_name: gettext('Uncategorized'), name: null, id: null };
sort(items_list, numericComp(item => categories[item.category]?.position), numericComp(item => item.position));
console.log('items_list', items_list)
const grouped_items = [...groupBy(items_list, item => categories[item.category])]
console.log('grouped_items', grouped_items)
const all_questionnaires: (Omit<Questionnaire, 'id'> & { _new_id?: number, id?: number })[] = await api.getQuestionnaires();
const order_questionnaires = ref(all_questionnaires.filter(q => q.type.startsWith('O')));
const position_questionnaires = ref(all_questionnaires.filter(q => q.type.startsWith('P')));
const order_datafields = ref(await api.getDatafields('O'));
const position_datafields = ref(await api.getDatafields('P'));
function saveQuestionnaire(questionnaire) {
if (questionnaire.id) {
return api.updateQuestionnaire(questionnaire.id, questionnaire);
} else {
return api.createQuestionnaire(questionnaire).then(d => {
questionnaire.id = d.id;
return d;
});
}
}
function addPositionQuestionnaire () {
position_questionnaires.value.push({
all_sales_channels: false, children: [], limit_sales_channels: [], position: 0,
items: [], internal_name: "Unnamed questionnaire", type: "PS",
_new_id: Date.now(),
});
}
function addOrderQuestionnaire () {
order_questionnaires.value.push({
all_sales_channels: false, children: [], limit_sales_channels: [], position: 0,
items: [], internal_name: "Unnamed questionnaire", type: "OS",
_new_id: Date.now(),
});
}
async function saveData () {
using pb = ProgressBar.show('saving questionnaires')
let promises = [];
for (const questionnaire of order_questionnaires.value) {
promises.push(saveQuestionnaire(questionnaire))
}
for (const questionnaire of position_questionnaires.value) {
promises.push(saveQuestionnaire(questionnaire))
}
await Promise.all(promises)
}
/*
export default {
components: {
QuestionnaireElement, SlickList, SlickItem,
},
methods: {
i18n_any,
},
data() {
return {
order_questionnaires,
position_questionnaires,
order_datafields,
position_datafields,
items: items_list,
grouped_items,
categories,
}
}
}*/
const selected_product = ref("")
const preview_mode = ref(false)
</script>
<style>
.hidden-questionnaire { opacity: 0.3; }
.questionnaires-list { margin-right: 180px; }
.question-edit-buttons { float:right; }
.question-edit-buttons div { position: absolute; margin-left: 10px; min-width: 100px; }
.question-edit-buttons button { }
.form-group { margin-bottom: 30px }
.questionnaires-editor.product-selected .panel .panel-heading {}
.questionnaires-editor.product-selected .panel { margin-bottom: 0; border: 0 none; border-bottom: 1px solid #ddd; box-shadow: none; border-radius: 0; }
.questionnaires-editor.product-selected .panel .panel-heading { font-style: italic; background: white; border: 0 none; }
.filter-row { background: #f8e6ff; border: 1px solid #e3cbed; padding: 10px; }
.debuginfo { font-size: 70%; background: rgba(200, 200, 200, 0.5); }
.dependency-info { position: absolute; }
.dependency-info > span { }
.category-header { margin: 8px 0 -5px 0; font-weight: bold; color: #737373; }
</style>
<template>
<div class="questionnaires-editor">
<p class="filter-row">
Order questionnaires
</p>
<div class="questionnaires-list">
<SlickList axis="y" v-model:list="order_questionnaires" useDragHandle appendTo="#orderQuestionnaireListParent" id="orderQuestionnaireListParent">
<SlickItem v-for="(questionnaire, index) in order_questionnaires" :key="questionnaire.id || questionnaire._new_id" :index="index">
<QuestionnaireElement
:questionnaire="questionnaire"
:datafields="order_datafields"
:grouped_items="null"
:selected_product="null"
:preview_mode="false" />
</SlickItem>
</SlickList>
</div>
<p>
<button class="btn btn-default" @click="addOrderQuestionnaire()"><i class="fa fa-plus"></i> {{ gettext('New questionnaire') }}</button>
<button class="btn btn-default" @click="saveData()"><i class="fa fa-save"></i> {{ gettext('Save') }}</button>
</p>
</div>
<div :class="`questionnaires-editor ${selected_product ? 'product-selected':''}`">
<p class="filter-row">
Questionnaires for product:
<select v-model="selected_product">
<option value="">(all)</option>
<optgroup v-for="[category, items] in grouped_items" :label="category.internal_name || i18n_any(category.name)">
<option v-for="item in items" :value="item.id">
{{ item.internal_name || i18n_any(item.name) }}
</option>
</optgroup>
</select>
&nbsp;
<label><input type="checkbox" v-model="preview_mode" :disabled="!selected_product"> {{ gettext('Preview') }}</label>
</p>
<div class="questionnaires-list" v-if="preview_mode && selected_product">
<QuestionnaireElement v-for="(questionnaire, index) in position_questionnaires.filter(q => q.items.indexOf(selected_product as any) !== -1)"
:questionnaire="questionnaire"
:datafields="position_datafields"
:grouped_items="grouped_items"
:selected_product="selected_product"
:preview_mode="true"/>
</div>
<div class="questionnaires-list" v-else>
<SlickList axis="y" v-model:list="position_questionnaires" useDragHandle appendTo="#questionnaireListParent" id="questionnaireListParent">
<SlickItem v-for="(questionnaire, index) in position_questionnaires" :key="questionnaire.id || questionnaire._new_id" :index="index">
<QuestionnaireElement
:questionnaire="questionnaire"
:datafields="position_datafields"
:grouped_items="grouped_items"
:selected_product="selected_product"
:preview_mode="false"/>
</SlickItem>
</SlickList>
</div>
<p>
<button class="btn btn-default" @click="addPositionQuestionnaire()"><i class="fa fa-plus"></i> {{ gettext('New questionnaire') }}</button>
<button class="btn btn-default" @click="saveData()"><i class="fa fa-save"></i> {{ gettext('Save') }}</button>
</p>
</div>
</template>