first commit
This commit is contained in:
189
src/layout/AppMenuFooterPanel.vue
Normal file
189
src/layout/AppMenuFooterPanel.vue
Normal file
@@ -0,0 +1,189 @@
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
import { sessionUser, sessionRole } from '@/app/session'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
const router = useRouter()
|
||||
const pop = ref(null)
|
||||
|
||||
function isAdminRole (r) {
|
||||
return r === 'admin' || r === 'tenant_admin'
|
||||
}
|
||||
|
||||
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 label = computed(() => {
|
||||
const name = sessionUser.value?.user_metadata?.full_name
|
||||
return name || sessionUser.value?.email || 'Conta'
|
||||
})
|
||||
|
||||
const sublabel = computed(() => {
|
||||
const r = sessionRole.value
|
||||
if (!r) return 'Sessão'
|
||||
if (isAdminRole(r)) return 'Administrador'
|
||||
if (r === 'therapist') return 'Terapeuta'
|
||||
if (r === 'patient') return 'Paciente'
|
||||
return r
|
||||
})
|
||||
|
||||
function toggle (e) {
|
||||
pop.value?.toggle(e)
|
||||
}
|
||||
|
||||
function close () {
|
||||
try {
|
||||
pop.value?.hide()
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function goMyProfile () {
|
||||
close()
|
||||
|
||||
// navegação segura por name
|
||||
safePush(
|
||||
{ name: 'MeuPerfil' },
|
||||
'/me/perfil'
|
||||
)
|
||||
}
|
||||
|
||||
function goSettings () {
|
||||
close()
|
||||
|
||||
const r = sessionRole.value
|
||||
|
||||
if (isAdminRole(r) || r === 'therapist') {
|
||||
// rota por name (como você já usa)
|
||||
router.push({ name: 'ConfiguracoesAgenda' })
|
||||
return
|
||||
}
|
||||
|
||||
if (r === 'patient') {
|
||||
router.push('/patient/conta')
|
||||
return
|
||||
}
|
||||
|
||||
router.push('/')
|
||||
}
|
||||
|
||||
async function safePush (target, fallback) {
|
||||
try {
|
||||
await router.push(target)
|
||||
} catch (e) {
|
||||
// fallback quando o "name" não existe no router
|
||||
if (fallback) {
|
||||
try {
|
||||
await router.push(fallback)
|
||||
} catch {
|
||||
await router.push('/')
|
||||
}
|
||||
} else {
|
||||
await router.push('/')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function goSecurity () {
|
||||
close()
|
||||
|
||||
// ✅ 1) tenta por NAME (recomendado)
|
||||
// ✅ 2) fallback: caminhos mais prováveis do teu projeto
|
||||
// Ajuste/defina a rota no router como name: 'AdminSecurity' para ficar perfeito
|
||||
safePush(
|
||||
{ name: 'AdminSecurity' },
|
||||
'/admin/settings/security'
|
||||
)
|
||||
}
|
||||
|
||||
async function signOut () {
|
||||
close()
|
||||
try {
|
||||
await supabase.auth.signOut()
|
||||
} catch {
|
||||
// se falhar, ainda assim manda pro login
|
||||
} finally {
|
||||
router.push('/auth/login')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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"
|
||||
@click="toggle"
|
||||
>
|
||||
<!-- avatar -->
|
||||
<img
|
||||
v-if="sessionUser.value?.user_metadata?.avatar_url"
|
||||
:src="sessionUser.value.user_metadata.avatar_url"
|
||||
class="h-9 w-9 rounded-xl object-cover border border-[var(--surface-border)]"
|
||||
/>
|
||||
<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>
|
||||
|
||||
<!-- labels -->
|
||||
<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-70" />
|
||||
</button>
|
||||
|
||||
<Popover ref="pop" appendTo="body">
|
||||
<div class="min-w-[220px] p-1">
|
||||
<Button
|
||||
label="Configurações"
|
||||
icon="pi pi-cog"
|
||||
text
|
||||
class="w-full justify-start"
|
||||
@click="goSettings"
|
||||
/>
|
||||
|
||||
<Button
|
||||
label="Segurança"
|
||||
icon="pi pi-shield"
|
||||
text
|
||||
class="w-full justify-start"
|
||||
@click="goSecurity"
|
||||
/>
|
||||
|
||||
<Button
|
||||
label="Meu Perfil"
|
||||
icon="pi pi-user"
|
||||
text
|
||||
class="w-full justify-start"
|
||||
@click="goMyProfile"
|
||||
/>
|
||||
|
||||
<div class="my-1 border-t border-[var(--surface-border)]" />
|
||||
|
||||
<Button
|
||||
label="Sair"
|
||||
icon="pi pi-sign-out"
|
||||
severity="danger"
|
||||
text
|
||||
class="w-full justify-start"
|
||||
@click="signOut"
|
||||
/>
|
||||
</div>
|
||||
</Popover>
|
||||
</div>
|
||||
</template>
|
||||
Reference in New Issue
Block a user