Correcao Sidebar Classico e Rail, Correcao Layout, Ajuste de Breakpoint para Tailwind, Ajuste AppTopbar, Ajuste Menu PopOver, Recriado Paleta de Cores, Inserido algumas animações leves, Reajuste Cor items NOVOS da tabela, Drawer Ajuda Corrigido no Logout, Whatsapp, sms, email, recursos extras
This commit is contained in:
@@ -15,212 +15,338 @@
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { computed, nextTick, onMounted, onUnmounted, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
|
||||
import Popover from 'primevue/popover'
|
||||
import Popover from 'primevue/popover';
|
||||
|
||||
import { sessionUser, sessionRole } from '@/app/session'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { useRoleGuard } from '@/composables/useRoleGuard'
|
||||
import { sessionUser, sessionRole } from '@/app/session';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { useRoleGuard } from '@/composables/useRoleGuard';
|
||||
import { useLayout } from '@/layout/composables/layout';
|
||||
import { useUserSettingsPersistence } from '@/composables/useUserSettingsPersistence';
|
||||
import { useConfiguratorBar } from '@/layout/composables/useConfiguratorBar';
|
||||
|
||||
const props = defineProps({
|
||||
variant: { type: String, default: 'sidebar' }
|
||||
})
|
||||
variant: { type: String, default: 'sidebar' }
|
||||
});
|
||||
|
||||
const router = useRouter()
|
||||
const pop = ref(null)
|
||||
const router = useRouter();
|
||||
const pop = ref(null);
|
||||
|
||||
const { role, canSee } = useRoleGuard()
|
||||
const { role, canSee } = useRoleGuard();
|
||||
|
||||
const { toggleDarkMode, isDarkTheme } = useLayout();
|
||||
const { init: initSettings, queuePatch } = useUserSettingsPersistence();
|
||||
const { toggle: toggleThemeBar } = useConfiguratorBar();
|
||||
|
||||
onMounted(() => initSettings());
|
||||
|
||||
function isDarkNow() {
|
||||
return document.documentElement.classList.contains('app-dark');
|
||||
}
|
||||
|
||||
async function toggleDarkAndPersist() {
|
||||
try {
|
||||
toggleDarkMode();
|
||||
await nextTick();
|
||||
const theme_mode = isDarkNow() ? 'dark' : 'light';
|
||||
await queuePatch({ theme_mode }, { flushNow: true });
|
||||
} catch (e) {
|
||||
console.error('[FooterPanel][theme] falhou:', e?.message || e);
|
||||
}
|
||||
}
|
||||
|
||||
const initials = computed(() => {
|
||||
const name = sessionUser.value?.user_metadata?.full_name || sessionUser.value?.email || ''
|
||||
const parts = String(name).trim().split(/\s+/).filter(Boolean)
|
||||
const a = parts[0]?.[0] || 'U'
|
||||
const b = parts.length > 1 ? parts[parts.length - 1][0] : ''
|
||||
return (a + b).toUpperCase()
|
||||
})
|
||||
const name = sessionUser.value?.user_metadata?.full_name || sessionUser.value?.email || '';
|
||||
const parts = String(name).trim().split(/\s+/).filter(Boolean);
|
||||
const a = parts[0]?.[0] || 'U';
|
||||
const b = parts.length > 1 ? parts[parts.length - 1][0] : '';
|
||||
return (a + b).toUpperCase();
|
||||
});
|
||||
|
||||
const label = computed(() => {
|
||||
const name = sessionUser.value?.user_metadata?.full_name
|
||||
return name || sessionUser.value?.email || 'Conta'
|
||||
})
|
||||
const name = sessionUser.value?.user_metadata?.full_name;
|
||||
return name || sessionUser.value?.email || 'Conta';
|
||||
});
|
||||
|
||||
const sublabel = computed(() => {
|
||||
const r = role.value || sessionRole.value
|
||||
if (!r) return 'Sessão'
|
||||
if (r === 'clinic_admin' || r === 'tenant_admin' || r === 'admin') return 'Administrador'
|
||||
if (r === 'therapist') return 'Terapeuta'
|
||||
if (r === 'portal_user' || r === 'patient') return 'Portal'
|
||||
return r
|
||||
})
|
||||
const r = role.value || sessionRole.value;
|
||||
if (!r) return 'Sessão';
|
||||
if (r === 'clinic_admin' || r === 'tenant_admin' || r === 'admin') return 'Administrador';
|
||||
if (r === 'therapist') return 'Terapeuta';
|
||||
if (r === 'portal_user' || r === 'patient') return 'Portal';
|
||||
return r;
|
||||
});
|
||||
|
||||
const avatarUrl = computed(() => sessionUser.value?.user_metadata?.avatar_url || null)
|
||||
const avatarUrl = computed(() => sessionUser.value?.user_metadata?.avatar_url || null);
|
||||
|
||||
function toggle (e) { pop.value?.toggle(e) }
|
||||
function close () { try { pop.value?.hide() } catch {} }
|
||||
|
||||
async function safePush (target, fallback) {
|
||||
try {
|
||||
const r = router.resolve(target)
|
||||
if (r?.matched?.length) return await router.push(target)
|
||||
} catch {}
|
||||
if (fallback) { try { return await router.push(fallback) } catch {} }
|
||||
return router.push('/')
|
||||
function toggle(e) {
|
||||
pop.value?.toggle(e);
|
||||
}
|
||||
function close() {
|
||||
try {
|
||||
pop.value?.hide();
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function goMyProfile () { close(); safePush({ name: 'account-profile' }, '/account/profile') }
|
||||
function goSecurity () { close(); safePush({ name: 'account-security' }, '/account/security') }
|
||||
function goSettings () {
|
||||
close()
|
||||
if (canSee('settings.view')) return safePush({ name: 'ConfiguracoesAgenda' }, '/admin/settings')
|
||||
return safePush({ name: 'portal-sessoes' }, '/portal')
|
||||
}
|
||||
async function signOut () {
|
||||
close()
|
||||
try { await supabase.auth.signOut() } catch {}
|
||||
finally { router.push('/auth/login') }
|
||||
async function safePush(target, fallback) {
|
||||
try {
|
||||
const r = router.resolve(target);
|
||||
if (r?.matched?.length) return await router.push(target);
|
||||
} catch {}
|
||||
if (fallback) {
|
||||
try {
|
||||
return await router.push(fallback);
|
||||
} catch {}
|
||||
}
|
||||
return router.push('/');
|
||||
}
|
||||
|
||||
defineExpose({ toggle })
|
||||
function goMyProfile() {
|
||||
close();
|
||||
safePush({ name: 'account-profile' }, '/account/profile');
|
||||
}
|
||||
function goSecurity() {
|
||||
close();
|
||||
safePush({ name: 'account-security' }, '/account/security');
|
||||
}
|
||||
function goSettings() {
|
||||
close();
|
||||
if (canSee('settings.view')) return safePush({ name: 'ConfiguracoesAgenda' }, '/admin/settings');
|
||||
return safePush({ name: 'portal-sessoes' }, '/portal');
|
||||
}
|
||||
function goMyPlan() {
|
||||
close();
|
||||
const r = role.value || sessionRole.value;
|
||||
if (r === 'clinic_admin' || r === 'tenant_admin' || r === 'admin') {
|
||||
return safePush({ name: 'admin-meu-plano' }, '/admin/meu-plano');
|
||||
}
|
||||
if (r === 'supervisor') {
|
||||
return safePush({ name: 'supervisor.meu-plano' }, '/supervisor/meu-plano');
|
||||
}
|
||||
if (r === 'editor') {
|
||||
return safePush({ name: 'editor-meu-plano' }, '/editor/meu-plano');
|
||||
}
|
||||
if (r === 'portal_user' || r === 'patient') {
|
||||
return safePush({ name: 'portal-meu-plano' }, '/portal/meu-plano');
|
||||
}
|
||||
return safePush({ name: 'therapist-meu-plano' }, '/therapist/meu-plano');
|
||||
}
|
||||
// ── Logout com confirmação ────────────────────────────────────
|
||||
const signingOut = ref(false);
|
||||
const dots = ref('.');
|
||||
let _signOutTimer = null;
|
||||
let _dotsTimer = null;
|
||||
|
||||
function initSignOut() {
|
||||
signingOut.value = true;
|
||||
dots.value = '.';
|
||||
_dotsTimer = setInterval(() => {
|
||||
dots.value = dots.value.length >= 3 ? '.' : dots.value + '.';
|
||||
}, 400);
|
||||
_signOutTimer = setTimeout(async () => {
|
||||
_clearSignOutTimers();
|
||||
try {
|
||||
await supabase.auth.signOut();
|
||||
} catch {
|
||||
} finally {
|
||||
router.push('/auth/login');
|
||||
}
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
function cancelSignOut() {
|
||||
_clearSignOutTimers();
|
||||
signingOut.value = false;
|
||||
}
|
||||
|
||||
function _clearSignOutTimers() {
|
||||
clearTimeout(_signOutTimer);
|
||||
clearInterval(_dotsTimer);
|
||||
_signOutTimer = null;
|
||||
_dotsTimer = null;
|
||||
}
|
||||
|
||||
onUnmounted(_clearSignOutTimers);
|
||||
|
||||
defineExpose({ toggle });
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- ── SIDEBAR: trigger + popover ── -->
|
||||
<template v-if="variant === 'sidebar'">
|
||||
<div class="sticky bottom-0 z-20 border-t border-[var(--surface-border)] bg-[var(--surface-card)]">
|
||||
<button
|
||||
type="button"
|
||||
class="w-full px-3 py-3 flex items-center gap-3 hover:bg-[var(--surface-ground)] transition-colors duration-150"
|
||||
@click="toggle"
|
||||
>
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="h-9 w-9 rounded-xl object-cover border border-[var(--surface-border)]" alt="avatar" />
|
||||
<div v-else class="h-9 w-9 rounded-xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center text-sm font-semibold">{{ initials }}</div>
|
||||
<div class="min-w-0 flex-1 text-left">
|
||||
<div class="truncate text-sm font-semibold text-[var(--text-color)]">{{ label }}</div>
|
||||
<div class="truncate text-xs text-[var(--text-color-secondary)]">{{ sublabel }}</div>
|
||||
</div>
|
||||
<i class="pi pi-angle-up text-xs opacity-40" />
|
||||
</button>
|
||||
<!-- ── SIDEBAR: trigger + popover ── -->
|
||||
<template v-if="variant === 'sidebar'">
|
||||
<div class="sticky bottom-0 z-20 border-t border-[var(--surface-border)] bg-[var(--surface-card)]">
|
||||
<button type="button" class="w-full px-3 py-3 flex items-center gap-3 hover:bg-[var(--surface-ground)] transition-colors duration-150" @click="toggle">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="h-9 w-9 rounded-xl object-cover border border-[var(--surface-border)]" alt="avatar" />
|
||||
<div v-else class="h-9 w-9 rounded-xl border border-[var(--surface-border)] bg-[var(--surface-ground)] grid place-items-center text-sm font-semibold">{{ initials }}</div>
|
||||
<div class="min-w-0 flex-1 text-left">
|
||||
<div class="truncate text-sm font-semibold text-[var(--text-color)]">{{ label }}</div>
|
||||
<div class="truncate text-xs text-[var(--text-color-secondary)]">{{ sublabel }}</div>
|
||||
</div>
|
||||
<i class="pi pi-angle-up text-xs opacity-40" />
|
||||
</button>
|
||||
|
||||
<Popover ref="pop" appendTo="body">
|
||||
<!-- conteúdo reutilizado via template inline -->
|
||||
<template v-if="true">
|
||||
<div class="w-[224px] overflow-hidden rounded-[inherit]">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2.5 px-3.5 pt-3.5 pb-3 bg-[var(--primary-color)]/[0.06] border-b border-[var(--surface-border)]">
|
||||
<div class="relative shrink-0">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-9 h-9 rounded-[10px] object-cover ring-[1.5px] ring-[var(--primary-color)]/30" alt="avatar" />
|
||||
<div v-else class="w-9 h-9 rounded-[10px] grid place-items-center text-[1rem] font-bold text-[var(--primary-color)] bg-[var(--primary-color)]/10 ring-[1.5px] ring-[var(--primary-color)]/20">{{ initials }}</div>
|
||||
<span class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full bg-green-500 border-2 border-[var(--surface-card)]" />
|
||||
</div>
|
||||
<div class="min-w-0 flex flex-col gap-px">
|
||||
<span class="text-[1rem] font-bold text-[var(--text-color)] truncate tracking-tight">{{ label }}</span>
|
||||
<span class="text-[0.85rem] text-[var(--text-color-secondary)] truncate opacity-70">{{ sessionUser?.email }}</span>
|
||||
</div>
|
||||
<Popover ref="pop" appendTo="body">
|
||||
<!-- conteúdo reutilizado via template inline -->
|
||||
<template v-if="true">
|
||||
<div class="w-[224px] overflow-hidden rounded-[inherit]">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2.5 px-3.5 pt-3.5 pb-3 bg-[var(--primary-color)]/[0.06] border-b border-[var(--surface-border)]">
|
||||
<div class="relative shrink-0">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-9 h-9 rounded-[10px] object-cover ring-[1.5px] ring-[var(--primary-color)]/30" alt="avatar" />
|
||||
<div v-else class="w-9 h-9 rounded-[10px] grid place-items-center text-[1rem] font-bold text-[var(--primary-color)] bg-[var(--primary-color)]/10 ring-[1.5px] ring-[var(--primary-color)]/20">{{ initials }}</div>
|
||||
<span class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full bg-green-500 border-2 border-[var(--surface-card)]" />
|
||||
</div>
|
||||
<div class="min-w-0 flex flex-col gap-px">
|
||||
<span class="text-[1rem] font-bold text-[var(--text-color)] truncate tracking-tight">{{ label }}</span>
|
||||
<span class="text-[0.85rem] text-[var(--text-color-secondary)] truncate opacity-70">{{ sessionUser?.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nav items -->
|
||||
<div class="py-1.5">
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goMyProfile">
|
||||
<i class="pi pi-user text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu perfil
|
||||
</button>
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goMyPlan">
|
||||
<i class="pi pi-credit-card text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu Plano
|
||||
</button>
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goSecurity">
|
||||
<i class="pi pi-shield text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Segurança
|
||||
</button>
|
||||
<button
|
||||
v-if="canSee('settings.view')"
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSettings"
|
||||
>
|
||||
<i class="pi pi-cog text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Configurações
|
||||
</button>
|
||||
<div class="my-1 border-t border-[var(--surface-border)]" />
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="toggleDarkAndPersist">
|
||||
<i :class="['pi', isDarkTheme ? 'pi-sun' : 'pi-moon', 'text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150']" />
|
||||
{{ isDarkTheme ? 'Modo claro' : 'Modo escuro' }}
|
||||
</button>
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="toggleThemeBar">
|
||||
<i class="pi pi-palette text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Cores do tema
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: Sair -->
|
||||
<div class="border-t border-[var(--surface-border)] py-1.5">
|
||||
<button v-if="!signingOut" class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-red-500 hover:bg-red-500/[0.06] hover:pl-4 transition-all duration-150" @click="initSignOut">
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
Sair
|
||||
</button>
|
||||
<div v-else class="flex items-center gap-2 w-full px-3.5 py-[7px]">
|
||||
<span class="flex items-center gap-2.5 flex-1 text-[1rem] font-medium text-red-400 select-none">
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60" />
|
||||
Saindo{{ dots }}
|
||||
</span>
|
||||
<button class="shrink-0 text-[0.72rem] text-[var(--text-color-secondary)] hover:text-[var(--text-color)] border border-[var(--surface-border)] rounded px-2 py-0.5 transition-colors duration-150" @click="cancelSignOut">
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ── RAIL: só o popover, trigger externo ── -->
|
||||
<template v-else>
|
||||
<Popover ref="pop" appendTo="body">
|
||||
<div class="w-[224px] overflow-hidden rounded-[inherit]">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2.5 px-3.5 pt-3.5 pb-3 bg-[var(--primary-color)]/[0.06] border-b border-[var(--surface-border)]">
|
||||
<div class="relative shrink-0">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-9 h-9 rounded-[10px] object-cover ring-[1.5px] ring-[var(--primary-color)]/30" alt="avatar" />
|
||||
<div v-else class="w-9 h-9 rounded-[10px] grid place-items-center text-[1rem] font-bold text-[var(--primary-color)] bg-[var(--primary-color)]/10 ring-[1.5px] ring-[var(--primary-color)]/20">{{ initials }}</div>
|
||||
<span class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full bg-green-500 border-2 border-[var(--surface-card)]" />
|
||||
</div>
|
||||
<div class="min-w-0 flex flex-col gap-px">
|
||||
<span class="text-[1rem] font-bold text-[var(--text-color)] truncate tracking-tight">{{ label }}</span>
|
||||
<span class="text-[0.85rem] text-[var(--text-color-secondary)] truncate opacity-70">{{ sessionUser?.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nav items -->
|
||||
<div class="py-1.5">
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goMyProfile">
|
||||
<i class="pi pi-user text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu perfil
|
||||
</button>
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goMyPlan">
|
||||
<i class="pi pi-credit-card text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu Plano
|
||||
</button>
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="goSecurity">
|
||||
<i class="pi pi-shield text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Segurança
|
||||
</button>
|
||||
<button
|
||||
v-if="canSee('settings.view')"
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSettings"
|
||||
>
|
||||
<i class="pi pi-cog text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Configurações
|
||||
</button>
|
||||
<div class="my-1 border-t border-[var(--surface-border)]" />
|
||||
<button class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150" @click="toggleDarkAndPersist">
|
||||
<i :class="['pi', isDarkTheme ? 'pi-sun' : 'pi-moon', 'text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150']" />
|
||||
{{ isDarkTheme ? 'Modo claro' : 'Modo escuro' }}
|
||||
</button>
|
||||
<div class="relative footer-theme-panel">
|
||||
<button
|
||||
type="button"
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
v-styleclass="{
|
||||
selector: '@next',
|
||||
enterFromClass: 'hidden',
|
||||
enterActiveClass: 'p-anchored-overlay-enter-active',
|
||||
leaveToClass: 'hidden',
|
||||
leaveActiveClass: 'p-anchored-overlay-leave-active',
|
||||
hideOnOutsideClick: true
|
||||
}"
|
||||
>
|
||||
<i class="pi pi-palette text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Cores do tema
|
||||
</button>
|
||||
<AppConfigurator />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer: Sair -->
|
||||
<div class="border-t border-[var(--surface-border)] py-1.5">
|
||||
<button v-if="!signingOut" class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-red-500 hover:bg-red-500/[0.06] hover:pl-4 transition-all duration-150" @click="initSignOut">
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
Sair
|
||||
</button>
|
||||
<div v-else class="flex items-center gap-2 w-full px-3.5 py-[7px]">
|
||||
<span class="flex items-center gap-2.5 flex-1 text-[1rem] font-medium text-red-400 select-none">
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60" />
|
||||
Saindo{{ dots }}
|
||||
</span>
|
||||
<button class="shrink-0 text-[0.72rem] text-[var(--text-color-secondary)] hover:text-[var(--text-color)] border border-[var(--surface-border)] rounded px-2 py-0.5 transition-colors duration-150" @click="cancelSignOut">
|
||||
Cancelar
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nav items -->
|
||||
<div class="py-1.5">
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goMyProfile"
|
||||
>
|
||||
<i class="pi pi-user text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu perfil
|
||||
</button>
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSecurity"
|
||||
>
|
||||
<i class="pi pi-shield text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Segurança
|
||||
</button>
|
||||
<button
|
||||
v-if="canSee('settings.view')"
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSettings"
|
||||
>
|
||||
<i class="pi pi-cog text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Configurações
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: Sair -->
|
||||
<div class="border-t border-[var(--surface-border)] py-1.5">
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-red-500 hover:bg-red-500/[0.06] hover:pl-4 transition-all duration-150"
|
||||
@click="signOut"
|
||||
>
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
Sair
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- ── RAIL: só o popover, trigger externo ── -->
|
||||
<template v-else>
|
||||
<Popover ref="pop" appendTo="body">
|
||||
<div class="w-[224px] overflow-hidden rounded-[inherit]">
|
||||
<!-- Header -->
|
||||
<div class="flex items-center gap-2.5 px-3.5 pt-3.5 pb-3 bg-[var(--primary-color)]/[0.06] border-b border-[var(--surface-border)]">
|
||||
<div class="relative shrink-0">
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-9 h-9 rounded-[10px] object-cover ring-[1.5px] ring-[var(--primary-color)]/30" alt="avatar" />
|
||||
<div v-else class="w-9 h-9 rounded-[10px] grid place-items-center text-[1rem] font-bold text-[var(--primary-color)] bg-[var(--primary-color)]/10 ring-[1.5px] ring-[var(--primary-color)]/20">{{ initials }}</div>
|
||||
<span class="absolute -bottom-0.5 -right-0.5 w-2.5 h-2.5 rounded-full bg-green-500 border-2 border-[var(--surface-card)]" />
|
||||
</div>
|
||||
<div class="min-w-0 flex flex-col gap-px">
|
||||
<span class="text-[1rem] font-bold text-[var(--text-color)] truncate tracking-tight">{{ label }}</span>
|
||||
<span class="text-[0.85rem] text-[var(--text-color-secondary)] truncate opacity-70">{{ sessionUser?.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nav items -->
|
||||
<div class="py-1.5">
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goMyProfile"
|
||||
>
|
||||
<i class="pi pi-user text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Meu perfil
|
||||
</button>
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSecurity"
|
||||
>
|
||||
<i class="pi pi-shield text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Segurança
|
||||
</button>
|
||||
<button
|
||||
v-if="canSee('settings.view')"
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-[var(--text-color)] hover:bg-[var(--surface-ground)] hover:pl-4 transition-all duration-150"
|
||||
@click="goSettings"
|
||||
>
|
||||
<i class="pi pi-cog text-[0.72rem] opacity-40 group-hover:opacity-100 group-hover:text-[var(--primary-color)] transition-all duration-150" />
|
||||
Configurações
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Footer: Sair -->
|
||||
<div class="border-t border-[var(--surface-border)] py-1.5">
|
||||
<button
|
||||
class="group flex items-center gap-2.5 w-full px-3.5 py-[7px] text-[1rem] font-medium text-red-500 hover:bg-red-500/[0.06] hover:pl-4 transition-all duration-150"
|
||||
@click="signOut"
|
||||
>
|
||||
<i class="pi pi-sign-out text-[0.72rem] opacity-60 group-hover:opacity-100 transition-opacity duration-150" />
|
||||
Sair
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Popover>
|
||||
</template>
|
||||
</Popover>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
/* Zero padding nativo do PrimeVue — mínimo inevitável pois não há prop pra isso */
|
||||
.p-popover-content { padding: 0 !important; }
|
||||
</style>
|
||||
.p-popover-content {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user