Files
agenciapsilmno/src/App.vue
T
Leonardo a7f6bcbe66 F3 schema-per-tenant: frontend usa tenantDb() pra tabelas tenant
- useTenantDb composable + lib/supabase/tenantClient (tenantDb/tenantSchemaName)
- tenantStore: getters activeTenantSlug/activeTenantSchema; my_tenants() RPC
  passa a devolver slug+nome (migration 07)
- codemod scripts/codemod-tenant-db.py: supabase.from('<84 tabelas + 6 views
  tenant>') -> tenantDb().from(...) em 139 arquivos (777 chamadas), remove
  .eq('tenant_id') das cadeias tenant (173)
- passada manual (4 agentes): remove tenant_id de payloads insert/upsert/update,
  selects, .or/.is de defaults; onConflict ajustado pros uniques sem tenant_id
  (singletons usam 'singleton'); realtime de tabelas tenant aponta pro schema
  do tenant ativo; repos dropam tenant_id defensivamente de payloads externos
- agendaSelects: tenant_id fora do AGENDA_EVENT_SELECT (quebraria PostgREST)
- zero embeds cross-schema (todos FK embeds sao tenant->tenant ou global->global)
- build de producao passa; 67 .js checados

Pendente (fora do escopo F3, sao cross-tenant/anon -> F4/F6):
- AgendadorPublicoPage (anon, resolve tenant por link_slug)
- Saas{Feriados,NotificationTemplates,DocumentTemplates,Whatsapp}Page
  (gerenciam defaults do sistema / views cross-tenant)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-13 04:44:59 -03:00

102 lines
3.3 KiB
Vue

<!--
|--------------------------------------------------------------------------
| Agência PSI
|--------------------------------------------------------------------------
| Criado e desenvolvido por Leonardo Nohama
|
| Tecnologia aplicada à escuta.
| Estrutura para o cuidado.
|
| Arquivo: src/App.vue
| Data: 2026
| Local: São Carlos/SP Brasil
|--------------------------------------------------------------------------
| © 2026 Todos os direitos reservados
|--------------------------------------------------------------------------
-->
<script setup>
import { onMounted, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { supabase } from '@/lib/supabase/client';
import { tenantDb } from '@/lib/supabase/tenantClient';
import { useTenantStore } from '@/stores/tenantStore';
import { fetchDocsForPath } from '@/composables/useAjuda';
import AjudaDrawer from '@/components/AjudaDrawer.vue';
import AppOfflineOverlay from '@/components/AppOfflineOverlay.vue';
const route = useRoute();
const router = useRouter();
const tenantStore = useTenantStore();
function isTenantArea(path = '') {
return path.startsWith('/admin') || path.startsWith('/therapist') || path.startsWith('/configuracoes');
}
// ── Setup Wizard redirect ────────────────────────────────────────
// Cache por sessão: uma vez confirmado, não verifica de novo
let _setupClearedUid = null;
let _setupClearedIsClinic = null;
async function checkSetupWizard() {
if (!isTenantArea(route.path)) return;
if (route.path.includes('/setup')) return;
const uid = tenantStore.user?.id;
if (!uid) return;
const activeMembership = tenantStore.memberships?.find((m) => m.tenant_id === tenantStore.activeTenantId);
const kind = activeMembership?.kind ?? tenantStore.activeRole ?? '';
const isClinic = kind.startsWith('clinic');
// Se já confirmamos que este uid passou o setup, não verifica de novo
if (_setupClearedUid === uid && _setupClearedIsClinic === isClinic) return;
const { data } = await tenantDb().from('agenda_configuracoes')
.select('setup_concluido, setup_clinica_concluido, atendimento_mode')
.eq('owner_id', uid)
.maybeSingle();
if (!data) return; // sem linha = setup nunca iniciado, não redireciona
// Considera completo se qualquer flag de conclusão estiver setada
const setupDone = data.setup_concluido || data.setup_clinica_concluido || !!data.atendimento_mode;
if (setupDone) {
// Grava cache: não verifica mais nesta sessão
_setupClearedUid = uid;
_setupClearedIsClinic = isClinic;
return;
}
const dest = isClinic ? '/admin/setup' : '/therapist/setup';
router.push(dest);
}
onMounted(() => {
fetchDocsForPath(route.path);
});
watch(
() => route.fullPath,
() => {
fetchDocsForPath(route.path);
if (isTenantArea(route.path) && tenantStore.loaded) {
checkSetupWizard();
}
}
);
</script>
<template>
<router-view />
<!-- Drawer de ajuda global fora de qualquer layout, sempre disponível -->
<Teleport to="body">
<AjudaDrawer />
</Teleport>
<!-- Overlay de sem conexão -->
<AppOfflineOverlay />
</template>