Header & userPanel componentization

This commit is contained in:
Joan Sanchez 2022-03-04 09:04:22 +01:00
parent 44b823e376
commit fade50dd2a
18 changed files with 204 additions and 146 deletions

View File

@ -1,7 +1,7 @@
<template> <template>
<q-header class="bg-darker" color="white" elevated> <q-header class="bg-darker" color="white" elevated>
<q-toolbar class="q-py-sm q-px-md"> <q-toolbar class="q-py-sm q-px-md">
<q-btn flat @click="drawer = !drawer" round dense icon="menu" /> <q-btn flat @click="$emit('on-toggle-drawer')" round dense icon="menu" />
<router-link to="/"> <router-link to="/">
<q-btn flat round class="q-ml-xs" v-if="$q.screen.gt.xs"> <q-btn flat round class="q-ml-xs" v-if="$q.screen.gt.xs">
<q-avatar square size="md"><img src="@/assets/logo_icon.svg" alt="Logo" /></q-avatar> <q-avatar square size="md"><img src="@/assets/logo_icon.svg" alt="Logo" /></q-avatar>
@ -9,74 +9,40 @@
</router-link> </router-link>
<q-toolbar-title shrink class="text-weight-bold"> Salix </q-toolbar-title> <q-toolbar-title shrink class="text-weight-bold"> Salix </q-toolbar-title>
<q-space></q-space> <q-space></q-space>
<q-btn dense flat no-wrap> <div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<q-avatar size="lg"> <q-btn v-if="$q.screen.gt.xs" dense flat size="md" icon="add">
<q-img <q-icon name="arrow_drop_down" size="s" />
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`" <q-menu>
spinner-color="white" <q-list style="min-width: 150px">
/> <q-item clickable>
</q-avatar> <q-item-section>New customer</q-item-section>
<q-tooltip>Account</q-tooltip> </q-item>
<q-icon name="arrow_drop_down" size="s" /> <q-item clickable>
<q-item-section>New ticket</q-item-section>
<q-menu> </q-item>
<div class="row no-wrap q-pa-md"> </q-list>
<div class="column"> </q-menu>
<div class="text-h6 q-mb-md">Settings</div> </q-btn>
<q-toggle <q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications" />
:label="$t(`globals.lang['${language}']`)" <q-btn dense flat no-wrap>
icon="public" <q-avatar size="lg">
color="orange" <q-img
false-value="es" :src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
true-value="en" spinner-color="white"
v-model="language" />
/> </q-avatar>
<q-toggle <q-tooltip>Account</q-tooltip>
v-model="mode" <q-icon name="arrow_drop_down" size="s" />
checked-icon="dark_mode" <UserPanel />
color="orange" </q-btn>
unchecked-icon="light_mode" </div>
/>
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
</div>
<q-separator vertical inset class="q-mx-lg" />
<div class="column items-center" style="min-width: 150px">
<q-avatar size="80px">
<q-img
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
<div class="text-subtitle1 q-mt-md">
<strong>{{ user.nickname }}</strong>
</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
<q-btn
color="orange"
flat
label="Log Out"
size="sm"
icon="logout"
@click="logout()"
v-close-popup
/>
</div>
</div>
</q-menu>
</q-btn>
</q-toolbar> </q-toolbar>
</q-header> </q-header>
</template> </template>
<script lang="ts"> <script lang="ts">
import { Options, Vue } from 'vue-class-component'; import { Options, Vue } from 'vue-class-component';
import axios from 'axios'; import UserPanel from '@/components/UserPanel.vue';
import { Dark } from 'quasar';
interface UserProfile { interface UserProfile {
id: number; id: number;
@ -86,38 +52,11 @@ interface UserProfile {
} }
@Options({ @Options({
props: { components: {
drawer: { UserPanel,
type: Boolean,
required: true,
},
}, },
}) })
export default class Topbar extends Vue { export default class Topbar extends Vue {
drawer?: boolean;
private lang = 'es';
mounted(): void {
axios
.get('/api/accounts/acl')
.then((response) => {
this.$store.dispatch('updateUserData', response.data);
})
.catch(() => {
this.$q.notify({
message: this.$t('globals.accessDenied'),
type: 'negative',
});
this.$store.dispatch('logOut');
this.$router.push('/login');
});
}
logout(): void {
this.$store.dispatch('logOut');
this.$router.push('/login');
}
get user(): UserProfile { get user(): UserProfile {
return this.$store.state.user; return this.$store.state.user;
} }
@ -125,22 +64,5 @@ export default class Topbar extends Vue {
get token(): string { get token(): string {
return this.$store.getters.token; return this.$store.getters.token;
} }
get mode(): boolean {
return Dark.isActive;
}
set mode(value: boolean) {
Dark.set(value);
}
get language(): string {
return this.lang;
}
set language(value: string) {
this.lang = value;
this.$i18n.locale = value;
}
} }
</script> </script>

View File

@ -0,0 +1,102 @@
<template>
<q-menu>
<div class="row no-wrap q-pa-md">
<div class="column">
<div class="text-h6 q-mb-md">{{ $t('components.userPanel.settings') }}</div>
<q-toggle
:label="$t(`globals.lang['${language}']`)"
icon="public"
color="orange"
false-value="es"
true-value="en"
v-model="language"
/>
<q-toggle v-model="mode" checked-icon="dark_mode" color="orange" unchecked-icon="light_mode" />
<q-btn color="orange" outline size="sm" label="Settings" icon="settings" />
</div>
<q-separator vertical inset class="q-mx-lg" />
<div class="column items-center" style="min-width: 150px">
<q-avatar size="80px">
<q-img
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
<div class="text-subtitle1 q-mt-md">
<strong>{{ user.nickname }}</strong>
</div>
<div class="text-subtitle3 text-grey-7 q-mb-xs">@{{ user.username }}</div>
<q-btn color="orange" flat label="Log Out" size="sm" icon="logout" @click="logout()" v-close-popup />
</div>
</div>
</q-menu>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import axios from 'axios';
import { Dark } from 'quasar';
interface UserProfile {
id: number;
username: string;
nickname: string;
token: string;
}
@Options({})
export default class UserPanel extends Vue {
private lang = 'es';
mounted(): void {
axios
.get('/api/accounts/acl')
.then((response) => {
this.$store.dispatch('updateUserData', response.data);
})
.catch(() => {
this.$q.notify({
message: this.$t('errors.statusUnauthorized'),
type: 'negative',
});
this.$store.dispatch('logOut');
this.$router.push('/login');
});
}
logout(): void {
this.$store.dispatch('logOut');
this.$router.push('/login');
}
get user(): UserProfile {
return this.$store.state.user;
}
get token(): string {
return this.$store.getters.token;
}
get mode(): boolean {
return Dark.isActive;
}
set mode(value: boolean) {
Dark.set(value);
}
get language(): string {
return this.lang;
}
set language(value: string) {
this.lang = value;
this.$i18n.locale = value;
}
}
</script>

View File

@ -0,0 +1,5 @@
import toLowerCase from './toLowerCase';
export default {
toLowerCase,
};

View File

@ -0,0 +1,3 @@
export default function toLowerCase(value: string): string {
return value.toLowerCase();
}

0
src/core/helpers/Auth.ts Normal file
View File

View File

@ -5,6 +5,10 @@
"en": "English" "en": "English"
} }
}, },
"errors": {
"statusUnauthorized": "Access denied",
"statusInternalServerError": "An internal server error has ocurred"
},
"login": { "login": {
"title": "Login", "title": "Login",
"username": "Username", "username": "Username",
@ -16,7 +20,10 @@
}, },
"customer": {}, "customer": {},
"components": { "components": {
"dialog": {}, "topbar": {},
"calendar": {} "userPanel": {
"settings": "Settings",
"logOut": "Log Out"
}
} }
} }

View File

@ -3,9 +3,11 @@
"lang": { "lang": {
"es": "Español", "es": "Español",
"en": "Inglés" "en": "Inglés"
}, }
"accessDenied": "Acceso denegado", },
"internalServerError": "Ha ocurrido un error interno del servidor" "errors": {
"statusUnauthorized": "Acceso denegado",
"statusInternalServerError": "Ha ocurrido un error interno del servidor"
}, },
"login": { "login": {
"title": "Iniciar sesión", "title": "Iniciar sesión",
@ -18,7 +20,10 @@
}, },
"customer": {}, "customer": {},
"components": { "components": {
"dialog": {}, "topbar": {},
"calendar": {} "userPanel": {
"settings": "Configuración",
"logOut": "Cerrar sesión"
}
} }
} }

View File

@ -5,8 +5,13 @@ import store from './store';
import { Quasar } from 'quasar'; import { Quasar } from 'quasar';
import quasarUserOptions from './quasar-user-options'; import quasarUserOptions from './quasar-user-options';
import i18n from './i18n'; import i18n from './i18n';
import filters from './core/filters';
createApp(App).use(i18n).use(Quasar, quasarUserOptions).use(store).use(router).mount('#app'); const app = createApp(App).use(i18n).use(Quasar, quasarUserOptions).use(store).use(router);
app.config.globalProperties.$filters = filters;
app.mount('#app');
import axios from 'axios'; import axios from 'axios';
axios.interceptors.request.use( axios.interceptors.request.use(

0
src/router/customer.ts Normal file
View File

View File

@ -10,22 +10,23 @@ const routes: Array<RouteRecordRaw> = [
{ {
path: '/', path: '/',
name: 'Main', name: 'Main',
component: () => import('../views/Main/Main.vue'), component: () => import('../views/Layout/Main.vue'),
redirect: { name: 'Dashboard' },
children: [ children: [
{ {
path: '/dashboard', path: '/dashboard',
name: 'Dashboard', name: 'Dashboard',
component: () => import('../views/Main/Dashboard.vue'), component: () => import('../views/Dashboard/Dashboard.vue'),
}, },
{ {
path: '/customer', path: '/customer',
name: 'Customer', name: 'Customer',
component: () => import('../views/Customer/Main.vue'), component: () => import('../views/Customer/Customer.vue'),
}, },
{ {
path: '/:pathMatch(.*)*', path: '/:pathMatch(.*)*',
name: 'NotFound', name: 'NotFound',
component: () => import('../views/Main/NotFound.vue'), component: () => import('../views/Layout/NotFound.vue'),
}, },
], ],
}, },

0
src/router/invoiceIn.ts Normal file
View File

1
src/shims-vuex.d.ts vendored
View File

@ -3,5 +3,6 @@ import { Store } from '@/store'; // path to store file
declare module '@vue/runtime-core' { declare module '@vue/runtime-core' {
interface ComponentCustomProperties { interface ComponentCustomProperties {
$store: Store; $store: Store;
$filters: Record<string, T>;
} }
} }

View File

@ -1,9 +1,7 @@
<template> <template>
<q-layout> <q-page class="q-pa-md">
<q-page class="q-pa-md"> <q-card class="q-pa-md"> Customer page.. </q-card>
<q-card class="q-pa-md"> Dashboard page.. </q-card> </q-page>
</q-page>
</q-layout>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -0,0 +1,21 @@
<template>
<q-page class="q-pa-md">
<q-card class="q-pa-md">
Dashboard page..
{{ $filters.toLowerCase('HELLO WORLD') }}
</q-card>
</q-page>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
@Options({})
export default class Dashboard extends Vue {
get whatever(): string {
return this.$filters.toLowerCase('HELLO WORLD');
}
}
</script>
<style lang="scss" scoped></style>

View File

@ -1,6 +1,6 @@
<template> <template>
<q-layout> <q-layout view="hHh lpR fFf">
<Topbar :drawer="drawer" /> <Topbar @on-toggle-drawer="onToggleDrawer()" />
<q-drawer <q-drawer
v-model="drawer" v-model="drawer"
show-if-above show-if-above
@ -71,6 +71,10 @@ import Topbar from '@/components/Topbar.vue';
export default class Main extends Vue { export default class Main extends Vue {
drawer = false; drawer = false;
miniState = true; miniState = true;
onToggleDrawer(): void {
this.drawer = !this.drawer;
}
} }
</script> </script>

View File

@ -111,7 +111,7 @@ export default class Login extends Vue {
message: this.$t('login.loginSuccess'), message: this.$t('login.loginSuccess'),
type: 'positive', type: 'positive',
}); });
this.$router.push('/'); this.$router.push('/dashboard');
}) })
.catch((error) => { .catch((error) => {
const errorCode = error.response.status; const errorCode = error.response.status;
@ -122,7 +122,7 @@ export default class Login extends Vue {
}); });
} else { } else {
this.$q.notify({ this.$q.notify({
message: this.$t('globals.internalServerError'), message: this.$t('errors.statusInternalServerError'),
type: 'negative', type: 'negative',
}); });
} }

View File

@ -1,16 +0,0 @@
<template>
<q-layout>
<q-page class="q-pa-md">
<q-card class="q-pa-md"> Dashboard page.. </q-card>
</q-page>
</q-layout>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
@Options({})
export default class Dashboard extends Vue {}
</script>
<style lang="scss" scoped></style>