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>
<q-header class="bg-darker" color="white" elevated>
<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="/">
<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>
@ -9,74 +9,40 @@
</router-link>
<q-toolbar-title shrink class="text-weight-bold"> Salix </q-toolbar-title>
<q-space></q-space>
<q-btn dense flat no-wrap>
<q-avatar size="lg">
<q-img
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
<q-tooltip>Account</q-tooltip>
<q-icon name="arrow_drop_down" size="s" />
<q-menu>
<div class="row no-wrap q-pa-md">
<div class="column">
<div class="text-h6 q-mb-md">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>
</q-btn>
<div class="q-pl-sm q-gutter-sm row items-center no-wrap">
<q-btn v-if="$q.screen.gt.xs" dense flat size="md" icon="add">
<q-icon name="arrow_drop_down" size="s" />
<q-menu>
<q-list style="min-width: 150px">
<q-item clickable>
<q-item-section>New customer</q-item-section>
</q-item>
<q-item clickable>
<q-item-section>New ticket</q-item-section>
</q-item>
</q-list>
</q-menu>
</q-btn>
<q-btn v-if="$q.screen.gt.xs" dense flat round size="md" icon="notifications" />
<q-btn dense flat no-wrap>
<q-avatar size="lg">
<q-img
:src="`/api/Images/user/160x160/${user.id}/download?access_token=${token}`"
spinner-color="white"
/>
</q-avatar>
<q-tooltip>Account</q-tooltip>
<q-icon name="arrow_drop_down" size="s" />
<UserPanel />
</q-btn>
</div>
</q-toolbar>
</q-header>
</template>
<script lang="ts">
import { Options, Vue } from 'vue-class-component';
import axios from 'axios';
import { Dark } from 'quasar';
import UserPanel from '@/components/UserPanel.vue';
interface UserProfile {
id: number;
@ -86,38 +52,11 @@ interface UserProfile {
}
@Options({
props: {
drawer: {
type: Boolean,
required: true,
},
components: {
UserPanel,
},
})
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 {
return this.$store.state.user;
}
@ -125,22 +64,5 @@ export default class Topbar extends Vue {
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,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"
}
},
"errors": {
"statusUnauthorized": "Access denied",
"statusInternalServerError": "An internal server error has ocurred"
},
"login": {
"title": "Login",
"username": "Username",
@ -16,7 +20,10 @@
},
"customer": {},
"components": {
"dialog": {},
"calendar": {}
"topbar": {},
"userPanel": {
"settings": "Settings",
"logOut": "Log Out"
}
}
}

View File

@ -3,9 +3,11 @@
"lang": {
"es": "Español",
"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": {
"title": "Iniciar sesión",
@ -18,7 +20,10 @@
},
"customer": {},
"components": {
"dialog": {},
"calendar": {}
"topbar": {},
"userPanel": {
"settings": "Configuración",
"logOut": "Cerrar sesión"
}
}
}

View File

@ -5,8 +5,13 @@ import store from './store';
import { Quasar } from 'quasar';
import quasarUserOptions from './quasar-user-options';
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';
axios.interceptors.request.use(

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

View File

@ -10,22 +10,23 @@ const routes: Array<RouteRecordRaw> = [
{
path: '/',
name: 'Main',
component: () => import('../views/Main/Main.vue'),
component: () => import('../views/Layout/Main.vue'),
redirect: { name: 'Dashboard' },
children: [
{
path: '/dashboard',
name: 'Dashboard',
component: () => import('../views/Main/Dashboard.vue'),
component: () => import('../views/Dashboard/Dashboard.vue'),
},
{
path: '/customer',
name: 'Customer',
component: () => import('../views/Customer/Main.vue'),
component: () => import('../views/Customer/Customer.vue'),
},
{
path: '/:pathMatch(.*)*',
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' {
interface ComponentCustomProperties {
$store: Store;
$filters: Record<string, T>;
}
}

View File

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

View File

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