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>

View File

@@ -15,12 +15,8 @@
<div>
<label>{{ $t("register.repeatPassword") }}<input type="password" v-model="repeatPassword" /></label>
</div>
<div>
<label>{{ $t("register.language") }}<select v-model="language">
<option value="en">{{ $t("register.languages.en") }}</option>
<option value="de">{{ $t("register.languages.de") }}</option>
</select></label>
</div>
<SelectDropdownWidget labelTr="settings.personal.label.language" :v-model="language"
tooltipTr="settings.personal.tooltip.language" :list="languages" :value="language" />
</div>
<ErrorDialog ref="errorDialog" />
</DialogWidget>
@@ -31,12 +27,14 @@ import { mapActions } from 'vuex';
import apiClient from '@/utils/axios.js';
import DialogWidget from '@/components/DialogWidget.vue';
import ErrorDialog from '@/dialogues/standard/ErrorDialog.vue';
import SelectDropdownWidget from '@/components/form/SelectDropdownWidget.vue';
export default {
name: 'RegisterDialog',
components: {
DialogWidget,
ErrorDialog,
SelectDropdownWidget,
},
data() {
return {
@@ -44,7 +42,8 @@ export default {
username: '',
password: '',
repeatPassword: '',
language: this.getBrowserLanguage(),
language: null,
languages: [],
buttons: [
{ text: 'register.close', action: 'close' },
{ text: 'register.register', action: 'register', disabled: !this.canRegister }
@@ -61,15 +60,22 @@ export default {
this.buttons[1].disabled = !newValue;
}
},
async created() {
await this.getLanguages();
await this.getBrowserLanguage();
},
methods: {
...mapActions(['login']),
getBrowserLanguage() {
async getBrowserLanguage() {
const browserLanguage = navigator.language || navigator.languages[0];
let short = '';
if (browserLanguage.startsWith('de')) {
return 'de';
short = 'de';
} else {
return 'en';
short = 'en';
}
const response = await apiClient.post('/api/settings/getparamvalueid', { paramValue: short });
this.language = response.data.paramValueId;
},
open() {
this.$refs.dialog.open();
@@ -93,7 +99,6 @@ export default {
});
if (response.status === 201) {
console.log(response.data);
this.login(response.data);
this.$refs.dialog.close();
this.$router.push('/activate');
@@ -108,6 +113,16 @@ export default {
this.$refs.errorDialog.open('tr:register.' + error.response.data.error);
}
}
},
async getLanguages() {
try {
const response = await apiClient.post('/api/settings/getparamvalues', {
type: 'language'
});
this.languages = response.data.map(item => { return { value: item.id, captionTr: `settings.personal.language.${item.name}` } });
} catch (err) {
console.error('Error loading languages:', err);
}
}
}
};

View File

@@ -9,6 +9,7 @@ import enChat from './locales/en/chat.json';
import enRegister from './locales/en/register.json';
import enError from './locales/en/error.json';
import enActivate from './locales/en/activate.json';
import enSettings from './locales/en/settings.json';
import deGeneral from './locales/de/general.json';
import deHeader from './locales/de/header.json';
@@ -18,6 +19,7 @@ import deChat from './locales/de/chat.json';
import deRegister from './locales/de/register.json';
import deError from './locales/de/error.json';
import deActivate from './locales/de/activate.json';
import deSettings from './locales/de/settings.json';
const messages = {
en: {
@@ -29,6 +31,7 @@ const messages = {
...enRegister,
...enError,
...enActivate,
...enSettings,
},
de: {
...deGeneral,
@@ -39,6 +42,7 @@ const messages = {
...deRegister,
...deError,
...deActivate,
...deSettings,
}
};

View File

@@ -38,6 +38,9 @@
"userrights": "Benutzerrechte",
"interrests": "Interessen",
"falukant": "Falukant"
},
"m-friends": {
"manageFriends": "Freunde verwalten"
}
}
}

View File

@@ -0,0 +1,32 @@
{
"settings": {
"personal": {
"title": "Persönliche Daten",
"label": {
"language": "Sprache",
"birthdate": "Geburtsdatum",
"gender": "Geschlecht",
"town": "Stadt",
"zip": "PLZ"
},
"tooltip": {
"language": "Sprache",
"birthdate": "Geburtsdatum",
"gender": "Geschlecht",
"town": "Stadt",
"zip": "PLZ"
},
"gender": {
"male": "Männlich",
"female": "Weiblich",
"transmale": "Trans-Frau",
"transfemale": "Trans-Mann",
"nonbinary": "Nonbinär"
},
"language": {
"de": "Deutsch",
"en": "Englisch"
}
}
}
}

View File

@@ -0,0 +1,3 @@
{
}

View File

@@ -7,7 +7,6 @@ import i18n from './i18n';
function getBrowserLanguage() {
const browserLanguage = navigator.language || navigator.languages[0];
console.log(browserLanguage);
if (browserLanguage.startsWith('de')) {
return 'de';
} else {

View File

@@ -11,12 +11,11 @@ const store = createStore({
menu: [],
},
mutations: {
dologin(state, user) {
async dologin(state, user) {
state.isLoggedIn = true;
state.user = user;
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('user', JSON.stringify(user));
console.log(state.user);
},
dologout(state) {
state.isLoggedIn = false;
@@ -45,7 +44,7 @@ const store = createStore({
},
actions: {
async login({ commit, dispatch }, user) {
commit('dologin', user);
await commit('dologin', user);
await dispatch('loadMenu');
dispatch('startMenuReload');
},

View File

@@ -1,11 +1,14 @@
<template>
<div>
Persönliche Einstellungen
</div>
<SettingsWidget :settingsType="'personal'" />
</template>
<script>
import SettingsWidget from '@/components/SettingsWidget.vue';
export default {
name: 'PersonalSettingsView',
components: {
SettingsWidget,
}
}
</script>
</script>