added lots of animations, added debounce to search query, fixed faulty refetching when logging out from profile page
This commit is contained in:
@@ -6,7 +6,7 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Vite App</title>
|
||||
</head>
|
||||
<body class="bg-light">
|
||||
<body style="overflow-Y: scroll;" class="bg-light">
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
27
src/App.vue
27
src/App.vue
@@ -3,8 +3,12 @@
|
||||
<v-main>
|
||||
<Notification />
|
||||
<div class="container shadow px-4 pt-3 pb-5 text-center">
|
||||
<VNavigation />
|
||||
<router-view></router-view>
|
||||
<VNavigation />
|
||||
<router-view v-slot="{ Component, route }">
|
||||
<transition name="fade-slide-y" mode="out-in">
|
||||
<component :is="Component" :key="route.path" />
|
||||
</transition>
|
||||
</router-view>
|
||||
</div>
|
||||
|
||||
</v-main>
|
||||
@@ -18,11 +22,28 @@ import VNavigation from './components/VNavigation.vue';
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.fade-slide-y- {
|
||||
&enter-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
&leave-active {
|
||||
transition: all .2s cubic-bezier(1,.5,.8,1)
|
||||
// transition: all .3s ease
|
||||
}
|
||||
&enter-from,
|
||||
&leave-to {
|
||||
transform: translateY(10px);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
import { ref, computed} from 'vue';
|
||||
import type { PropType, Ref } from 'vue';
|
||||
import { union } from 'lodash';
|
||||
import _debounce from 'lodash/debounce'
|
||||
|
||||
const props = defineProps({
|
||||
columns: {
|
||||
@@ -50,9 +51,9 @@ const unionColumns = computed(() => {
|
||||
return union(props.columns, columnsChecked.value)
|
||||
})
|
||||
|
||||
async function search() {
|
||||
const search = _debounce(() => {
|
||||
emit('search', queryString.value)
|
||||
}
|
||||
}, 150)
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,19 +1,3 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUser } from '@/stores/user';
|
||||
|
||||
const userStore = useUser()
|
||||
const router = useRouter()
|
||||
|
||||
async function onLogout() {
|
||||
if(await userStore.logout()){
|
||||
router.push({name: 'Login'})
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<nav class="nav justify-content-center border-bottom mb-4 pb-2 d-flex">
|
||||
<router-link class="nav-link" :to="{name: 'Home'}">Home</router-link>
|
||||
@@ -32,6 +16,20 @@ async function onLogout() {
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
||||
import { useRouter } from 'vue-router';
|
||||
import { useUser } from '@/stores/user';
|
||||
|
||||
const userStore = useUser()
|
||||
const router = useRouter()
|
||||
|
||||
function onLogout() {
|
||||
userStore.logout(router)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -29,25 +29,50 @@ function onCancel() {
|
||||
Zurück
|
||||
</router-link>
|
||||
|
||||
<button v-if="!isActive" type="button" @click="onEdit" class="btn btn-primary ms-auto">
|
||||
<i class="bi bi-pen"></i>
|
||||
Mitarbeiter bearbeiten
|
||||
</button>
|
||||
|
||||
<button v-if="isActive" type="submit" @click.prevent="onSave" class="btn btn-success ms-auto">
|
||||
<i class="bi bi-save"></i>
|
||||
Mitarbeiter speichern
|
||||
</button>
|
||||
|
||||
<button v-if="isActive" type="button" @click="onCancel" class="btn btn-outline-secondary ms-3">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
Abbrechen
|
||||
</button>
|
||||
<transition name="button-save" mode="out-in">
|
||||
<div class="ms-auto" v-if="isActive">
|
||||
<button v-if="isActive" type="submit" @click.prevent="onSave" class="btn btn-success">
|
||||
<i class="bi bi-save"></i>
|
||||
Mitarbeiter speichern
|
||||
</button>
|
||||
<button v-if="isActive" type="button" @click="onCancel" class="btn btn-outline-secondary ms-3">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
Abbrechen
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="ms-auto">
|
||||
<button v-if="!isActive" type="button" @click="onEdit" key="button-not-active" class="btn btn-primary">
|
||||
<i class="bi bi-pen"></i>
|
||||
Mitarbeiter bearbeiten
|
||||
</button>
|
||||
</div>
|
||||
</transition>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@mixin animation-target($offset) {
|
||||
opacity: 0;
|
||||
transform: translateX($offset);
|
||||
}
|
||||
.button-save-{
|
||||
&enter-active,
|
||||
&leave-active {
|
||||
transition: all .1s ease-in-out
|
||||
}
|
||||
|
||||
&enter-from,
|
||||
&leave-to {
|
||||
@include animation-target(15px)
|
||||
}
|
||||
|
||||
&leave-to {
|
||||
@include animation-target(-15px)
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -23,7 +23,7 @@ export const useNotifications = defineStore('notifications', {
|
||||
text:text,
|
||||
}
|
||||
)
|
||||
if (timeout !== -1) setTimeout(() => this.remove(id), timeout)
|
||||
if (timeout > -1) setTimeout(() => this.remove(id), timeout)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
import { defineStore, storeToRefs } from 'pinia'
|
||||
import { defineStore } from 'pinia'
|
||||
import { useNotifications } from './notifications'
|
||||
import { getUnixTime } from 'date-fns'
|
||||
import { AxiosError } from 'axios'
|
||||
import axios from '@/axios'
|
||||
import { apiUrl } from '@/axios'
|
||||
import Axios from 'axios'
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useRequest } from 'vue-request'
|
||||
import { json } from 'stream/consumers'
|
||||
import axios from '@/axios'
|
||||
import Axios from 'axios'
|
||||
import { useRouter } from 'vue-router'
|
||||
import type { Router } from 'vue-router'
|
||||
|
||||
type AuthSuccResult = {
|
||||
user: string,
|
||||
role: string,
|
||||
user: string,
|
||||
role: string,
|
||||
token: string
|
||||
}
|
||||
|
||||
|
||||
export const useUser = defineStore({
|
||||
id: 'storeUser',
|
||||
|
||||
@@ -23,8 +21,9 @@ export const useUser = defineStore({
|
||||
user: '',
|
||||
role: '',
|
||||
isLoggedIn: false,
|
||||
rememberMe: false,
|
||||
token: ''
|
||||
token: '',
|
||||
errorMessage: '',
|
||||
loading: ref(false)
|
||||
}
|
||||
},
|
||||
|
||||
@@ -36,18 +35,27 @@ export const useUser = defineStore({
|
||||
},
|
||||
|
||||
actions: {
|
||||
async login(username: string, password: string):
|
||||
Promise<boolean |
|
||||
'E_INVALID_AUTH_PASSWORD: Password mis-match' |
|
||||
'E_INVALID_AUTH_UID: User not found'>
|
||||
{
|
||||
|
||||
async login(username: string, password: string, router: Router) {
|
||||
this.loading = true
|
||||
const notifications = useNotifications()
|
||||
|
||||
|
||||
function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
/* async function axiosQuery<T>(username: string, password: string){
|
||||
return await axios.post<T>('login',{
|
||||
username,
|
||||
password
|
||||
})
|
||||
}
|
||||
|
||||
const { loading } = useRequest(axiosQuery(username, password))
|
||||
this.loading */
|
||||
try {
|
||||
const response = await axios.post<AuthSuccResult>('login', {
|
||||
username: username,
|
||||
password: password
|
||||
username,
|
||||
password
|
||||
})
|
||||
|
||||
this.isLoggedIn = true
|
||||
@@ -57,53 +65,43 @@ export const useUser = defineStore({
|
||||
|
||||
notifications.add('success', 'Login successful.')
|
||||
|
||||
return true
|
||||
} catch(err : unknown) {
|
||||
await router.push({name: 'Home'})
|
||||
this.loading = false
|
||||
} catch(err : unknown) {
|
||||
this.loading = false
|
||||
if (Axios.isAxiosError(err) && err.response && err.response.data){
|
||||
|
||||
const data = err.response.data
|
||||
|
||||
if(
|
||||
data === 'E_INVALID_AUTH_PASSWORD: Password mis-match' ||
|
||||
data === 'E_INVALID_AUTH_UID: User not found'
|
||||
) return data
|
||||
this.errorMessage = err.response.data as string
|
||||
}
|
||||
|
||||
else if(err instanceof Error){
|
||||
notifications.add('danger', err.message, -1)
|
||||
return false
|
||||
}
|
||||
|
||||
notifications.add('danger', err as string, -1)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
async logout(): Promise<boolean> {
|
||||
async logout(router: Router) {
|
||||
|
||||
const notifications = useNotifications()
|
||||
|
||||
try {
|
||||
await axios.post('logout', '', {
|
||||
const result = await axios.post('logout', '', {
|
||||
headers: {
|
||||
'Authorization': 'Bearer '+useUser().token
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
await router.push({name: 'Login'})
|
||||
notifications.add('success','Logged out successfully')
|
||||
|
||||
this.$reset()
|
||||
|
||||
return true
|
||||
}
|
||||
catch(error) {
|
||||
if(error instanceof Error) {
|
||||
|
||||
notifications.add('danger', error.message, -1)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -231,7 +231,7 @@ function onToggleEdit() {
|
||||
}
|
||||
|
||||
watch(() => [route.params, route.name], ([newParam, newName], [oldParam, oldName]) => {
|
||||
if(newName === oldName && newParam !== oldParam) {
|
||||
if(newName === oldName && newParam?.toString() !== oldParam?.toString()) {
|
||||
state.fetchFromApi(route.params.id)
|
||||
createUser.value = false
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
|
||||
<div>
|
||||
<IndexSettingsModal
|
||||
id="indexSettingsModal"
|
||||
:left-column="settings.columnsSelected"
|
||||
@@ -13,8 +13,7 @@
|
||||
@search="store.setSimpleSearch($event)"
|
||||
/>
|
||||
|
||||
|
||||
<table class="table table-hover table-bordered mt-3 ">
|
||||
<table class="table table-hover mt-3 ">
|
||||
<thead>
|
||||
<tr>
|
||||
<th
|
||||
@@ -37,14 +36,13 @@
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="employee in rows" @click="router.push({name: 'Employees/Details', params: {id: employee.id}})">
|
||||
<transition-group name="list" tag="tbody">
|
||||
<tr v-for="employee in rows" :key="employee.id" @click="router.push({name: 'Employees/Details', params: {id: employee.id}})">
|
||||
<td v-for="col in settings.columnsSelected">
|
||||
{{ employee[col] }}
|
||||
{{ employee[col] }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</transition-group>
|
||||
</table>
|
||||
|
||||
<h5 v-show="rows.length === 0" class="text-muted">Keine Daten anzuzeigen...</h5>
|
||||
@@ -56,7 +54,7 @@
|
||||
:last-page="store.meta.last_page"
|
||||
@set-page="paginatorSetPage"
|
||||
/>
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
@@ -110,10 +108,26 @@ function paginatorSetPage(e : number){
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
<style lang="scss" scoped>
|
||||
|
||||
.list- {
|
||||
&enter-active,
|
||||
&leave-active {
|
||||
transition: all .2s ease
|
||||
}
|
||||
|
||||
&enter-from,
|
||||
&leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
&move {
|
||||
transition: all .2s ease
|
||||
}
|
||||
}
|
||||
|
||||
table thead tr:first-child th:first-child {
|
||||
width: 51px;
|
||||
min-width: 51px;
|
||||
}
|
||||
|
||||
table th {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<template lang="pug">
|
||||
|
||||
<div>
|
||||
//- p {{ state }}
|
||||
|
||||
MonthPicker(:selectedMonth="selectedMonth" :selectedYear="selectedYear" @getMonth="selectedMonth = $event" @getYear="selectedYear = $event")
|
||||
|
||||
Schedule( :startDate="new Date(selectedYear, selectedMonth)")
|
||||
|
||||
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<img src="/src/assets/logo.png">
|
||||
<div>
|
||||
<img src="/src/assets/logo.png">
|
||||
|
||||
<form class="m-auto" novalidate>
|
||||
<h1 class="h3 mb-4">Bitte einloggen</h1>
|
||||
@@ -38,17 +39,25 @@
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn btn-lg btn-success w-100 mb-5"
|
||||
class="btn btn-success w-100 mb-5"
|
||||
:class="{'opacity-75': userStore.loading || userStore.isLoggedIn}"
|
||||
@click.prevent="onClick"
|
||||
>
|
||||
Einloggen
|
||||
<span
|
||||
v-if="userStore.loading"
|
||||
class="spinner-border spinner-border-sm"
|
||||
role="status"
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
{{userStore.loading ? "Lade..." : "Einloggen"}}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUser } from '@/stores/user'
|
||||
import { reactive, computed, ref, watch } from 'vue'
|
||||
import { reactive, computed, watch } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import useVuelidate from '@vuelidate/core'
|
||||
import { helpers, required } from '@vuelidate/validators'
|
||||
@@ -61,11 +70,11 @@ const input = reactive({
|
||||
password: '',
|
||||
})
|
||||
|
||||
const valPasswordCorrect = () => {
|
||||
return apiResponse.value !== 'E_INVALID_AUTH_PASSWORD: Password mis-match'
|
||||
function valPasswordCorrect() {
|
||||
return userStore.errorMessage !== 'E_INVALID_AUTH_PASSWORD: Password mis-match'
|
||||
}
|
||||
const valUsernameCorrect = () => {
|
||||
return apiResponse.value !== 'E_INVALID_AUTH_UID: User not found'
|
||||
function valUsernameCorrect() {
|
||||
return userStore.errorMessage !== 'E_INVALID_AUTH_UID: User not found'
|
||||
}
|
||||
|
||||
const rules = computed(() => ({
|
||||
@@ -79,30 +88,20 @@ const rules = computed(() => ({
|
||||
}
|
||||
}))
|
||||
|
||||
const v$ = useVuelidate(rules, input)
|
||||
|
||||
watch(input, () => {
|
||||
apiResponse.value = ''
|
||||
userStore.errorMessage = ''
|
||||
v$.value.$reset()
|
||||
})
|
||||
|
||||
const v$ = useVuelidate(rules, input)
|
||||
|
||||
const apiResponse = ref('')
|
||||
userStore.$subscribe((mutation, state) => {
|
||||
// if(state.isLoggedIn) router.push({name: 'Home'})
|
||||
})
|
||||
|
||||
async function onClick() {
|
||||
if(!(await v$.value.$validate())) return
|
||||
|
||||
const result = await userStore.login(input.username, input.password)
|
||||
|
||||
console.log("back in login form: "+result)
|
||||
|
||||
switch(result) {
|
||||
case true:
|
||||
router.push({name: 'Home'})
|
||||
break;
|
||||
case false:
|
||||
break;
|
||||
default:
|
||||
apiResponse.value = result
|
||||
if(await v$.value.$validate()) {
|
||||
await userStore.login(input.username, input.password, router)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user