Adiciona display: flex + flex-direction: column + gap: 8px no
.p-accordion pra os AccordionPanels nao colarem um no outro
quando todos estao fechados.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Volta -webkit-line-clamp: 2 no .mcfg-grp-desc e
.mcfg-nav-item__desc — descricao quebra em 2 linhas se precisar
ao inves de cortar com ellipsis em 1 linha.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Restaura label + desc tanto no header do grupo quanto no sub-item,
com altura confortavel (min-height 44px no item). Optimizacoes
mantidas pra evitar lag:
- Sem -webkit-line-clamp (custoso com o backdrop-filter do parent);
desc 1 linha com text-overflow: ellipsis
- Icone inline (16-18px, sem caixa 32x32 + box-shadow transition)
- Sem transition de color-mix em hover
Hover/active continuam com cor primary (12-16%). Desc no hover/active
ganha tom misturado primary 70% pra ficar legivel sem competir com
o label.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Continua usando o <Accordion> do PrimeVue, mas DOM/CSS bem mais leves:
- Header: icone inline + label + badge (era icone 36x36 caixa
centralizada + label + desc 2-linhas + badge)
- Sub-itens: botao denso 1-linha (icone + label, desc vai pro
title="" como tooltip nativo)
- Removidas transitions de box-shadow e color-mix nos hovers
(eram custosas com o backdrop-filter blur do parent)
- Removidas as line-clamp 2-linhas em cada item (~30 itens
abertos = ~30 layouts caros)
- Hover/active dos sub-itens com color-mix(--p-primary-color),
alinhado com o padrao do MelissaMenu (.mm-foot-item)
- Padding compactado, gap entre items 1px (era 6px)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Antes a sidebar tinha 4 items (Aparencia / Plano de fundo / Relogio /
Cronometro), cada um abria uma sessao separada. Agora vira 1 unico
item "Layout Melissa" com os 4 cards stackeados em uma tela so.
- grupos[0].items reduzido pra 1 entrada (key: aparencia, label:
Layout Melissa)
- INLINE_KEYS so tem 'aparencia' agora
- DEPRECATED_ALIASES adicionado: /melissa/fundo, /melissa/relogio,
/melissa/cronometro -> 'aparencia' (URLs antigas continuam abrindo
a tela unificada)
- Template: 4 v-if/v-else-if -> 1 <template v-if> com os 4 .mcfg-w
como siblings
- MelissaLayout SECOES.aparencia label: "Configuracoes do Melissa"
-> "Layout Melissa" (icon palette)
- MelissaMenu CATEGORIAS aparencia label idem
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Se o termo bate com o label da categoria (ex: "financeiro"),
inclui todos os sub-itens dessa categoria nos resultados. Antes
so casava por label do sub-item.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Input com pi-search a esquerda + botao limpar a direita. Quando
query tem texto, substitui a lista de categorias por uma lista
flat de sub-itens que casam (com nome da categoria a direita
como breadcrumb). Click no resultado dispara clicarSubItem (mesma
logica de navegacao) e limpa o termo. Empty state pra "nenhum
resultado".
Visual segue mm-aside: bg --m-bg-soft, border --m-border, focus
border --p-primary-color. Hover dos resultados usa color-mix
primary 12% (mesmo pattern do .mm-foot-item).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cada .mm-cat (exceto a ultima) ganha um ::after de 1px que sai
colado na borda esquerda e some no meio via linear-gradient
(--m-border-strong -> transparent). Da uma separacao visual
sutil sem precisar de border-bottom solido.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
.mm-sub__icon e .mm-link-row__icon agora usam --p-primary-color
em vez de --m-text-muted.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Hover e is-active dos .mm-foot-item agora usam --p-primary-color (text +
icone + bg color-mix 12-16%). Trocado "Meus Planos" -> "Meu Plano"
(singular). Cada item ganha is-active baseado em props.secaoAtiva
(perfil/plano/negocio/seguranca), Modo escuro fica is-active quando
isDarkTheme = true, Cores do Tema mantem is-active baseado em
themeViewActive.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Substitui o pill "Ligado/Desligado" por um switch CSS visual (track
+ thumb que desliza). Usa --p-primary-color quando ligado. O button
mantem o toggleDarkAndPersist no click + aria-pressed pra leitor de
tela. Switch tem aria-hidden pq a semantica vive no botao.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Antes o menu sempre abria em "Agenda e Pacientes" (CATEGORIAS[0]).
Agora resolve qual categoria contem o sub-item secaoAtiva e abre
direto nela: ex. estando em /melissa/financeiro-lancamentos, o menu
abre ja em "Financeiro". Sem secaoAtiva (resumo), mantem default
agenda-pacientes.
Watcher em props.secaoAtiva sincroniza o destaque se o user troca de
sessao com o menu aberto.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Em mobile cada .mf-card tem min/max-height 300px com scroll no body
(flex: 1 + min-height: 0 + overflow-y: auto). Garante que o "Ultimos
lancamentos" fique visivel e cada card tenha altura previsivel sem
consumir toda a altura do .mf-body. Chart wrap vira flex: 1 dentro
do body de 300px - head.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Em mobile reseta flex: 1 dos cards do row 50/50 (chart + projecao)
pra altura natural. O flex: 1 do desktop fazia os cards consumirem
toda a altura do .mf-body e empurrar o "Ultimos lancamentos" pra
fora do scroll visivel. Agora todos sizam por content em mobile.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrappa "Receita x Despesa" e "Projecao de Caixa" em .mf-cards-row
flex 50/50 lado a lado em desktop (alturas iguais via align-items:
stretch + body flex: 1 com scroll interno). "Ultimos lancamentos"
fica full-width abaixo.
Mobile empilha (column), chart wrap volta pra altura fixa 240px,
body sem scroll interno (.mf-body ja scrolla).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Inverte a ordem dentro do .mr-charts-row via flex order — "Sessoes no
periodo" (tabela) em 1o lugar, "Sessoes por mes/semana" (chart) em 2o.
Vale pra desktop (esquerda/direita) e mobile (em cima/embaixo).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Wrappa "Sessoes por mes/semana" e "Sessoes no periodo" em .mr-charts-row
flex 50/50 lado a lado em desktop. Mobile empilha (column) e a tabela
ganha min-height: 360px pra nao colapsar pra ~50px em telas curtas.
Chart wrap/skel viram flex: 1 dentro do card-chart pra acompanhar a
altura compartilhada da row.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
No mobile o botao "Menu Lancamentos/Notificacoes/etc" ja indica a sessao,
entao o pi-list/pi-bell ao lado do contador era redundante. Adiciona
.<prefix>-page__title-icon { display: none; } no @media max-width: 1023px.
Em MelissaConversas usa > i:first-child (icone nao tem classe dedicada).
Pages: FinanceiroLancamentos, Compromissos, Documentos, CadastrosRecebidos,
Conversas, AgendamentosRecebidos, Financeiro, Grupos, Notificacoes, Tags,
Medicos, Relatorios, Recorrencias.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Refator do mobile drawer em todas as Melissa Pages com sidebar:
scroll move pra dentro de .xx-side__scroll (flex: 1 + min-height: 0)
e o __footer vira flex-shrink: 0 last child de flex column. Espelha
o pattern do AppMenu/layout-sidebar Rail. Substitui o sticky/margin:auto
que falhava quando o conteudo era pequeno (deixava espaco vazio sob
o "Limpar filtros").
Pages: Compromissos, Conversas, Documentos, FinanceiroLancamentos,
Grupos, Medicos, Notificacoes, Pacientes, Recorrencias, Relatorios, Tags.
Pacientes (caso especial): mp-quick fixo no topo (max-height: 50%)
+ mp-side flex: 1 com scroll/footer interno.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Tres ajustes globais nas Melissa Pages com sidebar:
1) FOOTER "Limpar filtros" colado no bottom do drawer mobile
Problema: o sticky bottom precisa que algum container parent
tenha altura definida e overflow. No drawer, o `.xx-side` tinha
`height: auto` — entao o footer ficava no fluxo natural (logo
apos os cards) mesmo com pouco conteudo, em vez de empurrado pro
bottom do drawer.
Fix: `.xx-mobile-drawer__scroll .xx-side` ganha
`flex: 1; min-height: 0; display: flex; flex-direction: column`
pra ocupar altura disponivel; o `.xx-side__footer` ganha
`margin: auto -12px -24px` (margin-top: auto empurra pro fim).
Sticky bottom continua pro caso de scroll com muito conteudo.
Aplicado em: Compromissos, Grupos, Tags, Medicos, Conversas,
Recorrencias, Pacientes (caso especial — separa .mp-side de
.mp-quick), Cadastros Recebidos, FinanceiroLancamentos.
2) DRAWER MOBILE adicionado em Notificacoes, Documentos e
Relatorios (estavam com sidebar virando topo via max-height
50vh — faltava o pattern oficial das demais Melissa Pages).
Pattern aplicado:
- Aside host com id="<prefix>-mobile-drawer-target" + Transition
backdrop com fade
- Botao "Menu <Secao>" no header (esquerda do titulo)
- <Teleport :disabled="!isMobile"> envolvendo a sidebar
- Script: drawerOpen + isMobile + matchMedia listener registrado
no onMounted, removido no onBeforeUnmount
- CSS completo: .xx-mobile-drawer (fixed, transform translateX),
__scroll (overflow + padding), __backdrop (rgba 0.45 + blur),
overrides quando teleportada (sidebar perde bg/border-right,
footer vira sticky bottom com margin-top auto)
3) Botao "Menu" passa a ter sufixo da pagina:
- "Menu Lancamentos" (FinanceiroLancamentos)
- "Menu Notificacoes" (Notificacoes)
- "Menu Documentos" (Documentos)
- "Menu Relatorios" (Relatorios)
- "Menu Agendamentos" (AgendamentosRecebidos — corrigido tambem)
4) Bug de "lista vazia ao carregar via URL direto":
FinanceiroLancamentos e Relatorios usam composables que dependem
de tenantStore.activeTenantId. Quando aberta direto via URL
(sem navegar pelo menu), o tenantStore pode nao estar inicializado
ainda — entao fetchRecords() / loadSessions() retornam vazio.
Fix: adicionar `await tenantStore.ensureLoaded()` no onMounted
antes do fetch. Ja era pattern usado em outras Melissa Pages
(Compromissos, etc).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Trabalho de continuidade pós-blueprint:
A) Botao "Restaurar" visivel direto na linha da PatientsListPage
(layout Rail) quando paciente.status === 'Arquivado' — atalho
pra usuarios que filtram por arquivados sem precisar abrir o
menu de "..." (que ja tinha "Reativar" via PatientActionMenu).
Icone pi-undo + label "Restaurar" + tooltip + click chama
reactivatePatient do usePatientLifecycle. Aplicado tanto no
DataTable desktop quanto nos cards mobile.
B) Consolidacao: removido restorePatient do patientsRepository
(era duplicado com reactivatePatient do usePatientLifecycle).
MelissaPacientes agora consome reactivatePatient direto, fonte
unica de verdade pra toda transicao de status pra 'Ativo'.
C) MelissaLinkExterno (nova pagina nativa Melissa). Substitui o
embed via MelissaEmbed que duplicava 3 headers (layout + embed
+ hero sticky da pagina interna). Lógica preservada (RPC
issue_patient_invite + rotate_patient_invite_token_v2 +
copy/openLink), so o chrome muda pra casar com o blueprint
Melissa: 1 header com status pill (Link ativo/Gerando) +
botao "Gerar novo link" + Recarregar + Voltar; subheader
explicativo; body 2-col (esquerda card "Seu link publico" com
InputGroup + 2 CTAs grandes + card "Mensagem pronta"; direita
cards "Como funciona" + "Boas praticas"); mobile vira 1-col.
PatientsExternalLinkPage continua intacta — segue funcionando
no layout Rail. Wire-up no MelissaLayout: import +
render block + 'link-externo' literal em NON_CONFIG_SLUGS;
removido de MELISSA_EMBED_KEYS. Entry removido do EMBED_MAP
no MelissaEmbed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HANDOFF.md reescrito refletindo estado atual: working tree limpa,
5 commits criados na sessao, resumo do que foi feito (6 Melissa Pages
blueprint + dialogs harmonizados + ConversationDrawer WhatsApp +
bug fix de cores no MelissaPacientes), e o que continua pendente
(A66 V2 design aguardando feedback + restore na PatientsListPage)
- Obsidian/Brain/log.md: entrada da sessao 05-06 anexada com detalhes
e referencias dos 5 commits
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Remove database-novo/backups/2026-03-27 e 2026-03-29 (deveriam estar
no gitignore, mas haviam sido tracked antes)
- Atualiza db.config.json + generate-dashboard.cjs + dashboard.html
- HANDOFF.md atualizado com estado de 05-05 (sprint blueprint tabular +
arquivamento de pacientes)
- Obsidian/Brain/log.md: entrada da sessao 05-05 adicionada
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprint F (05-06). Blueprint tabular aplicado nas 6 paginas restantes;
dialogs harmonizados (FloatLabel + IconField + variant=filled + section
dividers, espelhando PatientsCadastroPage Identidade); ConversationDrawer
repaginado pra visual estilo WhatsApp.
Pages refatoradas (cada uma com subheader, sidebar __scroll + __footer
fixo "Limpar filtros", Xs inline pra zerar filtro individual, mobile
drawer com sticky footer):
- MelissaCompromissos: blueprint mantendo row design original (color
stripe + name + badges + descricao + meta inline). Filtros Status
(Ativos/Inativos) + Tipo (Nativos/Meus). Coluna Acoes frozen 140px
com toggle+pencil+trash.
- MelissaGrupos / MelissaTags: pattern completo + dialog "Pacientes
do grupo/tag" com lista vinculada via patient_group_patient /
patient_patient_tag. Avatar primary nos pacientes, header colorido
com cor da entidade, X de fechar igual .mc-close. Dialog de
criar/editar com FloatLabel + section dividers.
- MelissaMedicos: blueprint + dialog "Pacientes encaminhados" usando
cor primary do tema (medicos nao tem cor propria); dialog de
criar/editar com 4 secoes (Identificacao/Contato/Localizacao/Obs)
espelhando PatientsCadastroPage. Service ja tinha
fetchPatientsByMedicoNome (ILIKE em encaminhado_por).
- MelissaConversas: subheader, sidebar com bg-soft + border-right e
cards com sombra (mw-w--side), Limpar filtros global no footer fixo
(fix bug: filters era ref({...}) e eu lia filters.search direto, agora
usa .value), alerta de unlinked movido pro topo, kanban mobile com
min-height nas colunas pra mostrar mensagens.
- MelissaRecorrencias: subheader, button list de status (Ativas verde/
Encerradas vermelho/Todas) substitui SelectButton, busca por nome do
paciente, footer Limpar filtros, X inline no filtro Status.
ConversationDrawer redesign (WhatsApp-style):
- Header com avatar circular primary + iniciais + numero formatado
- Container de mensagens com bg "papel de parede" (color-mix com bege
esverdeado WA + radial-gradient pattern)
- Bolhas com cantos certos (top-left ou top-right zerado simulando
tail), sombra sutil, cores autenticas (#d9fdd3 light/#005c4b dark
outbound; #fff/#202c33 inbound), detecao dark via :global
- Time HH:MM + status overlay no canto inferior direito DENTRO do
balao; checks azuis quando lida (#53bdeb)
- Compose pill rounded-full + botao Send circular verde #00a884
- Removido fmtDateTime obsoleto (substituido por fmtTimeOnly)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sprints B (05-03) e C (05-04) acumulados:
- NotificationDrawer/Item redesign (visual mais limpo, ações inline)
- Dock pins compose (useMelissaDockPins) + cache store global (melissaCacheStore)
- MelissaAgenda: timeline FullCalendar parity + cards resumo, histórico
card com useMelissaAgendaHistorico, MelissaEventoPanel ajustado
- useFeriados: cache opt-in pra evitar fetch redundante de feriados
- PatientProntuario: aba Visão Geral nova; PatientConversationsTab polish
- AgendaClinicMosaic / AgendaTerapeutaPage / useAgendaSettings: ajustes
de paridade com Melissa
- DocumentsListPage: pequenos ajustes
- DB migration 20260504000001: fix do trigger pra status 'excluido' nas
cancel_notifications
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CLAUDE.md com 3 secoes (navegacao, regras de sessao, doc pra equipe).
Vault Obsidian/Brain/ commitado pra time editar conhecimento curado.
graphify-out/ no gitignore (regeneravel via /graphify src/).
Binarios do Obsidian.exe ignorados, so vault Brain/ vai pro repo.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Exporta classifySendError (era privada do store) pra poder testar
isolada. A funcao e' deterministica e pura, entao spec direto vale
mais que stub do supabase.functions.invoke.
Cobertura:
- 5xx downstream (502/503/504) -> banner "fora do ar" com 2 CTAs
(Configurar + Comprar creditos), incluindo o case sem code
- http_500 explicitamente NAO cai no ramo 5xx (e' catch geral, nao
"downstream fora") — checagem de regressao
- insufficient_credits, canal nao configurado/inativo (3 variacoes
de string), credenciais evolution/twilio incompletas
- evolution retornou X (com e sem status 5xx — confirma precedencia
dos ramos), twilio_send_failed_<code>
- auth (sessao expirou), forbidden (sem permissao) — ambos sem CTA
- "Edge Function returned a non-2xx" wrapper do supabase-js
- Fallback generico: code desconhecido com message custom; code+message
vazios -> mensagem padrao
- Robustez: case-insensitive (INSUFFICIENT_CREDITS -> reconhece),
status nao-numerico -> null em vez de NaN, codes nao-string
(undefined/number/object) nao quebram
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
10 botoes da pagina (header, filtros side, acoes inline do card,
fechar selecao da quick view) usavam o atributo HTML title nativo,
fora da convencao do projeto. Substitui por v-tooltip do PrimeVue
(auto-registrado via PrimeVueResolver) com posicao explicita por
contexto: bottom no header, top nas acoes, left no close da detail.
Sem mudanca funcional — apenas visual e de consistencia.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Migration nova (database-novo/migrations/20260427000001_*):
- ALTER TABLE user_settings ADD COLUMN melissa_prefs jsonb DEFAULT '{}'
NOT NULL — guarda toqueTermino, overlayOpacity, bgImageOpacity, use24h,
cardsAtivos[] e cardsLayout. Sanitizacao no client antes do upsert.
- bgUrl (data URL da foto, MBs) NAO entra aqui — segue em localStorage
ate migrarmos pra Supabase Storage.
Schema canonico (tenants_multi_tenant.sql) atualizado em paralelo:
- mesma coluna melissa_prefs jsonb
- check de layout_variant agora aceita 'melissa' alem de 'classic' e
'rail' (precondicao pra plugar o tema Direcao B no preference real)
Leitura/escrita no client ainda pendente — feita em sessao separada.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Roteamento por URL (substitui o ref local secaoAberta):
- routes.misc.js: rota vira /preview/melissa/:secao? — param opcional
- MelissaLayout.vue: secaoAberta agora e computed do route.params.secao,
validado contra SECOES (chave invalida -> null). abrirSecao/fecharSecao
fazem router.push em vez de mutar ref. Habilita back/forward, refresh
e deep-link tipo /preview/melissa/agenda.
Pagina Pacientes (WIP, ainda nao wireada no slot do Layout):
- src/layout/melissa/MelissaPacientes.vue (novo, ~? linhas) — fullscreen
3-col espelhando MelissaAgenda: aside esquerda com filtros (status /
grupos / tags), lista central com cards + busca, quick view direita
com KPIs do paciente selecionado + acoes.
- Carrega pacientes (todos os status), grupos/tags do tenant, vinculos
patient_groups + patient_tags + session counts em paralelo.
- Integra PatientProntuario (overlay), PatientCadastroDialog,
PatientCreatePopover + ComponentCadastroRapido, e
conversationDrawerStore (acao WhatsApp da quick view).
useMelissaPacientes ganha opcao { onlyActive }:
- default true (compat com cards do resumo / cronometro / eventos hoje
— so faz sentido com ativos)
- false retorna Ativo + Inativo + Arquivado, pra uso na pagina nova
- select agora inclui data_nascimento (necessario pros KPIs da quick view)
Cronometro: zera ao parar — terminou a sessao, fica pronto pra proxima
sem precisar reabrir o popover.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Dois pontos de quebra agora:
- <xl (<=1279px) "compact": view-switcher (Dia/Semana/Mes/Lista) sai da
toolbar e entra no menu "Acoes" com check icon no ativo. Filtros
tambem migram pra dentro pra nao inflar a barra.
- <lg (<=1023px) "mobile": .ma-side e .ma-widgets viajam pra fora do
.ma-page via Teleport, num <aside class="ma-mobile-drawer"> sempre
presente no DOM (v-show controla display) — garante target valido
desde o mount. Botao "Menu" mobile-only aparece a esquerda do header.
Backdrop entre drawer e .ma-page com Transition de fade.
Bonus styles.scss: fix borda dupla do FullCalendar.
.fc-scrollgrid em light mode mantinha borda externa que somada com a
borda das celulas da ponta dava 2px na borda do calendario. Zera o
contorno do contairner — celulas (td/th) ja desenham a grade visual.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
O watch de scheduleProgressSave referenciava form.* antes da declaração
do reactive form, violando TDZ e quebrando a página inteira no load.
Move o watch pra depois da `const form = reactive(resetForm())`.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Sessão de domingo curta. Bug do chip resolvido pela manhã, polimento
da Agenda à tarde (toolbar + stats interativos), à noite extração do
MelissaEventoPanel novo (não testado em browser, fica pra amanhã).
Working tree não commitado: B3 (MelissaEventoPanel novo + handlers
no MelissaLayout + patient_id no normalize + defineExpose). Ver
seção "PENDENTE DE TESTE" no HANDOFF pra plano de validação.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
B1 — Toolbar
- Cluster Hoje + chevrons num pill único (mais coeso)
- Título com flex+ellipsis (some min-width:130px que truncava feio em
view Mês/Lista)
- Botão "Hoje" disabled visual (opacity 0.45) quando hoje cai no range
visível — antes ficava idêntico, sem affordance
- title="" → v-tooltip.top nos chevrons (memória: tooltips PrimeVue)
- focus-visible com outline accent em todos os botões da toolbar
- Visual refinado: padding/font-weight, view-btn ativo com box-shadow
B2 — Stats interativos
- Click no stat filtra fcEvents + sessoesHoje pelo predicado correspondente
(Total/Sessões/Realizadas/Faltas — feriados continuam sempre)
- Stat ativo ganha borda accent + bg color-mix
- Stats com value=0 ficam disabled (cursor:not-allowed, opacity 0.4)
- Click no stat ativo limpa o filtro
- Chip flutuante "Filtrando: X" no canto sup direito do FC, click limpa
- Tooltip dinâmico explicando a ação esperada
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- HANDOFF.md atualizado: bug do chip do cronômetro resolvido em 2026-04-27.
Causa-raiz documentada (múltiplos Teleports compartilhando target +
Transition>Element v-if gera comment placeholder VNode → emitsOptions:null
no shouldUpdateComponent) e fix oficial (Transition envolvendo Teleport).
- Backups locais 2026-03-23 removidos do índice (já estavam .gitignored,
apenas saneamento).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- Severidade dos toasts de validação: warn → error (não selecionar tenant,
valor < 1, > WA_ADJUST_MAX, nada removível, excede max removível)
- Remove :max do <InputNumber> no formulário — antes ele clampeava
silenciosamente o valor digitado pro máximo permitido, escondendo o erro.
Agora deixa o usuário digitar e estourar o toast vermelho do submit.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Fecha o gap de analytics que faltava: MRR/ARR de assinatura já existia,
mas não havia visão de receita dos créditos WhatsApp comprados via Asaas.
Banco (migration 20260423000011) — 4 RPCs saas_admin only:
- saas_wa_credits_revenue_stats(from, to): total arrecadado, count de
compras, tenants únicos, créditos vendidos, ticket médio.
- saas_wa_credits_top_packages(from, to): ranking top 10 pacotes por
revenue, consolida nome atual se pacote foi renomeado.
- saas_wa_credits_usage_summary(): snapshot atual de lifetime_purchased
vs lifetime_used vs current_balance + taxa de consumo.
- saas_wa_credits_revenue_evolution(from, to, bucket_days): série
temporal pra sparkline.
Todas com check is_saas_admin() no início + SECURITY DEFINER.
Frontend:
- useSaasCreditsAnalytics composable orquestra as 4 RPCs em paralelo
com seleção de período (30d/90d/6m/12m) que ajusta bucket_days
automaticamente.
- SaasCreditsRevenueCard.vue: 4 KPIs (receita + ticket médio, compras +
tenants, créditos vendidos, % consumo global), sparkline SVG com
indicador de tendência, ranking top 5 pacotes.
- Integrado no SaasDashboard logo antes da tabela "Distribuição por plano".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
=== 8.2 Botão "Lembrar paciente" na agenda ===
Edge nova send-session-reminder-manual:
- Recebe {event_id}, autoriza (member ativo do tenant), resolve template
lembrete_sessao (custom → default global), envia via Evolution, registra
outbound em conversation_messages + log em session_reminder_logs com
reminder_type='manual'.
- Reusa lógica do cron reminders (sanitização, fmt datas, render template)
mas sem janela/dedup — terapeuta pode redisparar quantas vezes quiser
(log usa UPSERT; UNIQUE (event_id, reminder_type) sobrescreve).
Migration 20260423000008 adiciona 'manual' ao CHECK constraint de
session_reminder_logs.reminder_type.
UI: botão verde pi-whatsapp no footer do AgendaEventDialog (só em edit
de sessão com paciente vinculado). Confirm dialog + toast + erros
amigáveis (no_phone, invalid_phone, no_active_channel, template_not_found,
forbidden, send_failed).
=== 8.3 Status sessão dispara mensagem ===
Migration 20260423000009 cria trigger AFTER UPDATE OF status em
agenda_eventos: quando status muda pra cancelado/remarcado/confirmado,
dispara edge send-session-status-notification via pg_net (não bloqueia
o UPDATE). Settings app.settings.supabase_url/service_role_key reusadas.
Edge nova send-session-status-notification:
- Body {event_id, old_status, new_status}
- STATUS_TEMPLATE_MAP: cancelado→cancelamento_sessao, remarcado→
remarcacao_sessao, confirmado→confirmacao_sessao.
- Respeita opt-out (conversation_optouts), canal ativo, template
existente (tenant-specific → global default). Skip silencioso em
caso de falta de config.
- Insere outbound em conversation_messages (sem log unique — múltiplas
mudanças de status geram múltiplas mensagens por design).
=== 8.4 Intake abandonado vira lead no CRM ===
Migration 20260423000010:
- Adiciona 'in_progress' e 'abandoned_lead' ao CHECK de
patient_intake_requests.status. Colunas last_progress_at e
lead_thread_key.
- RPC convert_abandoned_intake_to_lead(intake_id): cria mensagem
placeholder inbound no CRM do tenant (thread_key anon:{phone}) +
conversation_notes com resumo dos dados coletados + marca status.
Edge save-intake-progress:
- POST {token, nome_completo?, telefone?, email_principal?, ...}
- Whitelist de campos (ALLOWED_FIELDS) pra proteger contra POST
malicioso tentar setar status/owner/etc.
- Busca por token, set status='in_progress' se era 'new', atualiza
campos enviados + last_progress_at.
Edge convert-abandoned-intakes (cron):
- Body opcional {idle_minutes} (default 30).
- Varre patient_intake_requests status='in_progress' + last_progress_at
mais antigo que cutoff. Filtra só os com nome_completo OU telefone
(contato mínimo pra valer lead). Chama RPC pra cada um.
Hook no form público CadastroPacienteExterno:
- Watch em nome_completo, telefone, email_principal, onde_nos_conheceu
dispara scheduleProgressSave() com debounce 1.5s.
- savePartialProgress só chama a edge se tem nome OU telefone.
- Silent fail — autosave não é crítico.
Cron do convert-abandoned-intakes NÃO ativado automaticamente (igual
heartbeat/SLA). Template comentado não está na migration — admin
descomenta SELECT cron.schedule manualmente quando quiser ligar.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>