295 lines
8.0 KiB
Vue
295 lines
8.0 KiB
Vue
<template>
|
|
<q-dialog v-model="preview" auto-close square>
|
|
<q-card class="q-dialog-plugin">
|
|
<q-img :src="image" fit="contain">
|
|
<template #error>
|
|
<div style="width: 100%; height: 100%" class="row justify-center">
|
|
<img style="height: 100%; background: white" src="no-image.svg" />
|
|
</div>
|
|
</template>
|
|
</q-img>
|
|
</q-card>
|
|
</q-dialog>
|
|
<q-form @submit="save" @reset="reset">
|
|
<q-card-section class="fit row justify-start content-center items-center">
|
|
<q-input
|
|
v-model="userModel.firstname"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Vorname"
|
|
:rules="[notEmpty]"
|
|
autocomplete="given-name"
|
|
filled
|
|
/>
|
|
<q-input
|
|
v-model="userModel.lastname"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Nachname"
|
|
:rules="[notEmpty]"
|
|
autocomplete="family-name"
|
|
filled
|
|
/>
|
|
<q-input
|
|
v-model="userModel.display_name"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Angezeigter Name"
|
|
:rules="[notEmpty]"
|
|
autocomplete="nickname"
|
|
filled
|
|
/>
|
|
<q-input
|
|
v-model="userModel.mail"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="E-Mail"
|
|
:rules="[isEmail, notEmpty]"
|
|
autocomplete="email"
|
|
filled
|
|
/>
|
|
<q-input
|
|
v-model="userModel.userid"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Benutzername"
|
|
:readonly="!newUser"
|
|
:rules="newUser ? [isFreeUID, notEmpty] : []"
|
|
autocomplete="username"
|
|
filled
|
|
/>
|
|
<q-select
|
|
v-model="userModel.roles"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Rollen"
|
|
filled
|
|
multiple
|
|
use-chips
|
|
:readonly="!canSetRoles"
|
|
:options="allRoles"
|
|
option-label="name"
|
|
option-value="name"
|
|
/>
|
|
<IsoDateInput
|
|
v-model="userModel.birthday"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
label="Geburtstag"
|
|
autocomplete="bday"
|
|
/>
|
|
<q-file
|
|
v-model="avatar"
|
|
filled
|
|
clearable
|
|
label="Avatar"
|
|
accept="image/*, .jpg, .png"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
@clear="avatar = undefined"
|
|
@rejected="onAvatarRejected"
|
|
>
|
|
<template #before>
|
|
<q-avatar @click="preview = true">
|
|
<q-img
|
|
style="min-width: 100%; min-height: 100%"
|
|
:src="image"
|
|
@error="hasAvatar = false"
|
|
>
|
|
<template #error><img style="width: 100%" src="no-image.svg" /></template
|
|
></q-img>
|
|
</q-avatar>
|
|
</template>
|
|
<template #append>
|
|
<q-icon
|
|
v-if="!avatar && hasAvatar"
|
|
:name="deleteAvatar ? 'mdi-delete-restore' : 'mdi-delete'"
|
|
class="cursor-pointer"
|
|
@click="deleteAvatar = !deleteAvatar"
|
|
/>
|
|
</template>
|
|
</q-file>
|
|
</q-card-section>
|
|
<q-separator v-if="!newUser" />
|
|
<q-card-section
|
|
v-if="!newUser"
|
|
class="fit row justify-start content-center items-center q-gutter-y-md"
|
|
>
|
|
<PasswordInput
|
|
v-if="isCurrentUser"
|
|
v-model="password"
|
|
:rules="[notEmpty]"
|
|
filled
|
|
label="Passwort"
|
|
autocomplete="current-password"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
hint="Passwort muss immer eingetragen werden"
|
|
/>
|
|
<PasswordInput
|
|
v-model="newPassword"
|
|
filled
|
|
label="Neues Password"
|
|
autocomplete="new-password"
|
|
class="col-xs-12 col-sm-6 q-pa-sm"
|
|
/>
|
|
</q-card-section>
|
|
<q-card-actions align="right">
|
|
<q-btn label="Reset" type="reset" />
|
|
<q-btn color="primary" type="submit" label="Speichern" />
|
|
</q-card-actions>
|
|
</q-form>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Notify } from 'quasar';
|
|
import { IsoDateInput, PasswordInput } from '@flaschengeist/api/components';
|
|
import { defineComponent, computed, ref, onBeforeMount, PropType, watchEffect } from 'vue';
|
|
import {
|
|
avatarURL,
|
|
hasPermission,
|
|
notEmpty,
|
|
isEmail,
|
|
useMainStore,
|
|
useUserStore,
|
|
isAxiosError,
|
|
} from '@flaschengeist/api';
|
|
import { PERMISSIONS } from '../../permissions';
|
|
|
|
export default defineComponent({
|
|
name: 'MainUserSettings',
|
|
components: { IsoDateInput, PasswordInput },
|
|
props: {
|
|
user: {
|
|
required: true,
|
|
type: Object as PropType<FG.User>,
|
|
},
|
|
newUser: { type: Boolean, default: false },
|
|
},
|
|
emits: {
|
|
'update:user': (payload: FG.User) => !!payload,
|
|
},
|
|
setup(props, { emit }) {
|
|
const userStore = useUserStore();
|
|
const mainStore = useMainStore();
|
|
|
|
onBeforeMount(() => {
|
|
void userStore.getRoles(false);
|
|
});
|
|
|
|
const preview = ref(false);
|
|
const password = ref('');
|
|
const newPassword = ref('');
|
|
const avatar = ref<File>();
|
|
const userModel = ref(Object.assign({}, props.user));
|
|
const previewURL = ref<string>(); //< Preview of an avatar as data URL
|
|
|
|
const allRoles = computed(() => userStore.roles.map((role) => role.name));
|
|
|
|
const canSetRoles = computed(() => hasPermission(PERMISSIONS.SET_ROLES));
|
|
const isCurrentUser = computed(() => userModel.value.userid === mainStore.currentUser.userid);
|
|
|
|
const deleteAvatar = ref(false);
|
|
const hasAvatar = ref(true);
|
|
|
|
/* Reset model if props changed */
|
|
watchEffect(() => {
|
|
if (props.user.userid && props.user.userid !== userModel.value.userid) reset();
|
|
});
|
|
|
|
watchEffect(() => {
|
|
if (avatar.value && avatar.value instanceof File) {
|
|
let reader = new FileReader();
|
|
|
|
reader.onload = (e) => {
|
|
previewURL.value = <string | undefined>(e.target?.result || undefined);
|
|
};
|
|
|
|
reader.readAsDataURL(avatar.value);
|
|
} else previewURL.value = undefined;
|
|
});
|
|
|
|
/**
|
|
* Notify user is file does not fulfil the requirements
|
|
*/
|
|
function onAvatarRejected() {
|
|
Notify.create({
|
|
group: false,
|
|
type: 'negative',
|
|
message: 'Datei zu groß oder keine gültige Bilddatei.',
|
|
timeout: 10000,
|
|
progress: true,
|
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
|
});
|
|
avatar.value = new File([], '', {});
|
|
}
|
|
|
|
/**
|
|
* Save changes, emit user model, directly uploads / deletes avatar by using the API
|
|
*/
|
|
async function save() {
|
|
let changed: FG.User = userModel.value;
|
|
if (typeof changed.birthday === 'string') changed.birthday = new Date(changed.birthday);
|
|
changed = Object.assign(changed, {
|
|
password: password.value,
|
|
});
|
|
if (newPassword.value != '') {
|
|
changed = Object.assign(changed, {
|
|
new_password: newPassword.value,
|
|
});
|
|
}
|
|
|
|
emit('update:user', changed);
|
|
|
|
if (avatar.value !== undefined && avatar.value.name) {
|
|
await userStore
|
|
.uploadAvatar(changed, avatar.value)
|
|
.catch((response) => isAxiosError(response, 400) && onAvatarRejected());
|
|
hasAvatar.value = true;
|
|
} else if (deleteAvatar.value) {
|
|
await userStore.deleteAvatar(changed);
|
|
hasAvatar.value = false;
|
|
}
|
|
password.value = '';
|
|
}
|
|
|
|
function reset() {
|
|
userModel.value = Object.assign({}, props.user);
|
|
password.value = '';
|
|
newPassword.value = '';
|
|
avatar.value = undefined;
|
|
previewURL.value = undefined;
|
|
deleteAvatar.value = false;
|
|
}
|
|
|
|
function isFreeUID(val: string) {
|
|
return (
|
|
userStore.users.findIndex((user) => user.userid === val) === -1 ||
|
|
'Benutzername ist schon vergeben'
|
|
);
|
|
}
|
|
|
|
const image = computed(() => {
|
|
if (previewURL.value) {
|
|
return previewURL.value;
|
|
}
|
|
if (!deleteAvatar.value) {
|
|
return avatarURL(props.user);
|
|
}
|
|
return 'no-image.svg';
|
|
});
|
|
|
|
return {
|
|
allRoles,
|
|
avatar,
|
|
canSetRoles,
|
|
deleteAvatar,
|
|
hasAvatar,
|
|
image,
|
|
isCurrentUser,
|
|
isEmail,
|
|
isFreeUID,
|
|
newPassword,
|
|
notEmpty,
|
|
onAvatarRejected,
|
|
password,
|
|
preview,
|
|
reset,
|
|
save,
|
|
userModel,
|
|
};
|
|
},
|
|
});
|
|
</script>
|