Melissa: promove rota oficial + redirect automatico da home

Sai do estado "preview/sandbox" e liga o Melissa como layout real
ativavel pelo user em Configuracoes -> Profile.

Mudancas:

- routes.misc.js: /preview/melissa/:secao? -> /melissa/:secao?,
  nome PreviewMelissa -> Melissa. Sem alias por compat (autorizado).

- router/index.js: novo beforeEach apos o supportGuard e antes do
  applyGuards. Quando to.name e' therapist.dashboard ou admin.dashboard
  E localStorage.layout_variant === 'melissa' E viewport >= 1280px,
  redireciona pra { name: 'Melissa' }. Le do localStorage (gravado pelo
  bootstrapUserSettings + setVariant) pra evitar esperar store do DB e
  evitar flash do shell antes do redirect. Bypassa mobile pq Melissa
  nao foi feito pra <xl e o effectiveVariant ja forca 'classic' la.

- MelissaLayout.vue: 2 chamadas router.push apontavam pra
  'PreviewMelissa', agora 'Melissa'. Header doc atualizado.

- useMelissaPacientes.js: comment doc citando /preview/melissa
  generalizado pra "sem session retorna vazio".

- ProfilePage.vue: card Melissa perde badge "Em construcao" e ganha
  badge "Beta". Texto explicativo perde "navegacao completa ainda
  nao esta integrada" e ganha "Ao salvar, sua proxima entrada na
  home cai direto no Melissa". Link /preview/melissa -> /melissa.
  Remove regra CSS .lv-card--wip orfa.

