195 lines
6.9 KiB
Markdown
195 lines
6.9 KiB
Markdown
# Router — AppLayout Compartilhado
|
|
|
|
**Data:** 2026-03-25
|
|
**Arquivo:** `src/router/index.js` e arquivos de rota relacionados
|
|
|
|
---
|
|
|
|
## Problema
|
|
|
|
A aplicação apresentava comportamento de "reload" ao navegar entre seções distintas (ex: de `/configuracoes/agenda` para `/therapist/agenda/recorrencias`). O efeito visual era a sidebar e a topbar piscando e sendo completamente reconstruídas — mesmo sendo o mesmo layout.
|
|
|
|
Além disso, ao clicar rapidamente para trocar de página, o Vue recarregava o componente de layout inteiro, causando lentidão perceptível.
|
|
|
|
### Causa raiz
|
|
|
|
Cada área da aplicação tinha seu **próprio registro de rota com `component: AppLayout`**:
|
|
|
|
```
|
|
/therapist → { component: AppLayout } ← instância A
|
|
/admin → { component: AppLayout } ← instância B
|
|
/configuracoes→ { component: AppLayout } ← instância C
|
|
/account → { component: AppLayout } ← instância D
|
|
...
|
|
```
|
|
|
|
O Vue Router trata registros de rota distintos como componentes distintos. Ao navegar de `/configuracoes` para `/therapist`, o Vue **desmontava** a instância A e **montava** a instância B do zero — sidebar, topbar, watchers, stores, tudo reiniciava. Isso é o comportamento correto do Vue Router, mas estava sendo usado de forma incorreta para uma aplicação que tem um layout único.
|
|
|
|
---
|
|
|
|
## Solução
|
|
|
|
Consolidar todas as áreas autenticadas sob **um único registro de rota pai** com `component: AppLayout`. O Vue Router reutiliza a instância do componente pai enquanto navega entre os filhos.
|
|
|
|
### Arquitetura após a mudança
|
|
|
|
```
|
|
/ → AppLayout (instância única — nunca desmontada)
|
|
├── therapist/ → RouterPassthrough → página
|
|
├── admin/ → RouterPassthrough → página
|
|
├── configuracoes/ → ConfiguracoesPage → sub-página
|
|
├── account/ → RouterPassthrough → página
|
|
├── upgrade/ → RouterPassthrough → página
|
|
├── supervisor/ → RouterPassthrough → página
|
|
└── features/ → RouterPassthrough → página
|
|
```
|
|
|
|
**Antes:** AppLayout era desmontado/remontado ao trocar de seção.
|
|
**Depois:** Apenas o conteúdo (área central) é trocado. AppLayout nunca desmonta.
|
|
|
|
---
|
|
|
|
## Componente RouterPassthrough
|
|
|
|
Criado em `src/layout/RouterPassthrough.vue`:
|
|
|
|
```vue
|
|
<template><router-view /></template>
|
|
```
|
|
|
|
Serve como "rota de grupo com componente" para as áreas que não têm layout intermediário próprio (therapist, admin, account etc.). Ele é necessário porque o Vue Router precisa de um componente em cada nível da hierarquia para renderizar os filhos via `<router-view>`.
|
|
|
|
---
|
|
|
|
## Arquivos alterados
|
|
|
|
### `src/router/index.js`
|
|
|
|
Antes: importava cada rota e espalhava todas no nível raiz do array de rotas.
|
|
Depois: cria um único pai `{ path: '/', component: AppLayout }` e coloca todas as rotas de app como filhas.
|
|
|
|
```js
|
|
const routes = [
|
|
// Rotas sem layout
|
|
...publicRoutes,
|
|
...authRoutes,
|
|
|
|
// Setup wizards (fullscreen, fora do AppLayout)
|
|
...therapistStandalone,
|
|
...clinicStandalone,
|
|
|
|
// ─── AppLayout compartilhado ───────────────────────────
|
|
{
|
|
path: '/',
|
|
component: AppLayout,
|
|
children: [
|
|
therapistRoutes, // path: 'therapist'
|
|
adminRoutes, // path: 'admin'
|
|
accountRoutes, // path: 'account'
|
|
billingRoutes, // path: 'upgrade'
|
|
supervisorRoutes, // path: 'supervisor'
|
|
featuresRoutes, // path: 'features'
|
|
configuracoesRoutes, // path: 'configuracoes'
|
|
]
|
|
},
|
|
|
|
// AppLayout próprio (usuários completamente distintos)
|
|
saasRoutes,
|
|
editorRoutes,
|
|
portalRoutes,
|
|
|
|
// Catch-all (sempre por último)
|
|
...miscRoutes,
|
|
];
|
|
```
|
|
|
|
### `src/router/routes.therapist.js` e `routes.clinic.js`
|
|
|
|
Setup wizards (tela cheia, sem layout) foram separados como `export const therapistStandalone` / `clinicStandalone` e continuam com paths absolutos (`/therapist/setup`, `/admin/setup`).
|
|
|
|
A rota principal passou de:
|
|
```js
|
|
{ path: '/therapist', component: AppLayout, children: [...] }
|
|
```
|
|
Para:
|
|
```js
|
|
{ path: 'therapist', component: RouterPassthrough, meta: {...}, children: [...] }
|
|
```
|
|
|
|
### `src/router/routes.configs.js`
|
|
|
|
Removido o double-nesting desnecessário. Antes havia:
|
|
```js
|
|
{ path: '/configuracoes', component: AppLayout,
|
|
children: [
|
|
{ path: '', component: ConfiguracoesPage, children: [...] }
|
|
]
|
|
}
|
|
```
|
|
|
|
Depois, `ConfiguracoesPage` (que já tem `<router-view>` próprio) assume diretamente:
|
|
```js
|
|
{ path: 'configuracoes', component: ConfiguracoesPage,
|
|
redirect: { name: 'ConfiguracoesAgenda' },
|
|
meta: { requiresAuth: true, roles: [...] },
|
|
children: [...]
|
|
}
|
|
```
|
|
|
|
### `src/router/guards.js`
|
|
|
|
`/configuracoes` adicionado ao `isTenantArea` para garantir que o tenant e entitlements são carregados corretamente ao acessar a URL diretamente:
|
|
|
|
```js
|
|
const isTenantArea =
|
|
to.path.startsWith('/admin') ||
|
|
to.path.startsWith('/therapist') ||
|
|
to.path.startsWith('/supervisor') ||
|
|
to.path.startsWith('/configuracoes');
|
|
```
|
|
|
|
### `src/App.vue`
|
|
|
|
Mesma correção no `isTenantArea` local, que controla o `checkSetupWizard`.
|
|
|
|
---
|
|
|
|
## O que foi mantido separado (e por quê)
|
|
|
|
| Área | Motivo para manter AppLayout próprio |
|
|
|---|---|
|
|
| `/saas` | Usado exclusivamente por `saas_admin`. Nunca há navegação cruzada com áreas de tenant. |
|
|
| `/editor` | Role de plataforma distinto. Área separada. |
|
|
| `/portal` | Sessão de paciente. Usuário completamente diferente. |
|
|
|
|
Mesclar essas áreas no AppLayout compartilhado não traria ganho algum e criaria acoplamento desnecessário entre contextos de usuário distintos.
|
|
|
|
---
|
|
|
|
## Debug removido na mesma sessão
|
|
|
|
Foram removidos resquícios de código de debug que estavam ativos em produção e impactavam **todos** os usuários:
|
|
|
|
- **`router/index.js`**: `console.trace()` injetado em `router.push` e `router.replace` a cada clique de navegação (gerava stack trace completo).
|
|
- **`App.vue`**: `debugSnapshot()` disparava 3 queries Supabase (`auth.getUser`, `profiles`, `rpc.my_tenants`) a **cada troca de rota**, causando o principal sintoma de lentidão.
|
|
|
|
O sistema de suporte técnico (`supportGuard` + `supportDebugStore`) **não foi afetado** — é uma feature controlada que só ativa via `?support=TOKEN` validado no banco.
|
|
|
|
---
|
|
|
|
## Meta inheritance
|
|
|
|
O Vue Router 4 mescla automaticamente o `meta` de todos os registros de rota correspondidos (`to.matched`). Isso significa que os filhos do `RouterPassthrough` herdam o `meta` do pai sem nenhuma configuração adicional.
|
|
|
|
Exemplo: ao navegar para `/therapist/agenda`, `to.meta` conterá:
|
|
```js
|
|
{
|
|
area: 'therapist', // herdado do pai 'therapist'
|
|
requiresAuth: true, // herdado do pai 'therapist'
|
|
roles: ['therapist'], // herdado do pai 'therapist'
|
|
feature: 'agenda.view' // da própria rota 'agenda'
|
|
}
|
|
```
|
|
|
|
Os guards continuam funcionando sem nenhuma mudança de lógica.
|