Bugs in settings fixed, profile added

This commit is contained in:
Torsten Schulz
2024-09-21 00:25:42 +02:00
parent c5a72d57d8
commit e494fe41db
65 changed files with 3121 additions and 7478 deletions

View File

@@ -3,10 +3,10 @@
<div class="logo"><img src="/images/icons/logo_color.png"></div>
<div class="window-bar">
<button v-for="dialog in openDialogs" :key="dialog.dialog.name" class="dialog-button"
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.title">
@click="toggleDialogMinimize(dialog.dialog.name)" :title="dialog.dialog.localTitle">
<img v-if="dialog.dialog.icon" :src="'/images/icons/' + dialog.dialog.icon" />
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.title) : dialog.dialog.title
}}</span>
<span class="button-text">{{ dialog.dialog.isTitleTranslated ? $t(dialog.dialog.localTitle) :
dialog.dialog.localTitle }}</span>
</button>
</div>
<div class="static-block">

View File

@@ -60,6 +60,7 @@ nav>ul {
flex-direction: row;
margin: 0;
cursor: pointer;
z-index: 999;
}
ul {

View File

@@ -7,7 +7,7 @@
<span v-if="icon" class="dialog-icon">
<img :src="'/images/icons/' + icon" alt="Icon" />
</span>
<span class="dialog-title">{{ isTitleTranslated ? $t(title) : title }}</span>
<span class="dialog-title">{{ localIsTitleTranslated ? $t(localTitle) : localTitle }}</span>
<span v-if="!modal" class="dialog-minimize" @click="minimize">_</span>
<span v-if="showClose" class="dialog-close" @click="close"></span>
</div>
@@ -73,12 +73,13 @@ export default {
isDragging: false,
dragOffsetX: 0,
dragOffsetY: 0,
localTitle: this.title,
localIsTitleTranslated: this.isTitleTranslated,
};
},
computed: {
dialogWidth() {
const val = this.width || '70%';
console.log(val);
return val;
},
dialogHeight() {
@@ -90,6 +91,9 @@ export default {
if (!newValue) {
this.minimized = false;
}
},
title(newValue) {
this.updateTitle(newValue);
}
},
methods: {
@@ -107,9 +111,13 @@ export default {
this.$store.dispatch('dialogs/removeOpenDialog', this.name);
},
buttonClick(action) {
this.$emit(action);
if (action === 'close') {
this.close();
if (typeof action === 'function') {
action(); // Wenn action eine Funktion ist, rufe sie direkt auf
} else {
this.$emit(action);
if (action === 'close') {
this.close();
}
}
},
handleOverlayClick() {
@@ -131,7 +139,6 @@ export default {
this.dragOffsetY = event.clientY - dialog.offsetTop;
document.addEventListener('mousemove', this.onDrag);
document.addEventListener('mouseup', this.stopDragging);
console.log('dragging started');
},
onDrag(event) {
if (!this.isDragging) return;
@@ -143,13 +150,15 @@ export default {
document.removeEventListener('mousemove', this.onDrag);
document.removeEventListener('mouseup', this.stopDragging);
},
},
mounted() {
this.$store.subscribe((mutation) => {
if (mutation.type === 'dialogs/toggleDialogMinimize' && mutation.payload === this.name) {
this.minimized = !this.minimized;
}
});
updateTitle(newTitle, newIsTitleTranslated) {
this.localTitle = newTitle;
this.localIsTitleTranslated = newIsTitleTranslated;
this.$store.dispatch('dialogs/updateDialogTitle', {
name: this.name,
newTitle: newTitle,
isTitleTranslated: this.localIsTitleTranslated
});
},
}
};
</script>

View File

@@ -0,0 +1,76 @@
<template>
<div ref="quillEditor" class="quill-editor"></div>
</template>
<script>
import Quill from 'quill/core';
import Toolbar from 'quill/modules/toolbar';
import Snow from 'quill/themes/snow';
import Bold from 'quill/formats/bold';
import Italic from 'quill/formats/italic';
import Underline from 'quill/formats/underline';
import List from 'quill/formats/list';
Quill.register({
'modules/toolbar': Toolbar,
'themes/snow': Snow,
'formats/bold': Bold,
'formats/italic': Italic,
'formats/underline': Underline,
'formats/list': List
});
export default {
name: 'QuillEditor',
props: {
content: {
type: String,
default: ''
},
placeholder: {
type: String,
default: 'Compose an epic...'
}
},
data() {
return {
editor: null,
};
},
mounted() {
this.editor = new Quill(this.$refs.quillEditor, {
theme: 'snow',
placeholder: this.placeholder,
modules: {
toolbar: [
[{ 'header': [1, 2, false] }],
['bold', 'italic', 'underline'],
['link', 'image'],
[{ 'list': 'ordered' }, { 'list': 'bullet' }],
[{ 'align': [] }],
[{ 'color': [] }, { 'background': [] }],
['clean']
]
}
});
this.editor.on('text-change', () => {
this.$emit('update:content', this.editor.root.innerHTML);
});
this.editor.root.innerHTML = this.content;
},
beforeUnmount() {
if (this.editor) {
this.editor.off('text-change');
this.editor = null;
}
}
};
</script>
<style scoped>
.quill-editor {
min-height: 200px;
}
</style>

View File

@@ -1,30 +1,58 @@
<template>
<div class="settings-widget">
<template v-for="setting in settings">
<InputStringWidget v-if="setting.datatype == 'string'" :labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value :list="languagesList()"
@input="handleInput(setting.id, $event)" />
<DateInputWidget v-else-if="setting.datatype == 'date'" :labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
@input="handleInput(setting.id, $event)" />
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:list="getSettingOptions(setting.name, setting.options)" @input="handleInput(setting.id, $event)" />
<InputNumberWidget v-else-if="setting.datatype == 'int'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToInt(setting.value)" min="0"
max="200" @input="handleInput(setting.id, $event)" />
<FloatInputWidget v-else-if="setting.datatype == 'float'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToFloat(setting.value)"
@input="handleInput(setting.id, $event)" />
<CheckboxWidget v-else-if="setting.datatype == 'bool'" :labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToBool(setting.value)"
@input="handleInput(setting.id, $event)" />
<div v-else>{{ setting }}
</div>
</template>
<table>
<tr v-for="setting in settings" :key="setting.id">
<td>
<InputStringWidget v-if="setting.datatype == 'string'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:list="languagesList()" @input="handleInput(setting.id, $event)" />
<DateInputWidget v-else-if="setting.datatype == 'date'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
@input="handleInput(setting.id, $event)" />
<SelectDropdownWidget v-else-if="setting.datatype == 'singleselect'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value=setting.value
:list="getSettingOptions(setting.name, setting.options)"
@input="handleInput(setting.id, $event)" />
<InputNumberWidget v-else-if="setting.datatype == 'int'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToInt(setting.value)"
min="0" max="200" @input="handleInput(setting.id, $event)" />
<FloatInputWidget v-else-if="setting.datatype == 'float'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToFloat(setting.value)"
@input="handleInput(setting.id, $event)" />
<CheckboxWidget v-else-if="setting.datatype == 'bool'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="convertToBool(setting.value)"
@input="handleInput(setting.id, $event)" />
<MultiselectWidget v-else-if="setting.datatype == 'multiselect'"
:labelTr="`settings.personal.label.${setting.name}`"
:tooltipTr="`settings.personal.tooltip.${setting.name}`" :value="setting.value"
:list="getSettingOptions(setting.name, setting.options)"
@input="handleInput(setting.id, $event)" />
<div v-else>{{ setting }}</div>
<span v-if="setting.unit">&nbsp;{{ setting.unit }}</span>
</td>
<td>
<select v-model="setting.visibility.id"
@change="handleVisibilityChange(setting.id, setting.visibility.id)">
<option v-for="visibility in possibleVisibilities" :key="visibility.id" :value="visibility.id">
{{ $t(`settings.visibility.${visibility.description}`) }}
</option>
</select>
</td>
</tr>
</table>
</div>
</template>
@@ -33,10 +61,11 @@ import apiClient from '@/utils/axios.js';
import { mapGetters } from 'vuex';
import InputStringWidget from '@/components/form/InputStringWidget.vue';
import DateInputWidget from '@/components/form/DateInputWidget.vue';
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget';
import InputNumberWidget from '@/components/form/InputNumberWidget';
import FloatInputWidget from '@/components/form/FloatInputWidget';
import CheckboxWidget from '@/components/form/CheckboxWidget';
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget.vue';
import InputNumberWidget from '@/components/form/InputNumberWidget.vue';
import FloatInputWidget from '@/components/form/FloatInputWidget.vue';
import CheckboxWidget from '@/components/form/CheckboxWidget.vue';
import MultiselectWidget from '@/components/form/MultiselectWidget.vue';
export default {
name: "SettingsWidget",
@@ -46,7 +75,8 @@ export default {
SelectDropdownWidget,
InputNumberWidget,
FloatInputWidget,
CheckboxWidget
CheckboxWidget,
MultiselectWidget
},
props: {
settingsType: {
@@ -54,6 +84,10 @@ export default {
required: true
}
},
data: {
settings: [],
possibleVisibilities: [],
},
computed: {
...mapGetters(['user']),
},
@@ -64,6 +98,8 @@ export default {
async fetchSettings() {
if (this.user && this.user.id) {
try {
const visibilityResponse = await apiClient.get('/api/settings/visibilities');
this.possibleVisibilities = visibilityResponse.data;
const userid = this.user.id;
const response = await apiClient.post('/api/settings/filter', {
userid: userid,
@@ -73,7 +109,7 @@ export default {
} catch (err) {
this.settings = [];
}
}
}
},
getSettingOptions(fieldName, options) {
return options.map((option) => {
@@ -94,6 +130,7 @@ export default {
settingId: settingId,
value: value
});
this.fetchSettings();
} catch (err) {
console.error('Error updating setting:', err);
}
@@ -120,7 +157,17 @@ export default {
} else {
return false;
}
}
},
async handleVisibilityChange(settingId, visibilityId) {
try {
await apiClient.post('/api/settings/update-visibility', {
userParamTypeId: settingId,
visibilityId: visibilityId
});
} catch (err) {
console.error('Error updating visibility:', err);
}
},
},
data() {
return {
@@ -129,3 +176,9 @@ export default {
}
};
</script>
<style lang="scss" scoped>
label {
float: left;
}
</style>

View File

@@ -30,7 +30,7 @@ export default {
},
methods: {
updateValue(checked) {
this.$emit("input", checked);
this.$emit("input", checked || false);
}
}
};

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="number" :value="formattedValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@input="updateValue($event.target.value)" :step="step" />
@change="updateValue($event.target.value)" :step="step" />
<span v-if="postfix">{{ postfix }}</span>
</label>
</template>
@@ -41,7 +41,7 @@ export default {
},
computed: {
formattedValue() {
return this.value != null && typeof this.value === 'float' ? this.value.toFixed(this.decimals) : '';
return this.value != null ? this.value.toFixed(this.decimals) : '';
},
step() {
return Math.pow(10, -this.decimals);

View File

@@ -1,8 +1,8 @@
<template>
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
@input="updateValue($event.target.value)" />
<input type="number" :value="value" :title="$t(tooltipTr)" :min="min" :max="max"
@change="updateValue($event.target.value)" />
</label>
</template>
@@ -38,7 +38,8 @@ export default {
},
methods: {
updateValue(value) {
this.$emit("input", parseFloat(value));
console.log('changed to ', value)
this.$emit("input", parseInt(value));
}
}
};

View File

@@ -2,7 +2,7 @@
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="text" :value="value" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@input="validateAndUpdate($event.target.value)" />
@change="validateAndUpdate($event.target.value)" />
</label>
</template>

View File

@@ -0,0 +1,139 @@
<template>
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<Multiselect
v-model="selectedOptions"
:options="validList"
:multiple="true"
:close-on-select="false"
:clear-on-select="false"
:preserve-search="true"
:placeholder="$t('select_option')"
:track-by="'value'"
>
<template #option="{ option }">
<span v-if="option && option.value">Option: {{ getTranslation(option) }}</span>
</template>
<template #tag="{ option, remove }">
<span v-if="option && option.captionTr" class="custom-tag">
{{ $t(option.captionTr) }}
<span @click="remove(option)">×</span>
</span>
<span v-else>@e</span>
</template>
</Multiselect>
</label>
</template>
<script>
import Multiselect from 'vue-multiselect';
import 'vue-multiselect/dist/vue-multiselect.min.css';
export default {
name: "MultiselectWidget",
components: { Multiselect },
props: {
labelTr: { type: String, required: true },
value: { type: String, required: false, default: '[]' },
tooltipTr: { type: String, required: true },
width: { type: Number, required: false, default: 10 },
list: {
type: Array,
required: true,
default: () => [] // Standardwert hinzufügen, um undefined zu vermeiden
},
},
data() {
return {
internalValues: this.stringToArray(this.value), // Speichert nur die IDs (Werte)
selectedOptions: this.getOptionsFromIds(this.stringToArray(this.value)) // Hilfsvariable, speichert die vollständigen Objekte
};
},
computed: {
validList() {
return this.validatedList(); // Immer ein Array zurückgeben
}
},
watch: {
value(newValue) {
const ids = this.stringToArray(newValue);
this.internalValues = ids; // Nur die IDs speichern
this.selectedOptions = this.getOptionsFromIds(ids); // Optionen basierend auf IDs setzen
},
selectedOptions(newOptions) {
this.internalValues = newOptions.map(option => option.value); // Nur die IDs extrahieren
this.updateValue();
}
},
methods: {
stringToArray(str) {
try {
const array = JSON.parse(str);
return array.filter(item => item !== null && item !== undefined);
} catch (error) {
console.error('Invalid JSON string in value:', str);
return [];
}
},
updateValue() {
const stringValue = JSON.stringify(this.internalValues); // In JSON-String umwandeln
this.$emit("input", stringValue); // String an das Parent-Element übermitteln
},
getTranslation(option) {
return option.captionTr ? this.$t(option.captionTr) : option.caption;
},
findOption(optionId) {
return this.validatedList().find(opt => opt.value === optionId);
},
getOptionsFromIds(ids) {
return ids.map(id => this.findOption(id)).filter(option => option); // Vollständige Objekte basierend auf IDs abrufen
},
validatedList() {
// Überprüfen, ob die Liste valide ist
if (!this.list || !Array.isArray(this.list)) {
return [];
}
return this.list.filter(option => option && option.value !== null && option.value !== undefined && (option.captionTr || option.caption));
}
}
};
</script>
<style scoped>
label {
display: block;
margin-bottom: 1em;
}
label>span {
display: inline-block;
margin-bottom: 0.5em;
}
.multiselect {
margin-left: 0.5em;
}
.custom-tag {
background-color: #f0f0f0;
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
margin-right: 5px;
display: inline-block;
}
.custom-tag span {
margin-left: 8px;
cursor: pointer;
}
.multiselect {
display: inline-block;
width: 7em;
}
.multiselect__tags {
white-space: nowrap;
}
</style>