Tradeoff aceito: rotas especificas (/therapist/agenda etc.) seguem
no shell classico/rail. So a HOME do role e' interceptada pra /melissa.
Coerente com o desenho atual do MelissaLayout, que ja abre Agenda /
Pacientes / etc. como overlays internos via deep-link /melissa/<secao>.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-04-28 17:45:53 -03:00
parent 68d601e0f4
commit 76b58af9a1
5 changed files with 49 additions and 27 deletions
+5 -4
View File
@@ -12,7 +12,8 @@
* *
* Dados ainda em mock. Próxima fase pluga Supabase. * Dados ainda em mock. Próxima fase pluga Supabase.
* *
* Rota atual (sandbox): /preview/melissa * Rota: /melissa (com :secao? opcional). User com layout_variant='melissa'
* em user_settings e' redirecionado da home do role pra ca pelo router.
*/ */
import { ref, computed, watch, onMounted, onBeforeUnmount, provide } from 'vue'; import { ref, computed, watch, onMounted, onBeforeUnmount, provide } from 'vue';
import { useRouter, useRoute } from 'vue-router'; import { useRouter, useRoute } from 'vue-router';
@@ -93,7 +94,7 @@ const SECOES = {
}; };
// Seção ativa = param `:secao?` da rota. URL é a fonte da verdade pra // Seção ativa = param `:secao?` da rota. URL é a fonte da verdade pra
// permitir back/forward e deep-link (ex: /preview/melissa/agenda abre o // permitir back/forward e deep-link (ex: /melissa/agenda abre o
// overlay da agenda direto). Se a chave for inválida, vira null. // overlay da agenda direto). Se a chave for inválida, vira null.
const router = useRouter(); const router = useRouter();
const route = useRoute(); const route = useRoute();
@@ -107,11 +108,11 @@ function abrirSecao(key) {
workspaceOpen.value = false; workspaceOpen.value = false;
eventoSelecionado.value = null; eventoSelecionado.value = null;
if (key === secaoAberta.value) return; // no-op, evita push duplicado if (key === secaoAberta.value) return; // no-op, evita push duplicado
router.push({ name: 'PreviewMelissa', params: { secao: key } }); router.push({ name: 'Melissa', params: { secao: key } });
} }
function fecharSecao() { function fecharSecao() {
if (!secaoAberta.value) return; if (!secaoAberta.value) return;
router.push({ name: 'PreviewMelissa', params: {} }); router.push({ name: 'Melissa', params: {} });
} }
// Prefs de layout/UI (toque, fundo, opacidade, formato hora) // Prefs de layout/UI (toque, fundo, opacidade, formato hora)
@@ -6,8 +6,8 @@
* - withTenantFilter (defesa em profundidade — RLS cobre, mas blindamos no client) * - withTenantFilter (defesa em profundidade — RLS cobre, mas blindamos no client)
* - normalizeStatus client-side (DB pode ter 'ativo'/'active'/null/etc.) * - normalizeStatus client-side (DB pode ter 'ativo'/'active'/null/etc.)
* *
* Sem auth (sem uid ou tenant), retorna vazio sem erro — uso em /preview/melissa * Sem auth (sem uid ou tenant), retorna vazio sem erro — permite a pagina
* permite a página renderizar mesmo sem session. * renderizar mesmo sem session (defesa em profundidade).
* *
* Forma normalizada do paciente: * Forma normalizada do paciente:
* { id, nome, email, telefone, avatar_url, status, last_attended_at, created_at } * { id, nome, email, telefone, avatar_url, status, last_attended_at, created_at }
+29
View File
@@ -105,6 +105,35 @@ router.beforeEach(async (to) => {
await supportGuard(to, pinia); await supportGuard(to, pinia);
}); });
// ── Layout Melissa: home do role -> /melissa ─────────────────────────
// Quando o user gravou layout_variant='melissa' em user_settings, qualquer
// entrada na home do role (therapist.dashboard ou admin.dashboard) cai
// direto no layout Melissa. Tomada de decisão via localStorage (espelho
// gravado pelo bootstrapUserSettings + setVariant) — evita esperar a
// store carregar do DB e prevenir flash do shell clássico antes do
// redirect. Bypassa em mobile (<1280px), onde Melissa não foi feito pra
// rodar e o effectiveVariant do layout.js já força 'classic'.
const HOME_ROUTE_NAMES = new Set(['therapist.dashboard', 'admin.dashboard']);
router.beforeEach((to) => {
if (!HOME_ROUTE_NAMES.has(to.name)) return;
let variant = null;
try {
variant = localStorage.getItem('layout_variant');
} catch {
// localStorage indisponível (modo privado / SSR) — segue o caminho normal
return;
}
if (variant !== 'melissa') return;
const isMobile = typeof window !== 'undefined'
&& typeof window.matchMedia === 'function'
&& window.matchMedia('(max-width: 1279px)').matches;
if (isMobile) return;
return { name: 'Melissa' };
});
applyGuards(router); applyGuards(router);
export default router; export default router;
+8 -5
View File
@@ -23,14 +23,17 @@ export default {
component: () => import('@/views/pages/Landing.vue') component: () => import('@/views/pages/Landing.vue')
}, },
// Sandbox do layout Melissa (Direção B — lockscreen-style) // Layout Melissa (Direcao B — lockscreen-style).
// Standalone, sem auth, sem AppLayout. Promovido de /preview/dashboard-win11. // Fullscreen, fora do AppLayout — UI inteira do Melissa vive aqui.
// Param `:secao?` opcional reflete a seção aberta na URL (agenda, // Quem ativa: user com user_settings.layout_variant = 'melissa'
// (escolhido em /account/profile). O router.beforeEach do
// index.js redireciona automaticamente da home do role pra ca.
// Param `:secao?` opcional reflete a secao aberta na URL (agenda,
// pacientes, conversas, etc.) — permite deep-link, back/forward, // pacientes, conversas, etc.) — permite deep-link, back/forward,
// refresh preservando estado. // refresh preservando estado.
{ {
path: 'preview/melissa/:secao?', path: 'melissa/:secao?',
name: 'PreviewMelissa', name: 'Melissa',
component: () => import('@/layout/melissa/MelissaLayout.vue') component: () => import('@/layout/melissa/MelissaLayout.vue')
}, },
+5 -16
View File
@@ -1424,16 +1424,16 @@ onBeforeUnmount(() => {
</div> </div>
</button> </button>
<!-- Layout 3: Melissa (Direção B) Em construção --> <!-- Layout 3: Melissa (Direção B) -->
<button <button
class="lv-card lv-card--wip" class="lv-card"
:class="{ 'lv-card--active': layoutConfig.variant === 'melissa' }" :class="{ 'lv-card--active': layoutConfig.variant === 'melissa' }"
@click=" @click="
setVariant('melissa'); setVariant('melissa');
markDirty(); markDirty();
" "
> >
<span class="lv-card__badge">Em construção</span> <span class="lv-card__badge lv-card__badge--beta">Beta</span>
<div class="lv-card__preview lv-card__preview--melissa"> <div class="lv-card__preview lv-card__preview--melissa">
<div class="lv-pm__clock">12<span class="lv-pm__sep">:</span>30</div> <div class="lv-pm__clock">12<span class="lv-pm__sep">:</span>30</div>
<div class="lv-pm__cards"> <div class="lv-pm__cards">
@@ -1459,8 +1459,8 @@ onBeforeUnmount(() => {
<i class="pi pi-info-circle text-[var(--text-color-secondary)] mt-0.5 shrink-0" /> <i class="pi pi-info-circle text-[var(--text-color-secondary)] mt-0.5 shrink-0" />
<div class="text-[1rem] text-[var(--text-color-secondary)] leading-relaxed"> <div class="text-[1rem] text-[var(--text-color-secondary)] leading-relaxed">
<strong>Rail:</strong> ícones no canto esquerdo + painel expansível. Disponível apenas no desktop.<br /> <strong>Rail:</strong> ícones no canto esquerdo + painel expansível. Disponível apenas no desktop.<br />
<strong>Melissa</strong> <span class="text-[0.85rem] uppercase tracking-wider font-semibold" style="color: var(--primary-color)"> em construção:</span> layout fullscreen com resumo do dia, busca rápida e cronômetro de sessão. Selecionar aqui salva sua preferência, mas a navegação completa ainda não está integrada. Acesse o preview em <strong>Melissa</strong> <span class="text-[0.85rem] uppercase tracking-wider font-semibold" style="color: var(--primary-color)"> beta:</span> layout fullscreen com resumo do dia, busca rápida e cronômetro de sessão. Ao salvar, sua próxima entrada na home cai direto no Melissa. Pra abrir manualmente,
<a href="/preview/melissa" target="_blank" rel="noopener" class="underline hover:text-[var(--text-color)]">/preview/melissa</a>. <a href="/melissa" target="_blank" rel="noopener" class="underline hover:text-[var(--text-color)]">/melissa</a>.
</div> </div>
</div> </div>
</div> </div>
@@ -1735,17 +1735,6 @@ onBeforeUnmount(() => {
} }
/* ─── Card Melissa (Direção B) — preview Win11 lockscreen ──── */ /* ─── Card Melissa (Direção B) — preview Win11 lockscreen ──── */
.lv-card--wip {
/* Listras suaves no fundo do card pra reforçar visualmente "em obras",
sem prejudicar o preview no topo. */
background-image: repeating-linear-gradient(
135deg,
transparent 0,
transparent 12px,
color-mix(in srgb, var(--primary-color) 4%, transparent) 12px,
color-mix(in srgb, var(--primary-color) 4%, transparent) 14px
);
}
.lv-card__badge { .lv-card__badge {
position: absolute; position: absolute;
top: 8px; top: 8px;