263 lines
6.9 KiB
Vue
263 lines
6.9 KiB
Vue
<template>
|
|
<q-layout view="lHh Lpr lFf">
|
|
<q-header elevated>
|
|
<q-toolbar>
|
|
<img data-v-2373a833="" class="brand-logo-tb" :src="LogoUrl" alt="Omnimed logo">
|
|
<q-toolbar-title> Omnimed </q-toolbar-title>
|
|
|
|
<div>Quasar v{{ $q.version }}</div>
|
|
<div class="q-ml-md">
|
|
<q-btn
|
|
v-if="!currentUser"
|
|
flat
|
|
round
|
|
color="white"
|
|
icon="lock"
|
|
to="/login"
|
|
>
|
|
<q-tooltip>Login</q-tooltip>
|
|
</q-btn>
|
|
|
|
<q-btn v-else flat round dense>
|
|
<q-avatar size="34px" class="user-avatar">
|
|
<img v-if="currentUser.avatar" :src="currentUser.avatar" :alt="currentUser.name" />
|
|
<span v-else>{{ userInitials }}</span>
|
|
</q-avatar>
|
|
|
|
<q-menu anchor="bottom right" self="top right">
|
|
<q-list dense style="min-width: 190px">
|
|
<q-item>
|
|
<q-item-section>
|
|
<q-item-label>{{ currentUser.name }}</q-item-label>
|
|
<q-item-label caption>{{ currentUser.email }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
|
|
<q-separator />
|
|
|
|
<q-item v-if="isAdmin" clickable v-close-popup to="/admin">
|
|
<q-item-section avatar>
|
|
<q-icon name="admin_panel_settings" />
|
|
</q-item-section>
|
|
<q-item-section>Admin</q-item-section>
|
|
</q-item>
|
|
|
|
<q-item clickable v-close-popup @click="logout">
|
|
<q-item-section avatar>
|
|
<q-icon name="logout" />
|
|
</q-item-section>
|
|
<q-item-section>Logout</q-item-section>
|
|
</q-item>
|
|
</q-list>
|
|
</q-menu>
|
|
</q-btn>
|
|
</div>
|
|
<div class="q-ml-md">
|
|
<q-select
|
|
v-model="model"
|
|
:options="options"
|
|
option-label="label"
|
|
option-value="value"
|
|
emit-value
|
|
map-options
|
|
class="q-select"
|
|
dark
|
|
>
|
|
<template v-slot:selected-item="scope">
|
|
<div class="row items-center no-wrap">
|
|
<img
|
|
class="border"
|
|
v-if="scope.opt.flagSrc"
|
|
:src="scope.opt.flagSrc"
|
|
:alt="scope.opt.label"
|
|
:style="scope.opt.flagStyle"
|
|
/>
|
|
<span v-else class="lang-fallback">{{ scope.opt.value.toUpperCase() }}</span>
|
|
<span class="bold text-white q-ml-md">{{ scope.opt.short_name }}</span>
|
|
</div>
|
|
</template>
|
|
<template v-slot:option="scope">
|
|
<q-item v-bind="scope.itemProps">
|
|
<q-item-section avatar>
|
|
<img
|
|
v-if="scope.opt.flagSrc"
|
|
:src="scope.opt.flagSrc"
|
|
:alt="scope.opt.label"
|
|
:style="scope.opt.flagStyle"
|
|
/>
|
|
<span v-else class="lang-fallback">{{ scope.opt.value.toUpperCase() }}</span>
|
|
</q-item-section>
|
|
<q-item-section>
|
|
<q-item-label>{{ scope.opt.label }}</q-item-label>
|
|
</q-item-section>
|
|
</q-item>
|
|
</template>
|
|
</q-select>
|
|
</div>
|
|
</q-toolbar>
|
|
</q-header>
|
|
|
|
<q-page-container>
|
|
<router-view />
|
|
</q-page-container>
|
|
</q-layout>
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from 'vue';
|
|
import { useI18n } from 'vue-i18n';
|
|
import { useRouter, useRoute } from 'vue-router';
|
|
import LogoUrl from 'src/assets/home/logo.png';
|
|
import { me, type UserShort } from 'src/api/api';
|
|
import { usePreferencesStore, type LanguageCode } from 'src/stores/preferences-store';
|
|
|
|
const { t } = useI18n();
|
|
const router = useRouter();
|
|
const route = useRoute();
|
|
const preferencesStore = usePreferencesStore();
|
|
const currentUser = ref<UserShort | null>(null);
|
|
const model = computed({
|
|
get: () => preferencesStore.language,
|
|
set: (language: LanguageCode) => {
|
|
preferencesStore.setLanguage(language);
|
|
},
|
|
});
|
|
const flagModules = import.meta.glob('../assets/flags/*', {
|
|
eager: true,
|
|
import: 'default',
|
|
});
|
|
|
|
const langs = [
|
|
{
|
|
code: 'it',
|
|
short_name: 'IT',
|
|
flag: 'it.svg',
|
|
style: 'width:32px;height:22px;',
|
|
},
|
|
{
|
|
code: 'en',
|
|
short_name: 'EN',
|
|
flag: 'en.svg',
|
|
style: 'width:32px;height:22px;',
|
|
},
|
|
{
|
|
code: 'en_us',
|
|
short_name: 'EN',
|
|
flag: 'en_us.svg',
|
|
style: 'width:32px;height:22px;',
|
|
},
|
|
{
|
|
code: 'de',
|
|
short_name: 'DE',
|
|
flag: 'de.svg',
|
|
style: 'width:32px;height:22px;',
|
|
},
|
|
{
|
|
code: 'de_ch',
|
|
short_name: 'DE',
|
|
flag: 'ch.svg',
|
|
style: 'width:22px;height:22px;object-fit: cover;',
|
|
},
|
|
{
|
|
code: 'fr',
|
|
short_name: 'FR',
|
|
flag: 'fr.svg',
|
|
style: 'width:32px;height:22px;',
|
|
},
|
|
{
|
|
code: 'fr_ch',
|
|
short_name: 'FR',
|
|
flag: 'ch.svg',
|
|
style: 'width:22px;height:22px;object-fit: cover;',
|
|
},
|
|
];
|
|
|
|
const options = computed(() =>
|
|
langs.map((lang) => ({
|
|
label: t(`language.${lang.code}`),
|
|
value: lang.code,
|
|
flagSrc: flagModules[`../assets/flags/${lang.flag}`] ?? null,
|
|
flagStyle: lang.style,
|
|
short_name: lang.short_name,
|
|
})),
|
|
);
|
|
const isAdmin = computed(() => currentUser.value?.roles.includes('admin') ?? false);
|
|
const userInitials = computed(() => {
|
|
const source = currentUser.value?.name?.trim() || currentUser.value?.email?.trim() || '?';
|
|
const parts = source.split(/\s+/).filter(Boolean);
|
|
const first = parts[0] ?? '';
|
|
const second = parts[1] ?? '';
|
|
return parts.length > 1
|
|
? `${first.charAt(0)}${second.charAt(0)}`.toUpperCase()
|
|
: first.slice(0, 2).toUpperCase();
|
|
});
|
|
|
|
onMounted(async () => {
|
|
await loadCurrentUser();
|
|
});
|
|
|
|
watch(
|
|
() => route.fullPath,
|
|
async () => {
|
|
await loadCurrentUser();
|
|
},
|
|
);
|
|
|
|
async function loadCurrentUser(): Promise<void> {
|
|
if (typeof window === 'undefined' || !window.localStorage.getItem('Auth-Token')) {
|
|
currentUser.value = null;
|
|
return;
|
|
}
|
|
|
|
const response = await me();
|
|
currentUser.value = response.error ? null : response.data;
|
|
}
|
|
|
|
async function logout(): Promise<void> {
|
|
if (typeof window !== 'undefined') {
|
|
window.localStorage.removeItem('Auth-Token');
|
|
}
|
|
currentUser.value = null;
|
|
await router.push('/');
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.lang-fallback {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
min-width: 32px;
|
|
height: 22px;
|
|
padding: 0 4px;
|
|
border: 1px solid currentColor;
|
|
border-radius: 4px;
|
|
font-size: 10px;
|
|
line-height: 1;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.border {
|
|
border: 1px solid #fff;
|
|
border-radius: 4px;
|
|
}
|
|
|
|
.q-select {
|
|
i.q-icon {
|
|
color: white !important;
|
|
}
|
|
}
|
|
|
|
.user-avatar {
|
|
background: linear-gradient(135deg, #0d47a1, #26a69a);
|
|
color: white;
|
|
font-size: 0.78rem;
|
|
font-weight: 700;
|
|
}
|
|
|
|
.brand-logo-tb{
|
|
height: 42px;
|
|
width: auto
|
|
}
|
|
</style>
|