ZERADO
This commit is contained in:
@@ -1,34 +1,161 @@
|
||||
<script setup>
|
||||
import { useLayout } from '@/layout/composables/layout';
|
||||
import { computed } from 'vue';
|
||||
import AppFooter from './AppFooter.vue';
|
||||
import AppSidebar from './AppSidebar.vue';
|
||||
import AppTopbar from './AppTopbar.vue';
|
||||
import { useLayout } from '@/layout/composables/layout'
|
||||
import { computed, onMounted, onBeforeUnmount, provide } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const { layoutConfig, layoutState, hideMobileMenu } = useLayout();
|
||||
import AppFooter from './AppFooter.vue'
|
||||
import AppSidebar from './AppSidebar.vue'
|
||||
import AppTopbar from './AppTopbar.vue'
|
||||
import AppRail from './AppRail.vue'
|
||||
import AppRailPanel from './AppRailPanel.vue'
|
||||
import AppRailTopbar from './AppRailTopbar.vue'
|
||||
|
||||
import { useTenantStore } from '@/stores/tenantStore'
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore'
|
||||
import { useTenantFeaturesStore } from '@/stores/tenantFeaturesStore'
|
||||
|
||||
const route = useRoute()
|
||||
const { layoutConfig, layoutState, hideMobileMenu, isDesktop } = useLayout()
|
||||
|
||||
// ✅ área do layout definida por rota (shell único)
|
||||
const layoutArea = computed(() => route.meta?.area || null)
|
||||
provide('layoutArea', layoutArea)
|
||||
|
||||
const tenantStore = useTenantStore()
|
||||
const entitlementsStore = useEntitlementsStore()
|
||||
const tf = useTenantFeaturesStore()
|
||||
|
||||
const containerClass = computed(() => {
|
||||
return {
|
||||
'layout-overlay': layoutConfig.menuMode === 'overlay',
|
||||
'layout-static': layoutConfig.menuMode === 'static',
|
||||
'layout-overlay-active': layoutState.overlayMenuActive,
|
||||
'layout-mobile-active': layoutState.mobileMenuActive,
|
||||
'layout-static-inactive': layoutState.staticMenuInactive
|
||||
};
|
||||
});
|
||||
return {
|
||||
'layout-overlay': layoutConfig.menuMode === 'overlay',
|
||||
'layout-static': layoutConfig.menuMode === 'static',
|
||||
'layout-overlay-active': layoutState.overlayMenuActive,
|
||||
'layout-mobile-active': layoutState.mobileMenuActive,
|
||||
'layout-static-inactive': layoutState.staticMenuInactive
|
||||
}
|
||||
})
|
||||
|
||||
function getTenantId () {
|
||||
return (
|
||||
tenantStore.activeTenantId ||
|
||||
tenantStore.tenantId ||
|
||||
tenantStore.currentTenantId ||
|
||||
tenantStore.tenant?.id ||
|
||||
null
|
||||
)
|
||||
}
|
||||
|
||||
async function revalidateAfterSessionRefresh () {
|
||||
try {
|
||||
if (!tenantStore.loaded && !tenantStore.loading) {
|
||||
await tenantStore.loadSessionAndTenant()
|
||||
}
|
||||
|
||||
const tid = getTenantId()
|
||||
if (!tid) return
|
||||
|
||||
await Promise.allSettled([
|
||||
entitlementsStore.loadForTenant?.(tid, { force: true }),
|
||||
tf.fetchForTenant?.(tid, { force: true })
|
||||
])
|
||||
} catch (e) {
|
||||
console.warn('[AppLayout] revalidateAfterSessionRefresh failed:', e?.message || e)
|
||||
}
|
||||
}
|
||||
|
||||
function onSessionRefreshed () {
|
||||
// ✅ Só revalidar tenantStore/entitlements em áreas TENANT.
|
||||
// Em /portal e /account isso causa vazamento de contexto e troca de menu.
|
||||
const p = String(route.path || '')
|
||||
const isTenantArea =
|
||||
p.startsWith('/admin') ||
|
||||
p.startsWith('/therapist') ||
|
||||
p.startsWith('/supervisor') ||
|
||||
p.startsWith('/saas')
|
||||
|
||||
if (!isTenantArea) return
|
||||
revalidateAfterSessionRefresh()
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('app:session-refreshed', onSessionRefreshed)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('app:session-refreshed', onSessionRefreshed)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="layout-wrapper" :class="containerClass">
|
||||
<AppTopbar />
|
||||
<AppSidebar />
|
||||
<div class="layout-main-container">
|
||||
<div class="layout-main">
|
||||
<router-view />
|
||||
</div>
|
||||
<AppFooter />
|
||||
<!-- ══ Layout 2: Rail + Painel + Main (full-width) ══════════ -->
|
||||
<template v-if="layoutConfig.variant === 'rail' && isDesktop()">
|
||||
<div class="l2-root">
|
||||
<AppRail />
|
||||
<div class="l2-body">
|
||||
<AppRailTopbar />
|
||||
<div class="l2-content">
|
||||
<AppRailPanel />
|
||||
<div class="l2-main">
|
||||
<router-view />
|
||||
</div>
|
||||
</div>
|
||||
<div class="layout-mask animate-fadein" @click="hideMobileMenu" />
|
||||
</div>
|
||||
</div>
|
||||
<Toast />
|
||||
</template>
|
||||
|
||||
<!-- ══ Layout 1: Clássico ═══════════════════════════════════ -->
|
||||
<template v-else>
|
||||
<div class="layout-wrapper" :class="containerClass">
|
||||
<AppTopbar />
|
||||
<AppSidebar />
|
||||
<div class="layout-main-container">
|
||||
<div class="layout-main">
|
||||
<router-view />
|
||||
</div>
|
||||
<AppFooter />
|
||||
</div>
|
||||
<div class="layout-mask animate-fadein" @click="hideMobileMenu" />
|
||||
</div>
|
||||
<Toast />
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ─── Layout 2 ───────────────────────────────────────────── */
|
||||
.l2-root {
|
||||
display: flex;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
background: var(--surface-ground);
|
||||
}
|
||||
|
||||
/* Coluna direita do rail: topbar + conteúdo */
|
||||
.l2-body {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Linha: painel lateral + main */
|
||||
.l2-content {
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Área de conteúdo principal */
|
||||
.l2-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
/* Headers sticky no Rail colam no topo do scroll container (já abaixo da topbar) */
|
||||
--layout-sticky-top: 0px;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user