From 0658e2e9bf0bc32afdaaa1c8dbb1dafcdd67868a Mon Sep 17 00:00:00 2001 From: Leonardo Date: Wed, 25 Mar 2026 12:14:43 -0300 Subject: [PATCH] =?UTF-8?q?Adicionada=20compress=C3=A3o=20Brotli/Gzip,=20a?= =?UTF-8?q?uto-import=20de=20Vue=20e=20PrimeVue,=20e=20an=C3=A1lise=20visu?= =?UTF-8?q?al=20do=20bundle=20para=20otimiza=C3=A7=C3=A3o=20de=20produ?= =?UTF-8?q?=C3=A7=C3=A3o=20e=20Remove=20AppLayout=20duplicado=20de=20cada?= =?UTF-8?q?=20=C3=A1rea=20(therapist,=20admin,=20configuracoes,=20account,?= =?UTF-8?q?=20supervisor,=20billing,=20features)=20e=20consolida=20sob=20u?= =?UTF-8?q?m=20=C3=BAnico=20pai=20no=20router/index.js.=20Adiciona=20Route?= =?UTF-8?q?rPassthrough=20para=20grupos=20de=20rota=20sem=20layout=20inter?= =?UTF-8?q?medi=C3=A1rio.=20Remove=20debug=20ativo=20(console.trace=20em?= =?UTF-8?q?=20router.push=20e=20queries=20Supabase=20em=20todo=20watch=20d?= =?UTF-8?q?e=20rota)=20que=20degradava=20performance=20para=20todos=20os?= =?UTF-8?q?=20usu=C3=A1rios.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/architecture/router-shared-applayout.md | 194 ++++++++++ package-lock.json | 375 +++++++++---------- package.json | 3 +- src/App.vue | 118 +----- src/auto-imports.d.ts | 114 +++--- src/features/patients/PatientsListPage.vue | 14 +- src/layout/RouterPassthrough.vue | 1 + src/main.js | 102 ++--- src/router/guards.js | 6 +- src/router/index.js | 123 +++--- src/router/routes.account.js | 6 +- src/router/routes.billing.js | 6 +- src/router/routes.clinic.js | 375 ++++++++----------- src/router/routes.configs.js | 176 +++++---- src/router/routes.features.js | 11 +- src/router/routes.supervisor.js | 8 +- src/router/routes.therapist.js | 315 ++++++++-------- vite.config.mjs | 23 +- 18 files changed, 979 insertions(+), 991 deletions(-) create mode 100644 docs/architecture/router-shared-applayout.md create mode 100644 src/layout/RouterPassthrough.vue diff --git a/docs/architecture/router-shared-applayout.md b/docs/architecture/router-shared-applayout.md new file mode 100644 index 0000000..db21505 --- /dev/null +++ b/docs/architecture/router-shared-applayout.md @@ -0,0 +1,194 @@ +# 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 + +``` + +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 ``. + +--- + +## 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 `` 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. diff --git a/package-lock.json b/package-lock.json index b59f100..0a88ddf 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,9 +45,10 @@ "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.55.0", "tailwindcss": "^4.1.18", - "unplugin-auto-import": "^21.0.0", + "unplugin-auto-import": "^0.18.6", "unplugin-vue-components": "^0.27.3", "vite": "^5.3.1", + "vite-plugin-compression": "^0.5.1", "vitest": "^4.0.18" } }, @@ -3189,6 +3190,20 @@ "url": "https://github.com/sponsors/rawify" } }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3547,6 +3562,18 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -4764,9 +4791,9 @@ } }, "node_modules/strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", "dev": true, "dependencies": { "js-tokens": "^9.0.1" @@ -4947,28 +4974,25 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" }, "node_modules/unimport": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz", - "integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==", + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", "dev": true, "dependencies": { - "acorn": "^8.16.0", + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "pkg-types": "^2.3.0", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", "scule": "^1.3.0", - "strip-literal": "^3.1.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" - }, - "engines": { - "node": ">=18.12.0" + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" } }, "node_modules/unimport/node_modules/confbox": { @@ -5015,7 +5039,7 @@ "url": "https://github.com/sponsors/antfu" } }, - "node_modules/unimport/node_modules/pkg-types": { + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", @@ -5026,19 +5050,13 @@ "pathe": "^2.0.3" } }, - "node_modules/unimport/node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, "engines": { - "node": ">=18.12.0" + "node": ">= 10.0.0" } }, "node_modules/unplugin": { @@ -5055,26 +5073,28 @@ } }, "node_modules/unplugin-auto-import": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz", - "integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==", + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz", + "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==", "dev": true, "dependencies": { - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "picomatch": "^4.0.3", - "unimport": "^5.6.0", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "unimport": "^3.13.4", + "unplugin": "^1.16.0" }, "engines": { - "node": ">=20.19.0" + "node": ">=14" }, "funding": { "url": "https://github.com/sponsors/antfu" }, "peerDependencies": { - "@nuxt/kit": "^4.0.0", + "@nuxt/kit": "^3.2.2", "@vueuse/core": "*" }, "peerDependenciesMeta": { @@ -5086,69 +5106,28 @@ } } }, - "node_modules/unplugin-auto-import/node_modules/confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true - }, - "node_modules/unplugin-auto-import/node_modules/local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "node_modules/unplugin-auto-import/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "dependencies": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" + "balanced-match": "^1.0.0" + } + }, + "node_modules/unplugin-auto-import/node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.2" }, "engines": { - "node": ">=14" + "node": ">=16 || 14 >=14.17" }, "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/unplugin-auto-import/node_modules/pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "dependencies": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "node_modules/unplugin-auto-import/node_modules/unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "dependencies": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" - }, - "engines": { - "node": ">=18.12.0" - } - }, - "node_modules/unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "dev": true, - "dependencies": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - }, - "engines": { - "node": ">=20.19.0" - }, - "funding": { - "url": "https://github.com/sponsors/sxzz" + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/unplugin-vue-components": { @@ -5388,6 +5367,20 @@ } } }, + "node_modules/vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "dependencies": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.0.0" + } + }, "node_modules/vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", @@ -8189,6 +8182,17 @@ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", "dev": true }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -8439,6 +8443,16 @@ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true }, + "jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -9195,9 +9209,9 @@ "dev": true }, "strip-literal": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", - "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", "dev": true, "requires": { "js-tokens": "^9.0.1" @@ -9327,25 +9341,25 @@ "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==" }, "unimport": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/unimport/-/unimport-5.7.0.tgz", - "integrity": "sha512-njnL6sp8lEA8QQbZrt+52p/g4X0rw3bnGGmUcJnt1jeG8+iiqO779aGz0PirCtydAIVcuTBRlJ52F0u46z309Q==", + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", "dev": true, "requires": { - "acorn": "^8.16.0", + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", "escape-string-regexp": "^5.0.0", "estree-walker": "^3.0.3", - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "mlly": "^1.8.0", - "pathe": "^2.0.3", - "picomatch": "^4.0.3", - "pkg-types": "^2.3.0", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", "scule": "^1.3.0", - "strip-literal": "^3.1.0", - "tinyglobby": "^0.2.15", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" }, "dependencies": { "confbox": { @@ -9378,33 +9392,29 @@ "mlly": "^1.7.4", "pkg-types": "^2.3.0", "quansync": "^0.2.11" - } - }, - "pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", - "dev": true, - "requires": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "requires": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + }, + "dependencies": { + "pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "dev": true, + "requires": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + } } } } }, + "universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true + }, "unplugin": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", @@ -9416,71 +9426,41 @@ } }, "unplugin-auto-import": { - "version": "21.0.0", - "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-21.0.0.tgz", - "integrity": "sha512-vWuC8SwqJmxZFYwPojhOhOXDb5xFhNNcEVb9K/RFkyk/3VnfaOjzitWN7v+8DEKpMjSsY2AEGXNgt6I0yQrhRQ==", + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz", + "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==", "dev": true, "requires": { - "local-pkg": "^1.1.2", - "magic-string": "^0.30.21", - "picomatch": "^4.0.3", - "unimport": "^5.6.0", - "unplugin": "^2.3.11", - "unplugin-utils": "^0.3.1" + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "unimport": "^3.13.4", + "unplugin": "^1.16.0" }, "dependencies": { - "confbox": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", - "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", - "dev": true - }, - "local-pkg": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.1.2.tgz", - "integrity": "sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==", + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", "dev": true, "requires": { - "mlly": "^1.7.4", - "pkg-types": "^2.3.0", - "quansync": "^0.2.11" + "balanced-match": "^1.0.0" } }, - "pkg-types": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", - "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "requires": { - "confbox": "^0.2.2", - "exsolve": "^1.0.7", - "pathe": "^2.0.3" - } - }, - "unplugin": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-2.3.11.tgz", - "integrity": "sha512-5uKD0nqiYVzlmCRs01Fhs2BdkEgBS3SAVP6ndrBsuK42iC2+JHyxM05Rm9G8+5mkmRtzMZGY8Ct5+mliZxU/Ww==", - "dev": true, - "requires": { - "@jridgewell/remapping": "^2.3.5", - "acorn": "^8.15.0", - "picomatch": "^4.0.3", - "webpack-virtual-modules": "^0.6.2" + "brace-expansion": "^2.0.2" } } } }, - "unplugin-utils": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/unplugin-utils/-/unplugin-utils-0.3.1.tgz", - "integrity": "sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==", - "dev": true, - "requires": { - "pathe": "^2.0.3", - "picomatch": "^4.0.3" - } - }, "unplugin-vue-components": { "version": "0.27.5", "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", @@ -9604,6 +9584,17 @@ "rollup": "^4.20.0" } }, + "vite-plugin-compression": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/vite-plugin-compression/-/vite-plugin-compression-0.5.1.tgz", + "integrity": "sha512-5QJKBDc+gNYVqL/skgFAP81Yuzo9R+EAf19d+EtsMF/i8kFUpNi3J/H01QD3Oo8zBQn+NzoCIFkpPLynoOzaJg==", + "dev": true, + "requires": { + "chalk": "^4.1.2", + "debug": "^4.3.3", + "fs-extra": "^10.0.0" + } + }, "vitest": { "version": "4.0.18", "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.0.18.tgz", diff --git a/package.json b/package.json index 365735e..1ab31a1 100644 --- a/package.json +++ b/package.json @@ -50,9 +50,10 @@ "rollup-plugin-visualizer": "^7.0.1", "sass": "^1.55.0", "tailwindcss": "^4.1.18", - "unplugin-auto-import": "^21.0.0", + "unplugin-auto-import": "^0.18.6", "unplugin-vue-components": "^0.27.3", "vite": "^5.3.1", + "vite-plugin-compression": "^0.5.1", "vitest": "^4.0.18" } } diff --git a/src/App.vue b/src/App.vue index 8835083..dcf1bc5 100644 --- a/src/App.vue +++ b/src/App.vue @@ -19,7 +19,6 @@ 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'; @@ -28,25 +27,14 @@ import AppOfflineOverlay from '@/components/AppOfflineOverlay.vue'; const route = useRoute(); const router = useRouter(); const tenantStore = useTenantStore(); -const entStore = useEntitlementsStore(); function isTenantArea(path = '') { - return path.startsWith('/admin') || path.startsWith('/therapist'); -} - -function isPortalArea(path = '') { - return path.startsWith('/portal'); -} - -function isSaasArea(path = '') { - return path.startsWith('/saas'); + return path.startsWith('/admin') || path.startsWith('/therapist') || path.startsWith('/configuracoes'); } // ── 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; const uid = tenantStore.user?.id; @@ -56,7 +44,6 @@ async function checkSetupWizard() { 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'); @@ -68,113 +55,16 @@ async function checkSetupWizard() { } } -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(); - - // 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(); - - 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(); - } -} - -onMounted(async () => { - // 🔥 PRIMEIRO LOG — TENANT ID BRUTO (mantive sua ideia) - console.log('[SEU_TENANT_ID]', localStorage.getItem('tenant_id')); - - // snapshot inicial - await debugSnapshot('mounted'); - - // Carrega docs de ajuda para a rota inicial +onMounted(() => { 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(); + checkSetupWizard(); } } ); diff --git a/src/auto-imports.d.ts b/src/auto-imports.d.ts index ac15611..0693796 100644 --- a/src/auto-imports.d.ts +++ b/src/auto-imports.d.ts @@ -6,68 +6,68 @@ // biome-ignore lint: disable export {} declare global { - const EffectScope: typeof import('vue').EffectScope - const computed: typeof import('vue').computed - const createApp: typeof import('vue').createApp - const customRef: typeof import('vue').customRef - const defineAsyncComponent: typeof import('vue').defineAsyncComponent - const defineComponent: typeof import('vue').defineComponent - const effectScope: typeof import('vue').effectScope - const getCurrentInstance: typeof import('vue').getCurrentInstance - const getCurrentScope: typeof import('vue').getCurrentScope + const EffectScope: typeof import('vue')['EffectScope'] + const computed: typeof import('vue')['computed'] + const createApp: typeof import('vue')['createApp'] + const customRef: typeof import('vue')['customRef'] + const defineAsyncComponent: typeof import('vue')['defineAsyncComponent'] + const defineComponent: typeof import('vue')['defineComponent'] + const effectScope: typeof import('vue')['effectScope'] + const getCurrentInstance: typeof import('vue')['getCurrentInstance'] + const getCurrentScope: typeof import('vue')['getCurrentScope'] const getCurrentWatcher: typeof import('vue').getCurrentWatcher - const h: typeof import('vue').h - const inject: typeof import('vue').inject - const isProxy: typeof import('vue').isProxy - const isReactive: typeof import('vue').isReactive - const isReadonly: typeof import('vue').isReadonly - const isRef: typeof import('vue').isRef + const h: typeof import('vue')['h'] + const inject: typeof import('vue')['inject'] + const isProxy: typeof import('vue')['isProxy'] + const isReactive: typeof import('vue')['isReactive'] + const isReadonly: typeof import('vue')['isReadonly'] + const isRef: typeof import('vue')['isRef'] const isShallow: typeof import('vue').isShallow - const markRaw: typeof import('vue').markRaw - const nextTick: typeof import('vue').nextTick - const onActivated: typeof import('vue').onActivated - const onBeforeMount: typeof import('vue').onBeforeMount - const onBeforeUnmount: typeof import('vue').onBeforeUnmount - const onBeforeUpdate: typeof import('vue').onBeforeUpdate - const onDeactivated: typeof import('vue').onDeactivated - const onErrorCaptured: typeof import('vue').onErrorCaptured - const onMounted: typeof import('vue').onMounted - const onRenderTracked: typeof import('vue').onRenderTracked - const onRenderTriggered: typeof import('vue').onRenderTriggered - const onScopeDispose: typeof import('vue').onScopeDispose - const onServerPrefetch: typeof import('vue').onServerPrefetch - const onUnmounted: typeof import('vue').onUnmounted - const onUpdated: typeof import('vue').onUpdated - const onWatcherCleanup: typeof import('vue').onWatcherCleanup - const provide: typeof import('vue').provide - const reactive: typeof import('vue').reactive - const readonly: typeof import('vue').readonly - const ref: typeof import('vue').ref - const resolveComponent: typeof import('vue').resolveComponent - const shallowReactive: typeof import('vue').shallowReactive - const shallowReadonly: typeof import('vue').shallowReadonly - const shallowRef: typeof import('vue').shallowRef - const toRaw: typeof import('vue').toRaw - const toRef: typeof import('vue').toRef - const toRefs: typeof import('vue').toRefs - const toValue: typeof import('vue').toValue - const triggerRef: typeof import('vue').triggerRef - const unref: typeof import('vue').unref - const useAttrs: typeof import('vue').useAttrs - const useCssModule: typeof import('vue').useCssModule - const useCssVars: typeof import('vue').useCssVars - const useId: typeof import('vue').useId - const useModel: typeof import('vue').useModel - const useSlots: typeof import('vue').useSlots - const useTemplateRef: typeof import('vue').useTemplateRef - const watch: typeof import('vue').watch - const watchEffect: typeof import('vue').watchEffect - const watchPostEffect: typeof import('vue').watchPostEffect - const watchSyncEffect: typeof import('vue').watchSyncEffect + const markRaw: typeof import('vue')['markRaw'] + const nextTick: typeof import('vue')['nextTick'] + const onActivated: typeof import('vue')['onActivated'] + const onBeforeMount: typeof import('vue')['onBeforeMount'] + const onBeforeUnmount: typeof import('vue')['onBeforeUnmount'] + const onBeforeUpdate: typeof import('vue')['onBeforeUpdate'] + const onDeactivated: typeof import('vue')['onDeactivated'] + const onErrorCaptured: typeof import('vue')['onErrorCaptured'] + const onMounted: typeof import('vue')['onMounted'] + const onRenderTracked: typeof import('vue')['onRenderTracked'] + const onRenderTriggered: typeof import('vue')['onRenderTriggered'] + const onScopeDispose: typeof import('vue')['onScopeDispose'] + const onServerPrefetch: typeof import('vue')['onServerPrefetch'] + const onUnmounted: typeof import('vue')['onUnmounted'] + const onUpdated: typeof import('vue')['onUpdated'] + const onWatcherCleanup: typeof import('vue')['onWatcherCleanup'] + const provide: typeof import('vue')['provide'] + const reactive: typeof import('vue')['reactive'] + const readonly: typeof import('vue')['readonly'] + const ref: typeof import('vue')['ref'] + const resolveComponent: typeof import('vue')['resolveComponent'] + const shallowReactive: typeof import('vue')['shallowReactive'] + const shallowReadonly: typeof import('vue')['shallowReadonly'] + const shallowRef: typeof import('vue')['shallowRef'] + const toRaw: typeof import('vue')['toRaw'] + const toRef: typeof import('vue')['toRef'] + const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] + const triggerRef: typeof import('vue')['triggerRef'] + const unref: typeof import('vue')['unref'] + const useAttrs: typeof import('vue')['useAttrs'] + const useCssModule: typeof import('vue')['useCssModule'] + const useCssVars: typeof import('vue')['useCssVars'] + const useId: typeof import('vue')['useId'] + const useModel: typeof import('vue')['useModel'] + const useSlots: typeof import('vue')['useSlots'] + const useTemplateRef: typeof import('vue')['useTemplateRef'] + const watch: typeof import('vue')['watch'] + const watchEffect: typeof import('vue')['watchEffect'] + const watchPostEffect: typeof import('vue')['watchPostEffect'] + const watchSyncEffect: typeof import('vue')['watchSyncEffect'] } // for type re-export declare global { // @ts-ignore - export type { Component, Slot, Slots, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, ShallowRef, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue' import('vue') } diff --git a/src/features/patients/PatientsListPage.vue b/src/features/patients/PatientsListPage.vue index efc9996..82b89b3 100644 --- a/src/features/patients/PatientsListPage.vue +++ b/src/features/patients/PatientsListPage.vue @@ -15,21 +15,21 @@ |-------------------------------------------------------------------------- -->