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:
Leonardo
2026-03-24 21:26:58 -03:00
parent a89d1f5560
commit 53a4980396
453 changed files with 121427 additions and 174407 deletions

View File

@@ -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>