Compare commits
2 Commits
629e7ce18e
...
76b58af9a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 76b58af9a1 | |||
| 68d601e0f4 |
@@ -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)
|
||||||
|
|||||||
@@ -474,13 +474,13 @@ function sessaoStatusColor(s) {
|
|||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
class="mp-header-btn"
|
class="mp-header-btn"
|
||||||
title="Ir pra Agenda"
|
v-tooltip.bottom="'Ir pra Agenda'"
|
||||||
@click="emit('goto-agenda')"
|
@click="emit('goto-agenda')"
|
||||||
>
|
>
|
||||||
<i class="pi pi-calendar text-xs" />
|
<i class="pi pi-calendar text-xs" />
|
||||||
<span>Ir pra agenda</span>
|
<span>Ir pra agenda</span>
|
||||||
</button>
|
</button>
|
||||||
<button class="mp-close" title="Voltar ao resumo (Esc)" @click="emit('close')">
|
<button class="mp-close" v-tooltip.bottom="'Voltar ao resumo (Esc)'" @click="emit('close')">
|
||||||
<i class="pi pi-times text-sm" />
|
<i class="pi pi-times text-sm" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -513,7 +513,7 @@ function sessaoStatusColor(s) {
|
|||||||
<button
|
<button
|
||||||
v-if="grupoFiltroId"
|
v-if="grupoFiltroId"
|
||||||
class="mp-side__clear"
|
class="mp-side__clear"
|
||||||
title="Limpar filtro de grupo"
|
v-tooltip.top="'Limpar filtro de grupo'"
|
||||||
@click="grupoFiltroId = null"
|
@click="grupoFiltroId = null"
|
||||||
>
|
>
|
||||||
<i class="pi pi-times text-[0.6rem]" />
|
<i class="pi pi-times text-[0.6rem]" />
|
||||||
@@ -543,7 +543,7 @@ function sessaoStatusColor(s) {
|
|||||||
<button
|
<button
|
||||||
v-if="tagFiltroId"
|
v-if="tagFiltroId"
|
||||||
class="mp-side__clear"
|
class="mp-side__clear"
|
||||||
title="Limpar filtro de tag"
|
v-tooltip.top="'Limpar filtro de tag'"
|
||||||
@click="tagFiltroId = null"
|
@click="tagFiltroId = null"
|
||||||
>
|
>
|
||||||
<i class="pi pi-times text-[0.6rem]" />
|
<i class="pi pi-times text-[0.6rem]" />
|
||||||
@@ -687,35 +687,35 @@ function sessaoStatusColor(s) {
|
|||||||
<div class="mp-card__actions" @click.stop>
|
<div class="mp-card__actions" @click.stop>
|
||||||
<button
|
<button
|
||||||
class="mp-card__action"
|
class="mp-card__action"
|
||||||
title="Abrir prontuário"
|
v-tooltip.top="'Abrir prontuário'"
|
||||||
@click="abrirProntuario(p)"
|
@click="abrirProntuario(p)"
|
||||||
>
|
>
|
||||||
<i class="pi pi-file" />
|
<i class="pi pi-file" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="mp-card__action"
|
class="mp-card__action"
|
||||||
title="Ver sessões"
|
v-tooltip.top="'Ver sessões'"
|
||||||
@click="verSessoes(p)"
|
@click="verSessoes(p)"
|
||||||
>
|
>
|
||||||
<i class="pi pi-history" />
|
<i class="pi pi-history" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="mp-card__action"
|
class="mp-card__action"
|
||||||
title="Conversar (WhatsApp)"
|
v-tooltip.top="'Conversar (WhatsApp)'"
|
||||||
@click="abrirWhatsapp(p)"
|
@click="abrirWhatsapp(p)"
|
||||||
>
|
>
|
||||||
<i class="pi pi-whatsapp" />
|
<i class="pi pi-whatsapp" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="mp-card__action"
|
class="mp-card__action"
|
||||||
title="Editar"
|
v-tooltip.top="'Editar'"
|
||||||
@click="editarPaciente(p)"
|
@click="editarPaciente(p)"
|
||||||
>
|
>
|
||||||
<i class="pi pi-pencil" />
|
<i class="pi pi-pencil" />
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
class="mp-card__action mp-card__action--danger"
|
class="mp-card__action mp-card__action--danger"
|
||||||
title="Arquivar"
|
v-tooltip.top="'Arquivar'"
|
||||||
@click="confirmarRemover(p)"
|
@click="confirmarRemover(p)"
|
||||||
>
|
>
|
||||||
<i class="pi pi-trash" />
|
<i class="pi pi-trash" />
|
||||||
@@ -758,7 +758,7 @@ function sessaoStatusColor(s) {
|
|||||||
|
|
||||||
<!-- Paciente selecionado: detalhes + ações -->
|
<!-- Paciente selecionado: detalhes + ações -->
|
||||||
<div v-if="pacienteSelecionado" class="mp-detail">
|
<div v-if="pacienteSelecionado" class="mp-detail">
|
||||||
<button class="mp-detail__close" title="Limpar seleção" @click="limparSelecao">
|
<button class="mp-detail__close" v-tooltip.left="'Limpar seleção'" @click="limparSelecao">
|
||||||
<i class="pi pi-times text-xs" />
|
<i class="pi pi-times text-xs" />
|
||||||
</button>
|
</button>
|
||||||
<div class="mp-detail__avatar">
|
<div class="mp-detail__avatar">
|
||||||
|
|||||||
@@ -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 }
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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')
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user