Added vuelidator to the project, implemented update of Employeeprofile
This commit is contained in:
100
package-lock.json
generated
100
package-lock.json
generated
@@ -10,6 +10,8 @@
|
||||
"dependencies": {
|
||||
"@oruga-ui/oruga-next": "^0.4.5",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"@vuelidate/core": "^2.0.0-alpha.30",
|
||||
"@vuelidate/validators": "^2.0.0-alpha.24",
|
||||
"axios": "^0.23.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.6.0",
|
||||
@@ -321,6 +323,72 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.20.tgz",
|
||||
"integrity": "sha512-FbpX+hD5BvXCQerEYO7jtAGHlhAkhTQ4KIV73kmLWNlawWhTiVuQxizgVb0BOkX5oG9cIRZ42EG++d/k/Efp0w=="
|
||||
},
|
||||
"node_modules/@vuelidate/core": {
|
||||
"version": "2.0.0-alpha.30",
|
||||
"resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0-alpha.30.tgz",
|
||||
"integrity": "sha512-J4OO/IqzBP/qlUJJssm+Af9aVIGCemCASiwvXOSHQoW0rUo9aeSsUQ/+14yBHZ7vQ5I1MLgTYMkgql0ejBXK1g==",
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.11.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vuelidate/core/node_modules/vue-demi": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@vuelidate/validators": {
|
||||
"version": "2.0.0-alpha.24",
|
||||
"resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0-alpha.24.tgz",
|
||||
"integrity": "sha512-LjZGP2dK9848Xw+KLDb6RlaeN3crIdl+z2TZYyg+BqrJhWAV1ZUnpgzGKf4wefsqF5dQ4bWAZY/xnBKn2naIfg==",
|
||||
"dependencies": {
|
||||
"vue-demi": "^0.11.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@vuelidate/validators/node_modules/vue-demi": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
"vue-demi-fix": "bin/vue-demi-fix.js",
|
||||
"vue-demi-switch": "bin/vue-demi-switch.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antfu"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@vue/composition-api": "^1.0.0-rc.1",
|
||||
"vue": "^3.0.0-0 || ^2.6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@vue/composition-api": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
@@ -2001,6 +2069,38 @@
|
||||
"resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.20.tgz",
|
||||
"integrity": "sha512-FbpX+hD5BvXCQerEYO7jtAGHlhAkhTQ4KIV73kmLWNlawWhTiVuQxizgVb0BOkX5oG9cIRZ42EG++d/k/Efp0w=="
|
||||
},
|
||||
"@vuelidate/core": {
|
||||
"version": "2.0.0-alpha.30",
|
||||
"resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.0-alpha.30.tgz",
|
||||
"integrity": "sha512-J4OO/IqzBP/qlUJJssm+Af9aVIGCemCASiwvXOSHQoW0rUo9aeSsUQ/+14yBHZ7vQ5I1MLgTYMkgql0ejBXK1g==",
|
||||
"requires": {
|
||||
"vue-demi": "^0.11.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-demi": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"@vuelidate/validators": {
|
||||
"version": "2.0.0-alpha.24",
|
||||
"resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.0-alpha.24.tgz",
|
||||
"integrity": "sha512-LjZGP2dK9848Xw+KLDb6RlaeN3crIdl+z2TZYyg+BqrJhWAV1ZUnpgzGKf4wefsqF5dQ4bWAZY/xnBKn2naIfg==",
|
||||
"requires": {
|
||||
"vue-demi": "^0.11.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"vue-demi": {
|
||||
"version": "0.11.4",
|
||||
"resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.11.4.tgz",
|
||||
"integrity": "sha512-/3xFwzSykLW2HiiLie43a+FFgNOcokbBJ+fzvFXd0r2T8MYohqvphUyDQ8lbAwzQ3Dlcrb1c9ykifGkhSIAk6A==",
|
||||
"requires": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"acorn": {
|
||||
"version": "7.4.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
"dependencies": {
|
||||
"@oruga-ui/oruga-next": "^0.4.5",
|
||||
"@popperjs/core": "^2.10.2",
|
||||
"@vuelidate/core": "^2.0.0-alpha.30",
|
||||
"@vuelidate/validators": "^2.0.0-alpha.24",
|
||||
"axios": "^0.23.0",
|
||||
"bootstrap": "^5.1.3",
|
||||
"bootstrap-icons": "^1.6.0",
|
||||
|
||||
@@ -1,20 +1,23 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { ref } from 'vue'
|
||||
import type { Ref } from 'vue'
|
||||
import { toRef } from 'vue'
|
||||
|
||||
const isActive : Ref<boolean> = ref(false)
|
||||
|
||||
const props = defineProps(['isActive'])
|
||||
const emit = defineEmits(['save', 'toggleEdit'])
|
||||
|
||||
const isActive = toRef(props, 'isActive')
|
||||
|
||||
function onEdit() {
|
||||
isActive.value = true
|
||||
emit('toggleEdit')
|
||||
}
|
||||
|
||||
function onSave() {
|
||||
|
||||
emit('save')
|
||||
}
|
||||
|
||||
function onCancel() {
|
||||
isActive.value = false
|
||||
emit('toggleEdit')
|
||||
}
|
||||
|
||||
</script>
|
||||
@@ -28,12 +31,12 @@ function onCancel() {
|
||||
|
||||
<button v-if="!isActive" type="button" @click="onEdit" class="btn btn-primary ms-auto">
|
||||
<i class="bi bi-pen"></i>
|
||||
Bearbeiten
|
||||
Mitarbeiter bearbeiten
|
||||
</button>
|
||||
|
||||
<button v-if="isActive" type="button" @click="onSave" class="btn btn-success ms-auto">
|
||||
<i class="bi bi-save"></i>
|
||||
Speichern
|
||||
Mitarbeiter speichern
|
||||
</button>
|
||||
|
||||
<button v-if="isActive" type="button" @click="onCancel" class="btn btn-outline-secondary ms-3">
|
||||
|
||||
@@ -1,3 +1,102 @@
|
||||
<template>
|
||||
|
||||
<VProfileControls class="mb-5" :isActive="editEmployee" @save="onUpdateEmployee" @toggleEdit="onToggleEdit" />
|
||||
|
||||
<form @keydown.enter="onEnter" class="text-start">
|
||||
<div class="row mb-5">
|
||||
<div class="col pe-5">
|
||||
<h4 class="">Persönliche Informationen</h4>
|
||||
|
||||
<label for="first-name" class="form-label">Vorname:</label>
|
||||
<input
|
||||
type="text"
|
||||
v-model="state.employee.firstName"
|
||||
id="first-name"
|
||||
class="form-control"
|
||||
:class="{'is-invalid' : classIsInvalid('employee', 'firstName')}"
|
||||
:disabled="!editEmployee"
|
||||
>
|
||||
<div
|
||||
v-for="(error) in v$.employee.firstName.$errors"
|
||||
class="invalid-feedback"
|
||||
id="firstNameFeedback">
|
||||
{{error.$message}}
|
||||
</div>
|
||||
|
||||
<label for="last-name" class="form-label">Nachname:</label>
|
||||
<input type="text" v-model="state.employee.lastName" id="last-name" class="form-control" :disabled="!editEmployee">
|
||||
|
||||
<label for="shorthand" class="form-label">Kürzel:</label>
|
||||
<input type="text" v-model="state.employee.shorthand" id="shorthand" class="form-control" :disabled="!editEmployee">
|
||||
</div>
|
||||
<div class="col ps-5 border-start">
|
||||
<h4 class="">Kontaktdaten</h4>
|
||||
<label for="phone" class="form-label">Telefonnummer:</label>
|
||||
<input type="phone" v-model="state.employee.phone" id="phone" class="form-control" :disabled="!editEmployee">
|
||||
<label for="mobile" class="form-label">Handynummer:</label>
|
||||
<input type="mobile" v-model="state.employee.mobile" id="mobile" class="form-control" :disabled="!editEmployee">
|
||||
<label for="email" class="form-label">E-Mail-Adresse:</label>
|
||||
<input
|
||||
type="email"
|
||||
v-model="state.employee.email"
|
||||
id="email"
|
||||
class="form-control"
|
||||
:class="{'is-invalid': classIsInvalid('employee', 'email')}" :disabled="!editEmployee"
|
||||
>
|
||||
<div v-for="(error) in v$.employee.email.$errors" class="invalid-feedback" id="emailFeedback">
|
||||
{{error.$message}}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col pe-5">
|
||||
<h4 class="">Vertragsinformationen:</h4>
|
||||
<label for="contract-hours" class="form-label">Wochenstunden:</label>
|
||||
<input type="number" v-model="state.employee.contractHours" id="contract-hours" class="form-control" :class="{'is-invalid' : classIsInvalid('employee', 'contractHours')}" :disabled="!editEmployee">
|
||||
<div v-for="(error) in v$.employee.contractHours.$errors" class="invalid-feedback" id="contractHoursFeedback">
|
||||
{{error.$message}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="col ps-5 border-start">
|
||||
<h4>Benutzerinformationen:</h4>
|
||||
<template v-if="state.employee.hasUser || createUser">
|
||||
<label for="username" class="form-label">Benutzername:</label>
|
||||
<input type="text" v-model="state.user.username" id="username" class="form-control" :disabled="!createUser" >
|
||||
<label for="password" class="form-label">Neues Passwort:</label>
|
||||
<input type="password" v-model="state.user.password" id="password" class="form-control" :disabled="!editEmployee">
|
||||
<div id="passwordFeedback" class="invalid-feedback">
|
||||
{{}}
|
||||
</div>
|
||||
<label for="password-repeat" class="form-label">Neues Passwort wiederholen:</label>
|
||||
<input type="password" v-model="state.user.passwordRepeat" id="password-repeat" class="form-control mb-3" :class="{'is-invalid': classIsInvalid('user', 'passwordRepeat')}" :disabled="!editEmployee" >
|
||||
<div v-for="(error) in v$.user.passwordRepeat.$errors" class="invalid-feedback" id="passwordRepeatFeedback">
|
||||
{{error.$message}}
|
||||
</div>
|
||||
|
||||
<template v-if="createUser">
|
||||
<button class="btn btn-success me-3">
|
||||
<i class="bi bi-save"></i>
|
||||
Benutzer speichern
|
||||
</button>
|
||||
<button class="btn btn-outline-secondary" @click="createUser = false">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
Abbrecchen
|
||||
</button>
|
||||
</template>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
<p>Kein Benutzer vorhanden</p>
|
||||
<button class="btn btn-primary" @click="createUser=true">Benutzer erstellen</button>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
type ResultData = {
|
||||
@@ -19,59 +118,124 @@ type ResultData = {
|
||||
}
|
||||
|
||||
import VProfileControls from '@/components/VProfileControls.vue';
|
||||
import { reactive, onMounted, computed, watch } from 'vue'
|
||||
import { reactive, onMounted, computed, ref, watch } from 'vue'
|
||||
import { useUser } from '@/stores/user';
|
||||
import axios from '@/axios'
|
||||
import { useNotifications } from '@/stores/notifications';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { useRoute } from 'vue-router';
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { required, email, between, decimal, sameAs, helpers } from '@vuelidate/validators'
|
||||
|
||||
const userStore = useUser()
|
||||
const useNotification = useNotifications()
|
||||
const route = useRoute()
|
||||
|
||||
const employee = reactive({
|
||||
id: NaN,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
shorthand: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
contractHours: '',
|
||||
hasUser: false
|
||||
const editEmployee = ref(false)
|
||||
|
||||
let state = reactive({
|
||||
employee: {
|
||||
id: NaN,
|
||||
firstName: '',
|
||||
lastName: '',
|
||||
shorthand: '',
|
||||
phone: '',
|
||||
mobile: '',
|
||||
email: '',
|
||||
contractHours: '',
|
||||
hasUser: false
|
||||
},
|
||||
user: {
|
||||
id: NaN,
|
||||
username: '',
|
||||
password: '',
|
||||
passwordRepeat: ''
|
||||
}
|
||||
})
|
||||
|
||||
const user = reactive({
|
||||
id: NaN,
|
||||
username: '',
|
||||
password: '',
|
||||
passwordRepeat: ''
|
||||
})
|
||||
const rules = computed(() => ({
|
||||
employee: {
|
||||
firstName: {
|
||||
required: helpers.withMessage('Vorname ist erforderlich.', required)
|
||||
},
|
||||
email: {
|
||||
email: helpers.withMessage('Muss eine korrekte E-Mail-Adresse enthalten.', email)
|
||||
},
|
||||
contractHours: {
|
||||
decimal,
|
||||
betweenValue: helpers.withMessage('Vertragsstunden müssen zwischen 0 und 40 liegen', between(0, 40))
|
||||
}
|
||||
},
|
||||
user: {
|
||||
passwordRepeat: {
|
||||
sameAs: helpers.withMessage('Die eingebebenen Passwörter müssen übereinstimmen', sameAs(state.user.password))
|
||||
}
|
||||
}
|
||||
}))
|
||||
|
||||
function onUpdate() {
|
||||
|
||||
const v$ = useVuelidate(rules, state)
|
||||
|
||||
const createUser = ref(false)
|
||||
|
||||
async function onUpdateEmployee() {
|
||||
if(await v$.value.$validate()){
|
||||
if(state.user.password !== undefined && state.user.password.length > 0){
|
||||
// push user
|
||||
}
|
||||
try {
|
||||
const result = await axios.patch('employees/'+state.employee.id, state.employee,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+userStore.token
|
||||
}
|
||||
}
|
||||
)
|
||||
onToggleEdit()
|
||||
}
|
||||
catch(error){
|
||||
if(error instanceof Error) useNotification.add('danger', error.message)
|
||||
else console.log(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function onEnter() {
|
||||
console.log("hi")
|
||||
onUpdateEmployee
|
||||
}
|
||||
|
||||
function onToggleEdit() {
|
||||
editEmployee.value = !editEmployee.value
|
||||
v$.value.$reset()
|
||||
getEmployee()
|
||||
}
|
||||
|
||||
watch(() => [route.params, route.name], ([newParam, newName], [oldParam, oldName]) => {
|
||||
if(newName === oldName && newParam !== oldParam) {
|
||||
getEmployee()
|
||||
createUser.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function classIsInvalid<E extends 'user' | 'employee'>(entity: E, field: E extends 'user' ? 'passwordRepeat' : 'firstName' | 'email' | 'contractHours') : boolean {
|
||||
if(entity === 'user'){
|
||||
let fU = field as 'passwordRepeat'
|
||||
return v$.value.user[fU].$dirty && v$.value.user[fU].$invalid
|
||||
}
|
||||
else if (entity === 'employee') {
|
||||
let fE = field as 'firstName' | 'email' | 'contractHours'
|
||||
return v$.value.employee[fE].$dirty && v$.value.employee[fE].$invalid
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async function getEmployee() {
|
||||
try {
|
||||
const data : ResultData = await <ResultData>(await axios.get('employees/'+route.params.id, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+useUser().token
|
||||
'Authorization': 'Bearer '+userStore.token
|
||||
}
|
||||
})).data
|
||||
|
||||
Object.assign(employee, data.employee)
|
||||
Object.assign(user, data.user)
|
||||
Object.assign(state, data)
|
||||
}
|
||||
catch(err){
|
||||
if(err instanceof Error) useNotification.add('danger', err.message, -1)
|
||||
@@ -84,63 +248,7 @@ onMounted(async () => {
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
<VProfileControls class="mb-5" />
|
||||
|
||||
{{employee}}
|
||||
|
||||
{{user}}
|
||||
|
||||
<form @keydown.enter="onEnter" class="text-start">
|
||||
<div class="row mb-5">
|
||||
<div class="col pe-5">
|
||||
<h4 class="">Persönliche Informationen</h4>
|
||||
|
||||
<label for="first-name" class="form-label">Vorname:</label>
|
||||
<input type="text" v-model="employee.firstName" id="first-name" class="form-control">
|
||||
|
||||
<label for="last-name" class="form-label">Nachname:</label>
|
||||
<input type="text" v-model="employee.lastName" id="last-name" class="form-control">
|
||||
|
||||
<label for="shorthand" class="form-label">Kürzel:</label>
|
||||
<input type="text" v-model="employee.shorthand" id="shorthand" class="form-control">
|
||||
</div>
|
||||
<div class="col ps-5 border-start">
|
||||
<h4 class="">Kontaktdaten</h4>
|
||||
<label for="phone" class="form-label">Telefonnummer:</label>
|
||||
<input type="phone" v-model="employee.phone" id="phone" class="form-control">
|
||||
<label for="mobile" class="form-label">Handynummer:</label>
|
||||
<input type="mobile" v-model="employee.mobile" id="mobile" class="form-control">
|
||||
<label for="email" class="form-label">E-Mail-Adresse:</label>
|
||||
<input type="email" v-model="employee.email" id="email" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col pe-5">
|
||||
<h4 class="">Vertragsinformationen:</h4>
|
||||
<label for="contract-hours" class="form-label">Wochenstunden:</label>
|
||||
<input type="number" v-model="employee.contractHours" id="contract-hours" class="form-control">
|
||||
</div>
|
||||
<div class="col ps-5 border-start">
|
||||
<template v-if="employee.hasUser">
|
||||
<h4>Benutzerinformationen:</h4>
|
||||
<label for="username" class="form-label">Benutzername:</label>
|
||||
<input type="text" v-model="user.username" id="username" class="form-control" disabled>
|
||||
<label for="password" class="form-label">Passwort:</label>
|
||||
<input type="password" v-model="user.password" id="password" class="form-control">
|
||||
<label for="password-repeat" class="form-label">Passwort wiederholen:</label>
|
||||
<input type="password" v-model="user.passwordRepeat" id="password-repeat" class="form-control">
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</form>
|
||||
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -148,4 +256,8 @@ onMounted(async () => {
|
||||
margin-top: 0.75rem
|
||||
}
|
||||
|
||||
i {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user