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
+140 -148
View File
@@ -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>