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:
+140
-148
@@ -15,187 +15,179 @@
|
||||
|--------------------------------------------------------------------------
|
||||
-->
|
||||
<script setup>
|
||||
import { onMounted, watch } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { useTenantStore } from '@/stores/tenantStore'
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore'
|
||||
import { fetchDocsForPath } from '@/composables/useAjuda'
|
||||
import { onMounted, watch } from 'vue';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
import { useTenantStore } from '@/stores/tenantStore';
|
||||
import { useEntitlementsStore } from '@/stores/entitlementsStore';
|
||||
import { fetchDocsForPath } from '@/composables/useAjuda';
|
||||
|
||||
import AjudaDrawer from '@/components/AjudaDrawer.vue'
|
||||
import AppOfflineOverlay from '@/components/AppOfflineOverlay.vue'
|
||||
import AjudaDrawer from '@/components/AjudaDrawer.vue';
|
||||
import AppOfflineOverlay from '@/components/AppOfflineOverlay.vue';
|
||||
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const tenantStore = useTenantStore()
|
||||
const entStore = useEntitlementsStore()
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const tenantStore = useTenantStore();
|
||||
const entStore = useEntitlementsStore();
|
||||
|
||||
function isTenantArea (path = '') {
|
||||
return path.startsWith('/admin') || path.startsWith('/therapist')
|
||||
function isTenantArea(path = '') {
|
||||
return path.startsWith('/admin') || path.startsWith('/therapist');
|
||||
}
|
||||
|
||||
function isPortalArea (path = '') {
|
||||
return path.startsWith('/portal')
|
||||
function isPortalArea(path = '') {
|
||||
return path.startsWith('/portal');
|
||||
}
|
||||
|
||||
function isSaasArea (path = '') {
|
||||
return path.startsWith('/saas')
|
||||
function isSaasArea(path = '') {
|
||||
return path.startsWith('/saas');
|
||||
}
|
||||
|
||||
// ── Setup Wizard redirect ────────────────────────────────────────
|
||||
async function checkSetupWizard () {
|
||||
// Só verifica em área de tenant
|
||||
if (!isTenantArea(route.path)) return
|
||||
// Não redireciona se já está no setup
|
||||
if (route.path.includes('/setup')) return
|
||||
async function checkSetupWizard() {
|
||||
// Só verifica em área de tenant
|
||||
if (!isTenantArea(route.path)) return;
|
||||
// Não redireciona se já está no setup
|
||||
if (route.path.includes('/setup')) return;
|
||||
|
||||
const uid = tenantStore.user?.id
|
||||
if (!uid) return
|
||||
const uid = tenantStore.user?.id;
|
||||
if (!uid) return;
|
||||
|
||||
const { data } = await supabase
|
||||
.from('agenda_configuracoes')
|
||||
.select('setup_concluido, setup_clinica_concluido')
|
||||
.eq('owner_id', uid)
|
||||
.maybeSingle()
|
||||
const { data } = await supabase.from('agenda_configuracoes').select('setup_concluido, setup_clinica_concluido').eq('owner_id', uid).maybeSingle();
|
||||
|
||||
if (!data) return
|
||||
if (!data) return;
|
||||
|
||||
// Determina o kind do tenant ativo para saber qual flag checar
|
||||
const activeMembership = tenantStore.memberships?.find(m => m.id === tenantStore.activeTenantId)
|
||||
const kind = activeMembership?.kind ?? tenantStore.activeRole ?? ''
|
||||
const isClinic = kind.startsWith('clinic')
|
||||
// Determina o kind do tenant ativo para saber qual flag checar
|
||||
const activeMembership = tenantStore.memberships?.find((m) => m.id === tenantStore.activeTenantId);
|
||||
const kind = activeMembership?.kind ?? tenantStore.activeRole ?? '';
|
||||
const isClinic = kind.startsWith('clinic');
|
||||
|
||||
const setupDone = isClinic ? data.setup_clinica_concluido : data.setup_concluido
|
||||
if (!setupDone) {
|
||||
const dest = isClinic ? '/admin/setup' : '/therapist/setup'
|
||||
router.push(dest)
|
||||
}
|
||||
const setupDone = isClinic ? data.setup_clinica_concluido : data.setup_concluido;
|
||||
if (!setupDone) {
|
||||
const dest = isClinic ? '/admin/setup' : '/therapist/setup';
|
||||
router.push(dest);
|
||||
}
|
||||
}
|
||||
|
||||
async function debugSnapshot (label = 'snapshot') {
|
||||
console.group(`🧭 [APP DEBUG] ${label}`)
|
||||
try {
|
||||
// 0) rota + meta
|
||||
console.log('route.fullPath:', route.fullPath)
|
||||
console.log('route.path:', route.path)
|
||||
console.log('route.name:', route.name)
|
||||
console.log('route.meta:', route.meta)
|
||||
async function debugSnapshot(label = 'snapshot') {
|
||||
console.group(`🧭 [APP DEBUG] ${label}`);
|
||||
try {
|
||||
// 0) rota + meta
|
||||
console.log('route.fullPath:', route.fullPath);
|
||||
console.log('route.path:', route.path);
|
||||
console.log('route.name:', route.name);
|
||||
console.log('route.meta:', route.meta);
|
||||
|
||||
// 1) storage
|
||||
console.groupCollapsed('📦 Storage')
|
||||
console.log('localStorage.tenant_id:', localStorage.getItem('tenant_id'))
|
||||
console.log('localStorage.currentTenantId:', localStorage.getItem('currentTenantId'))
|
||||
console.log('localStorage.tenant:', localStorage.getItem('tenant'))
|
||||
console.log('sessionStorage.redirect_after_login:', sessionStorage.getItem('redirect_after_login'))
|
||||
console.log('sessionStorage.intended_area:', sessionStorage.getItem('intended_area'))
|
||||
console.groupEnd()
|
||||
// 1) storage
|
||||
console.groupCollapsed('📦 Storage');
|
||||
console.log('localStorage.tenant_id:', localStorage.getItem('tenant_id'));
|
||||
console.log('localStorage.currentTenantId:', localStorage.getItem('currentTenantId'));
|
||||
console.log('localStorage.tenant:', localStorage.getItem('tenant'));
|
||||
console.log('sessionStorage.redirect_after_login:', sessionStorage.getItem('redirect_after_login'));
|
||||
console.log('sessionStorage.intended_area:', sessionStorage.getItem('intended_area'));
|
||||
console.groupEnd();
|
||||
|
||||
// 2) sessão auth (fonte real)
|
||||
const { data: authData, error: authErr } = await supabase.auth.getUser()
|
||||
if (authErr) console.warn('[auth.getUser] error:', authErr)
|
||||
const user = authData?.user || null
|
||||
console.log('auth.user:', user ? { id: user.id, email: user.email } : null)
|
||||
// 2) sessão auth (fonte real)
|
||||
const { data: authData, error: authErr } = await supabase.auth.getUser();
|
||||
if (authErr) console.warn('[auth.getUser] error:', authErr);
|
||||
const user = authData?.user || null;
|
||||
console.log('auth.user:', user ? { id: user.id, email: user.email } : null);
|
||||
|
||||
// 3) profiles.role (identidade global)
|
||||
let profileRole = null
|
||||
if (user?.id) {
|
||||
const { data: profile, error: pErr } = await supabase
|
||||
.from('profiles')
|
||||
.select('role')
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
// 3) profiles.role (identidade global)
|
||||
let profileRole = null;
|
||||
if (user?.id) {
|
||||
const { data: profile, error: pErr } = await supabase.from('profiles').select('role').eq('id', user.id).single();
|
||||
|
||||
if (pErr) console.warn('[profiles] error:', pErr)
|
||||
profileRole = profile?.role || null
|
||||
if (pErr) console.warn('[profiles] error:', pErr);
|
||||
profileRole = profile?.role || null;
|
||||
}
|
||||
console.log('profiles.role (global):', profileRole);
|
||||
|
||||
// 4) memberships via RPC (fonte de verdade do tenantStore)
|
||||
let rpcTenants = null;
|
||||
if (user?.id) {
|
||||
const { data: rpcData, error: rpcErr } = await supabase.rpc('my_tenants');
|
||||
if (rpcErr) console.warn('[rpc my_tenants] error:', rpcErr);
|
||||
rpcTenants = rpcData ?? null;
|
||||
}
|
||||
console.log('rpc.my_tenants():', rpcTenants);
|
||||
|
||||
// 5) stores (sempre logar)
|
||||
console.groupCollapsed('🏪 Stores (before optional loads)');
|
||||
console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId);
|
||||
console.log('tenantStore.activeRole:', tenantStore.activeRole);
|
||||
console.log('tenantStore.memberships:', tenantStore.memberships);
|
||||
console.log('entStore.loaded:', entStore.loaded);
|
||||
console.log('entStore.tenantId:', entStore.activeTenantId || entStore.tenantId);
|
||||
console.groupEnd();
|
||||
|
||||
// 6) IMPORTANTÍSSIMO: não carregar tenant fora da área tenant
|
||||
const path = route.path || '';
|
||||
|
||||
if (isTenantArea(path)) {
|
||||
console.log('✅ Tenant area detected → will loadSessionAndTenant + entitlements');
|
||||
await tenantStore.loadSessionAndTenant();
|
||||
|
||||
if (tenantStore.activeTenantId) {
|
||||
await entStore.loadForTenant(tenantStore.activeTenantId);
|
||||
}
|
||||
|
||||
console.groupCollapsed('🏪 Stores (after tenant loads)');
|
||||
console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId);
|
||||
console.log('tenantStore.activeRole:', tenantStore.activeRole);
|
||||
console.log('tenantStore.memberships:', tenantStore.memberships);
|
||||
console.log("entStore.can('online_scheduling.manage'):", entStore.can?.('online_scheduling.manage'));
|
||||
console.groupEnd();
|
||||
|
||||
// Redireciona para o wizard se setup não foi concluído
|
||||
await checkSetupWizard();
|
||||
} else if (isPortalArea(path)) {
|
||||
console.log('🟣 Portal area detected → SKIP tenantStore.loadSessionAndTenant()');
|
||||
} else if (isSaasArea(path)) {
|
||||
console.log('🟠 SaaS area detected → SKIP tenantStore.loadSessionAndTenant()');
|
||||
} else {
|
||||
console.log('⚪ Other/public area detected → SKIP tenantStore.loadSessionAndTenant()');
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[APP DEBUG] snapshot error:', e);
|
||||
} finally {
|
||||
console.groupEnd();
|
||||
}
|
||||
console.log('profiles.role (global):', profileRole)
|
||||
|
||||
// 4) memberships via RPC (fonte de verdade do tenantStore)
|
||||
let rpcTenants = null
|
||||
if (user?.id) {
|
||||
const { data: rpcData, error: rpcErr } = await supabase.rpc('my_tenants')
|
||||
if (rpcErr) console.warn('[rpc my_tenants] error:', rpcErr)
|
||||
rpcTenants = rpcData ?? null
|
||||
}
|
||||
console.log('rpc.my_tenants():', rpcTenants)
|
||||
|
||||
// 5) stores (sempre logar)
|
||||
console.groupCollapsed('🏪 Stores (before optional loads)')
|
||||
console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId)
|
||||
console.log('tenantStore.activeRole:', tenantStore.activeRole)
|
||||
console.log('tenantStore.memberships:', tenantStore.memberships)
|
||||
console.log('entStore.loaded:', entStore.loaded)
|
||||
console.log('entStore.tenantId:', entStore.activeTenantId || entStore.tenantId)
|
||||
console.groupEnd()
|
||||
|
||||
// 6) IMPORTANTÍSSIMO: não carregar tenant fora da área tenant
|
||||
const path = route.path || ''
|
||||
|
||||
if (isTenantArea(path)) {
|
||||
console.log('✅ Tenant area detected → will loadSessionAndTenant + entitlements')
|
||||
await tenantStore.loadSessionAndTenant()
|
||||
|
||||
if (tenantStore.activeTenantId) {
|
||||
await entStore.loadForTenant(tenantStore.activeTenantId)
|
||||
}
|
||||
|
||||
console.groupCollapsed('🏪 Stores (after tenant loads)')
|
||||
console.log('tenantStore.activeTenantId:', tenantStore.activeTenantId)
|
||||
console.log('tenantStore.activeRole:', tenantStore.activeRole)
|
||||
console.log('tenantStore.memberships:', tenantStore.memberships)
|
||||
console.log("entStore.can('online_scheduling.manage'):", entStore.can?.('online_scheduling.manage'))
|
||||
console.groupEnd()
|
||||
|
||||
// Redireciona para o wizard se setup não foi concluído
|
||||
await checkSetupWizard()
|
||||
} else if (isPortalArea(path)) {
|
||||
console.log('🟣 Portal area detected → SKIP tenantStore.loadSessionAndTenant()')
|
||||
} else if (isSaasArea(path)) {
|
||||
console.log('🟠 SaaS area detected → SKIP tenantStore.loadSessionAndTenant()')
|
||||
} else {
|
||||
console.log('⚪ Other/public area detected → SKIP tenantStore.loadSessionAndTenant()')
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('[APP DEBUG] snapshot error:', e)
|
||||
} finally {
|
||||
console.groupEnd()
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// 🔥 PRIMEIRO LOG — TENANT ID BRUTO (mantive sua ideia)
|
||||
console.log('[SEU_TENANT_ID]', localStorage.getItem('tenant_id'))
|
||||
// 🔥 PRIMEIRO LOG — TENANT ID BRUTO (mantive sua ideia)
|
||||
console.log('[SEU_TENANT_ID]', localStorage.getItem('tenant_id'));
|
||||
|
||||
// snapshot inicial
|
||||
await debugSnapshot('mounted')
|
||||
// snapshot inicial
|
||||
await debugSnapshot('mounted');
|
||||
|
||||
// Carrega docs de ajuda para a rota inicial
|
||||
fetchDocsForPath(route.path)
|
||||
})
|
||||
// Carrega docs de ajuda para a rota inicial
|
||||
fetchDocsForPath(route.path);
|
||||
});
|
||||
|
||||
// snapshot a cada navegação (isso é o que vai te salvar)
|
||||
watch(
|
||||
() => route.fullPath,
|
||||
async (to, from) => {
|
||||
await debugSnapshot(`route change: ${from} -> ${to}`)
|
||||
// Atualiza docs de ajuda ao navegar
|
||||
fetchDocsForPath(route.path)
|
||||
// Verifica setup sempre que entrar em área tenant
|
||||
if (isTenantArea(route.path) && tenantStore.loaded) {
|
||||
await checkSetupWizard()
|
||||
() => route.fullPath,
|
||||
async (to, from) => {
|
||||
await debugSnapshot(`route change: ${from} -> ${to}`);
|
||||
// Atualiza docs de ajuda ao navegar
|
||||
fetchDocsForPath(route.path);
|
||||
// Verifica setup sempre que entrar em área tenant
|
||||
if (isTenantArea(route.path) && tenantStore.loaded) {
|
||||
await checkSetupWizard();
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<router-view />
|
||||
<router-view />
|
||||
|
||||
<!-- Drawer de ajuda global — fora de qualquer layout, sempre disponível -->
|
||||
<Teleport to="body">
|
||||
<AjudaDrawer />
|
||||
</Teleport>
|
||||
<!-- Drawer de ajuda global — fora de qualquer layout, sempre disponível -->
|
||||
<Teleport to="body">
|
||||
<AjudaDrawer />
|
||||
</Teleport>
|
||||
|
||||
<!-- Overlay de sem conexão -->
|
||||
<AppOfflineOverlay />
|
||||
</template>
|
||||
<!-- Overlay de sem conexão -->
|
||||
<AppOfflineOverlay />
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user