Implemented personal settings

This commit is contained in:
Torsten Schulz
2024-07-22 18:14:12 +02:00
parent cd0699f3fd
commit 89842ff6c5
34 changed files with 899 additions and 113 deletions

View File

@@ -0,0 +1,100 @@
<template>
<div class="settings-widget">
<h2>{{ $t("settings.personal.title") }}</h2>
<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)" />
<div v-else>{{ setting }}
</div>
</template>
</div>
</template>
<script>
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';
export default {
name: "SettingsWidget",
components: {
InputStringWidget,
DateInputWidget,
SelectDropdownWidget,
},
props: {
settingsType: {
type: String,
required: true
}
},
computed: {
...mapGetters(['user']),
},
async mounted() {
await this.fetchSettings();
},
methods: {
async fetchSettings() {
if (this.user && this.user.id) {
try {
const userid = this.user.id;
const response = await apiClient.post('/api/settings/filter', {
userid: userid,
type: this.settingsType
});
this.settings = response.data;
} catch (err) {
this.settings = [];
}
}
},
getSettingOptions(fieldName, options) {
return options.map((option) => {
return {
value: option.id,
captionTr: `settings.personal.${fieldName}.${option.value}`
}
});
},
async handleInput(settingId, value) {
if (['object', 'array'].includes(typeof value)) {
return;
}
try {
const userid = this.user.id;
await apiClient.post('/api/settings/update', {
userid: userid,
settingId: settingId,
value: value
});
console.log('Setting updated:', settingId, value);
} catch (err) {
console.error('Error updating setting:', err);
}
},
languagesList() {
return [
{ value: 'en', captionTr: 'settings.personal.languages.en' },
{ value: 'de', captionTr: 'settings.personal.languages.de' },
];
}
},
data() {
return {
settings: [],
};
}
};
</script>

View File

@@ -0,0 +1,48 @@
<template>
<label>
<input type="checkbox" :checked="value" @change="updateValue($event.target.checked)" :title="$t(tooltipTr)" />
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
</label>
</template>
<script>
export default {
name: "CheckboxWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: Boolean,
required: false,
default: false
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
}
},
methods: {
updateValue(checked) {
this.$emit("input", checked);
}
}
};
</script>
<style scoped>
label {
display: flex;
align-items: center;
}
input[type="checkbox"] {
margin-right: 0.5em;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<input type="date" v-model="internalValue" :placeholder="$t(labelTr)" :title="$t(tooltipTr)"
@change="updateValue($event.target.value)" />
</label>
</template>
<script>
import { mapGetters } from 'vuex';
export default {
name: "DateInputWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: String,
required: false,
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
}
},
data() {
return {
internalValue: this.value
}
},
watch: {
value(newValue) {
this.internalValue = newValue;
}
},
computed: {
...mapGetters(['language']),
},
methods: {
updateValue(value) {
this.internalValue = value;
this.$emit("input", value);
}
}
};
</script>
<style scoped>
label {
display: block;
}
label>span {
display: inline-block;
}
input {
margin-left: 0.5em;
}
</style>

View File

@@ -0,0 +1,67 @@
<template>
<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" />
<span v-if="postfix">{{ postfix }}</span>
</label>
</template>
<script>
export default {
name: "FloatInputWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: Number,
required: false,
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
},
decimals: {
type: Number,
required: false,
default: 2
},
postfix: {
type: String,
required: false,
default: ''
}
},
computed: {
formattedValue() {
return this.value != null ? this.value.toFixed(this.decimals) : '';
},
step() {
return Math.pow(10, -this.decimals);
}
},
methods: {
updateValue(value) {
this.$emit("input", parseFloat(value).toFixed(this.decimals));
}
}
};
</script>
<style scoped>
label {
display: flex;
align-items: center;
}
input {
margin-right: 0.5em;
}
</style>

View File

@@ -0,0 +1,45 @@
<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)" />
</label>
</template>
<script>
export default {
name: "InputNumberWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: Number,
required: false,
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
},
min: {
type: Number,
required: false
},
max: {
type: Number,
required: false
}
},
methods: {
updateValue(value) {
this.$emit("input", parseFloat(value));
}
}
};
</script>

View File

@@ -0,0 +1,65 @@
<template>
<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)" />
</label>
</template>
<script>
export default {
name: "InputStringWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: String,
required: false,
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
},
regex: {
type: String,
required: false,
default: null
}
},
methods: {
validateAndUpdate(value) {
if (this.regex) {
const pattern = new RegExp(this.regex);
if (pattern.test(value)) {
this.updateValue(value);
}
} else {
this.updateValue(value);
}
},
updateValue(value) {
this.$emit("input", value);
}
}
};
</script>
<style scoped>
label {
display: block;
}
label > span {
display: inline-block;
}
input {
margin-left: 0.5em;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<label>
<span :style="{ width: width + 'em' }">{{ $t(labelTr) }}</span>
<select :title="$t(tooltipTr)" :value="value" @change="updateValue($event.target.value)">
<option v-if="allowNone" :value="noneValue">{{ $t('none') }}</option>
<option v-for="item in list" :key="item.value" :value="item.value">
{{ item.captionTr ? $t(item.captionTr) : item.caption }}
</option>
</select>
</label>
</template>
<script>
export default {
name: "SelectDropdownWidget",
props: {
labelTr: {
type: String,
required: true,
},
value: {
type: String,
required: false,
},
tooltipTr: {
type: String,
required: true,
},
width: {
type: Number,
required: false,
default: 10
},
list: {
type: Array,
required: true,
},
allowNone: {
type: Boolean,
required: false,
default: false
},
noneValue: {
type: String,
required: false,
default: ''
}
},
methods: {
updateValue(value) {
this.$emit("input", value);
}
}
};
</script>
<style scoped>
label {
display: block;
}
label>span {
display: inline-block;
}
select {
margin-left: 0.5em;
}
</style>