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:
@@ -16,155 +16,159 @@
|
||||
-->
|
||||
<!-- Mini icon rail (Layout 2) -->
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
import { useMenuStore } from '@/stores/menuStore'
|
||||
import { useLayout } from './composables/layout'
|
||||
import { sessionUser } from '@/app/session'
|
||||
import { useMenuStore } from '@/stores/menuStore';
|
||||
import { useLayout } from './composables/layout';
|
||||
import { sessionUser } from '@/app/session';
|
||||
|
||||
import AppMenuFooterPanel from './AppMenuFooterPanel.vue'
|
||||
import AppMenuFooterPanel from './AppMenuFooterPanel.vue';
|
||||
|
||||
const menuStore = useMenuStore()
|
||||
const { layoutState } = useLayout()
|
||||
const menuStore = useMenuStore();
|
||||
const { layoutState } = useLayout();
|
||||
|
||||
// ── Seções do rail (derivadas do model) ─────────────────────
|
||||
const railSections = computed(() => {
|
||||
const model = menuStore.model || []
|
||||
return model
|
||||
.filter(s => s.label && Array.isArray(s.items) && s.items.length)
|
||||
.filter(s => s.label.toLowerCase().normalize('NFD').replace(/\p{Diacritic}/gu, '').trim() !== 'inicio')
|
||||
.map(s => ({
|
||||
key: s.label,
|
||||
label: s.label,
|
||||
icon: s.icon || s.items.find(i => i.icon)?.icon || 'pi pi-fw pi-circle',
|
||||
items: s.items
|
||||
}))
|
||||
})
|
||||
const model = menuStore.model || [];
|
||||
return model
|
||||
.filter((s) => s.label && Array.isArray(s.items) && s.items.length)
|
||||
.filter(
|
||||
(s) =>
|
||||
s.label
|
||||
.toLowerCase()
|
||||
.normalize('NFD')
|
||||
.replace(/\p{Diacritic}/gu, '')
|
||||
.trim() !== 'inicio'
|
||||
)
|
||||
.map((s) => ({
|
||||
key: s.label,
|
||||
label: s.label,
|
||||
icon: s.icon || s.items.find((i) => i.icon)?.icon || 'pi pi-fw pi-circle',
|
||||
items: s.items
|
||||
}));
|
||||
});
|
||||
|
||||
// ── Avatar / iniciais ────────────────────────────────────────
|
||||
const avatarUrl = computed(() => sessionUser.value?.user_metadata?.avatar_url || null)
|
||||
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 userName = computed(() => sessionUser.value?.user_metadata?.full_name || sessionUser.value?.email || 'Conta')
|
||||
const avatarUrl = computed(() => sessionUser.value?.user_metadata?.avatar_url || null);
|
||||
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 userName = computed(() => sessionUser.value?.user_metadata?.full_name || sessionUser.value?.email || 'Conta');
|
||||
|
||||
// ── Início (fixo) ────────────────────────────────────────────
|
||||
function selectHome () {
|
||||
if (layoutState.railSectionKey === '__home__' && layoutState.railPanelOpen) {
|
||||
layoutState.railPanelOpen = false
|
||||
} else {
|
||||
layoutState.railSectionKey = '__home__'
|
||||
layoutState.railPanelOpen = true
|
||||
}
|
||||
function selectHome() {
|
||||
if (layoutState.railSectionKey === '__home__' && layoutState.railPanelOpen) {
|
||||
layoutState.railPanelOpen = false;
|
||||
} else {
|
||||
layoutState.railSectionKey = '__home__';
|
||||
layoutState.railPanelOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
const isHomeActive = computed(() =>
|
||||
layoutState.railSectionKey === '__home__' && layoutState.railPanelOpen
|
||||
)
|
||||
const isHomeActive = computed(() => layoutState.railSectionKey === '__home__' && layoutState.railPanelOpen);
|
||||
|
||||
// ── Seleção de seção ─────────────────────────────────────────
|
||||
function selectSection (section) {
|
||||
if (layoutState.railSectionKey === section.key && layoutState.railPanelOpen) {
|
||||
layoutState.railPanelOpen = false
|
||||
} else {
|
||||
layoutState.railSectionKey = section.key
|
||||
layoutState.railPanelOpen = true
|
||||
}
|
||||
function selectSection(section) {
|
||||
if (layoutState.railSectionKey === section.key && layoutState.railPanelOpen) {
|
||||
layoutState.railPanelOpen = false;
|
||||
} else {
|
||||
layoutState.railSectionKey = section.key;
|
||||
layoutState.railPanelOpen = true;
|
||||
}
|
||||
}
|
||||
|
||||
function isActiveSectionOrChild (section) {
|
||||
if (layoutState.railSectionKey === section.key && layoutState.railPanelOpen) return true
|
||||
const active = String(layoutState.activePath || '')
|
||||
return section.items.some(i => {
|
||||
const p = typeof i.to === 'string' ? i.to : ''
|
||||
return p && active.startsWith(p)
|
||||
})
|
||||
function isActiveSectionOrChild(section) {
|
||||
if (layoutState.railSectionKey === section.key && layoutState.railPanelOpen) return true;
|
||||
const active = String(layoutState.activePath || '');
|
||||
return section.items.some((i) => {
|
||||
const p = typeof i.to === 'string' ? i.to : '';
|
||||
return p && active.startsWith(p);
|
||||
});
|
||||
}
|
||||
|
||||
// ── Menu do usuário (rodapé) ─────────────────────────────────
|
||||
const footerPanel = ref(null)
|
||||
function toggleUserMenu (e) { footerPanel.value?.toggle(e) }
|
||||
const footerPanel = ref(null);
|
||||
function toggleUserMenu(e) {
|
||||
footerPanel.value?.toggle(e);
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<aside class="rail w-[60px] shrink-0 h-screen flex flex-col items-center border-r border-[var(--surface-border)] bg-[var(--surface-card)] z-50 select-none">
|
||||
<aside class="rail w-[60px] shrink-0 h-screen flex flex-col items-center border-r border-[var(--surface-border)] bg-[var(--surface-card)] z-50 select-none">
|
||||
<!-- ── Brand ──────────────────────────────────────────── -->
|
||||
<div class="w-full h-14 shrink-0 grid place-items-center border-b border-[var(--surface-border)]">
|
||||
<span class="text-[1.35rem] font-extrabold leading-none text-[var(--primary-color)] [text-shadow:0_0_20px_color-mix(in_srgb,var(--primary-color)_40%,transparent)]">Ψ</span>
|
||||
</div>
|
||||
|
||||
<!-- ── Brand ──────────────────────────────────────────── -->
|
||||
<div class="w-full h-14 shrink-0 grid place-items-center border-b border-[var(--surface-border)]">
|
||||
<span class="text-[1.35rem] font-extrabold leading-none text-[var(--primary-color)] [text-shadow:0_0_20px_color-mix(in_srgb,var(--primary-color)_40%,transparent)]">Ψ</span>
|
||||
</div>
|
||||
<!-- ── Nav icons ──────────────────────────────────────── -->
|
||||
<nav class="flex-1 w-full flex flex-col items-center gap-1 py-2.5 overflow-y-auto overflow-x-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" role="navigation" aria-label="Menu principal">
|
||||
<!-- Início fixo -->
|
||||
<button
|
||||
class="rail-btn relative w-10 h-10 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-base shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
:class="isHomeActive ? 'rail-btn--active bg-[color-mix(in_srgb,var(--primary-color)_12%,transparent)] !text-[var(--primary-color)]' : ''"
|
||||
v-tooltip.right="{ value: 'Início', showDelay: 0 }"
|
||||
aria-label="Início"
|
||||
@click="selectHome"
|
||||
>
|
||||
<i class="pi pi-fw pi-home" />
|
||||
</button>
|
||||
|
||||
<!-- ── Nav icons ──────────────────────────────────────── -->
|
||||
<nav class="flex-1 w-full flex flex-col items-center gap-1 py-2.5 overflow-y-auto overflow-x-hidden [scrollbar-width:none] [&::-webkit-scrollbar]:hidden" role="navigation" aria-label="Menu principal">
|
||||
<button
|
||||
v-for="section in railSections"
|
||||
:key="section.key"
|
||||
class="rail-btn relative w-10 h-10 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-base shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
:class="isActiveSectionOrChild(section) ? 'rail-btn--active bg-[color-mix(in_srgb,var(--primary-color)_12%,transparent)] !text-[var(--primary-color)]' : ''"
|
||||
v-tooltip.right="{ value: section.label, showDelay: 0 }"
|
||||
:aria-label="section.label"
|
||||
@click="selectSection(section)"
|
||||
>
|
||||
<i :class="section.icon" />
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<!-- Início fixo -->
|
||||
<button
|
||||
class="rail-btn relative w-10 h-10 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-base shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
:class="isHomeActive ? 'rail-btn--active bg-[color-mix(in_srgb,var(--primary-color)_12%,transparent)] !text-[var(--primary-color)]' : ''"
|
||||
v-tooltip.right="{ value: 'Início', showDelay: 0 }"
|
||||
aria-label="Início"
|
||||
@click="selectHome"
|
||||
>
|
||||
<i class="pi pi-fw pi-home" />
|
||||
</button>
|
||||
<!-- ── Rodapé ─────────────────────────────────────────── -->
|
||||
<div class="w-full flex flex-col items-center gap-1.5 py-2 pb-3 border-t border-[var(--surface-border)]">
|
||||
<button
|
||||
class="w-9 h-9 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-[0.875rem] shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
v-tooltip.right="{ value: 'Configurações', showDelay: 0 }"
|
||||
aria-label="Configurações"
|
||||
@click="$router.push('/configuracoes')"
|
||||
>
|
||||
<i class="pi pi-fw pi-cog" />
|
||||
</button>
|
||||
|
||||
<button
|
||||
v-for="section in railSections"
|
||||
:key="section.key"
|
||||
class="rail-btn relative w-10 h-10 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-base shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
:class="isActiveSectionOrChild(section) ? 'rail-btn--active bg-[color-mix(in_srgb,var(--primary-color)_12%,transparent)] !text-[var(--primary-color)]' : ''"
|
||||
v-tooltip.right="{ value: section.label, showDelay: 0 }"
|
||||
:aria-label="section.label"
|
||||
@click="selectSection(section)"
|
||||
>
|
||||
<i :class="section.icon" />
|
||||
</button>
|
||||
</nav>
|
||||
<!-- Avatar — trigger do menu de usuário -->
|
||||
<button
|
||||
class="w-9 h-9 rounded-[10px] border-none cursor-pointer overflow-hidden shrink-0 bg-[var(--surface-ground)] grid place-items-center transition-[transform,box-shadow] duration-150 hover:scale-105 hover:shadow-[0_0_0_2px_var(--primary-color)]"
|
||||
v-tooltip.right="{ value: userName, showDelay: 0 }"
|
||||
:aria-label="userName"
|
||||
@click="toggleUserMenu"
|
||||
>
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-full h-full object-cover" :alt="userName" />
|
||||
<span v-else class="text-[1rem] font-bold text-[var(--text-color)]">{{ initials }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Rodapé ─────────────────────────────────────────── -->
|
||||
<div class="w-full flex flex-col items-center gap-1.5 py-2 pb-3 border-t border-[var(--surface-border)]">
|
||||
<button
|
||||
class="w-9 h-9 rounded-[10px] grid place-items-center border-none bg-transparent text-[var(--text-color-secondary)] cursor-pointer text-[0.875rem] shrink-0 transition-[background,color,transform] duration-150 hover:bg-[var(--surface-ground)] hover:text-[var(--text-color)] hover:scale-105"
|
||||
v-tooltip.right="{ value: 'Configurações', showDelay: 0 }"
|
||||
aria-label="Configurações"
|
||||
@click="$router.push('/configuracoes')"
|
||||
>
|
||||
<i class="pi pi-fw pi-cog" />
|
||||
</button>
|
||||
|
||||
<!-- Avatar — trigger do menu de usuário -->
|
||||
<button
|
||||
class="w-9 h-9 rounded-[10px] border-none cursor-pointer overflow-hidden shrink-0 bg-[var(--surface-ground)] grid place-items-center transition-[transform,box-shadow] duration-150 hover:scale-105 hover:shadow-[0_0_0_2px_var(--primary-color)]"
|
||||
v-tooltip.right="{ value: userName, showDelay: 0 }"
|
||||
:aria-label="userName"
|
||||
@click="toggleUserMenu"
|
||||
>
|
||||
<img v-if="avatarUrl" :src="avatarUrl" class="w-full h-full object-cover" :alt="userName" />
|
||||
<span v-else class="text-[1rem] font-bold text-[var(--text-color)]">{{ initials }}</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- ── Menu de usuário (popup via AppMenuFooterPanel) ── -->
|
||||
<AppMenuFooterPanel ref="footerPanel" variant="rail" />
|
||||
|
||||
</aside>
|
||||
<!-- ── Menu de usuário (popup via AppMenuFooterPanel) ── -->
|
||||
<AppMenuFooterPanel ref="footerPanel" variant="rail" />
|
||||
</aside>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* Indicador lateral do botão ativo — pseudo-elemento não expressável em Tailwind */
|
||||
.rail-btn--active::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 22px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: var(--primary-color);
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: -10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 3px;
|
||||
height: 22px;
|
||||
border-radius: 0 3px 3px 0;
|
||||
background: var(--primary-color);
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user