Melissa: paginas nativas cfg-* + temas + textos com fundo + drawer WA

CHROME COMPARTILHADO + 18 PAGINAS NATIVAS
- MelissaConfigPage: chrome unico (header, drawer mobile, sidebar com Configuracoes
  + FAQ slot, main com Suspense). Replica fake-dialog right rule e fica flush
  com o config-aside global.
- 18 wrappers finos (~25 linhas cada): cfg-precificacao, cfg-descontos,
  cfg-excecoes, cfg-convenios, cfg-wa, cfg-wa-pessoal, cfg-wa-oficial,
  cfg-wa-templates, cfg-conversas-tags/autoreply/optouts/sla/bots,
  cfg-lembretes, cfg-creditos-wa, cfg-sms, cfg-email-templates,
  cfg-recursos-extras, cfg-recursos-extras-extrato, cfg-auditoria.
- Cada wrapper usa defineAsyncComponent + Suspense pra evitar race com
  tenantStore no boot (loading travado em alguns chooser-style pages).
- MelissaLayout: imports + SECOES + MELISSA_NON_CONFIG_SLUGS + render
  conditions atualizados pra cobrir os 18 slugs.

PAGINAS LEGADAS DETECTAM CONTEXTO MELISSA
- ConfiguracoesWhatsappChooserPage, WhatsappPage, TwilioWhatsappPage,
  SmsPage, RecursosExtrasPage, EmailTemplatesPage, AddonsExtratoPage,
  AgendadorPage, ConversasAutoreplyPage: route.startsWith('/melissa')
  decide se router.push vai pro slug Melissa ou /configuracoes legado.
- Anchors <a href="/configuracoes/..."> (que recarregavam pagina e
  vazavam o usuario do Melissa) trocados por RouterLink context-aware.
- MelissaAgenda.goSettings agora vai pra /melissa/agenda-config.

PERSONALIZAR > TEMAS
- melissaThemes.js: catalogo Freud/Klein/Jung (wallpaper + cor primaria
  + preset Lara/Nora + surface).
- Toggle de tema aplica tudo de uma vez; persistido em melissa_prefs.themeName.
- Boot resolve themeName -> imagem via fetch + data URL (sem guardar
  data URL gigante no DB).
- onCustomFileChange/onClearBg invalidam themeName quando user mexe no bg.

PERSONALIZAR > FUNDO NOS TEXTOS
- Pref textBgEnabled em melissa_prefs.
- MelissaHeroClock: prop textBg envolve relogio/data/saudacao/resumo
  em <span class="hero-text"> que ganha bg branco/preto 60% + borda
  + padding + radius quando o toggle esta on.
- Vars --m-hero-text-bg / --m-hero-text-border flipam com light/dark.

TOP + DOCK COM GRADIENT HORIZONTAL
- Var --m-band: preto 80% (dark) / branco 80% (light).
- .melissa-topbar-band: gradiente cor->transparente (right->left) atras
  dos botoes do topo.
- .melissa-dock: gradiente cor->transparente (left->right) atras dos pins.

MELISSANEGOCIO ABSORVE MINHA EMPRESA
- Adiciona logo upload + preview "cartao de visita" (computeds
  enderecoLinhas/redesValidas/temDados/logoDisplay + redeIcon helper).
- Normaliza dados legados do cfg-empresa: redes_sociais.{rede} virou {name}.
- Preview teleporta entre 3 destinos baseado no viewport:
  mobile -> drawer; mid-desktop -> sidebar; wide-desktop (>=1340px) ->
  painel flutuante FORA do fake dialog (ancora no right edge + 14px gap,
  altura segue conteudo, header alinhado com header do dialog).
- Remove cfg-empresa de melissaConfigGrupos.js + COMPONENT_MAP do
  MelissaConfiguracoes; grupo "Empresa & Plataforma" -> "Plataforma".

CRONOMETRO -> SESSAO AGENDADA
- MelissaCronometro emite session-end ao parar com paciente selecionado
  (threshold 5s pra ignorar start/stop acidental).
- MelissaLayout.onCronometroSessionEnd busca agenda_eventos do paciente
  no dia (tipo='sessao'), pega o mais recente e grava em
  extra_fields.cronometro_duracao_seg + cronometro_parado_em.
- Toast: sucesso ("X min salvos") ou warn ("sessao nao encontrada").

CONVERSATIONDRAWER WHATSAPP-LIKE
- Nova imagem whatsapp-bg.jpg (renomeada de hash random) usada como
  tile (380px) no .cd-msgs.
- Light: bege #efeae2 + multiply blend.
- Dark: #0b141a + camada 78% sobre o doodle.
- Bubbles WA-style (verde out / branco-dark in com tails) ja existiam.

EXTRATO RECURSOS EXTRAS
- Filtros 2-por-linha em Melissa (vs 1/4 no /configuracoes).
- Cards de Resumo teleportam pro #cfg-page-side em Melissa
  (1-col empilhado no drawer; 4-col inline no /configuracoes).
- Botoes de exportar com flex-1 distribuidos em uma unica linha em
  desktop, wrap no mobile.
- DataTable scrollable em ambos os layouts.

OUTROS AJUSTES MENORES
- Cfg-conversas-autoreply: dias semana 4-cols em Melissa (vs 7-cols
  no /configuracoes).
- Cfg-creditos-wa: 1/2 por linha (vs 1/2/4) em Melissa.
- Cfg-recursos-extras: pacotes 1/2 (vs 1/2/4); "Em breve" 1-col.
- WhatsAppPage aba Templates: guia de formatacao teleporta pro side
  drawer em Melissa, deixando textareas full-width.
- ConfigPage chrome agora tem #cfg-page-actions target pros Teleport
  de acoes (refresh button etc).
- Imagens renomeadas em src/assets/themes/ (freudwebp/melainewebp/
  jungwebp.webp) e src/assets/whatsapp-bg.jpg.
- JoditTextEditor.vue novo (wrapper Jodit generico, sem features de email).
- MelissaConfigList.vue novo (lista compartilhada de configs pro drawer).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-07 23:48:18 -03:00
parent cc7841bd1f
commit 9966b5f175
60 changed files with 4785 additions and 713 deletions
+328 -6
View File
@@ -20,6 +20,7 @@ import { useRouter, useRoute } from 'vue-router';
import { useToast } from 'primevue/usetoast';
import { useLayout } from '@/layout/composables/layout';
import { applyThemeEngine, surfaces as THEME_SURFACES, presetOptions as THEME_PRESETS } from '@/theme/theme.options';
import { MELISSA_THEME_NAMES, findMelissaTheme } from './melissaThemes';
import { useUserSettingsPersistence } from '@/composables/useUserSettingsPersistence';
import MelissaCronometro from './MelissaCronometro.vue';
import MelissaCard from './MelissaCard.vue';
@@ -46,6 +47,26 @@ import MelissaBloqueios from './MelissaBloqueios.vue';
import MelissaAgendador from './MelissaAgendador.vue';
import MelissaAgendaConfig from './MelissaAgendaConfig.vue';
import MelissaPagamento from './MelissaPagamento.vue';
import MelissaPrecificacao from './MelissaPrecificacao.vue';
import MelissaDescontos from './MelissaDescontos.vue';
import MelissaExcecoes from './MelissaExcecoes.vue';
import MelissaConvenios from './MelissaConvenios.vue';
import MelissaCfgWa from './MelissaCfgWa.vue';
import MelissaCfgWaPessoal from './MelissaCfgWaPessoal.vue';
import MelissaCfgWaOficial from './MelissaCfgWaOficial.vue';
import MelissaCfgWaTemplates from './MelissaCfgWaTemplates.vue';
import MelissaCfgConversasTags from './MelissaCfgConversasTags.vue';
import MelissaCfgConversasAutoreply from './MelissaCfgConversasAutoreply.vue';
import MelissaCfgConversasOptouts from './MelissaCfgConversasOptouts.vue';
import MelissaCfgConversasSla from './MelissaCfgConversasSla.vue';
import MelissaCfgConversasBots from './MelissaCfgConversasBots.vue';
import MelissaCfgLembretes from './MelissaCfgLembretes.vue';
import MelissaCfgCreditosWa from './MelissaCfgCreditosWa.vue';
import MelissaCfgSms from './MelissaCfgSms.vue';
import MelissaCfgEmailTemplates from './MelissaCfgEmailTemplates.vue';
import MelissaCfgRecursosExtras from './MelissaCfgRecursosExtras.vue';
import MelissaCfgRecursosExtrasExtrato from './MelissaCfgRecursosExtrasExtrato.vue';
import MelissaCfgAuditoria from './MelissaCfgAuditoria.vue';
// Sidebar global de configs removido — substituido por botao + popover
// (MelissaConfigPopover) dentro de cada pagina de config. Resolveu lag
// de scroll que o overlay sempre visivel causava em mobile.
@@ -68,6 +89,7 @@ import { useMelissaWhatsapp } from './composables/useMelissaWhatsapp';
import { useMelissaAgenda, MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda';
import { useMelissaDockPins } from './composables/useMelissaDockPins';
import { supabase } from '@/lib/supabase/client';
import { useTenantStore } from '@/stores/tenantStore';
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
import ConversationDrawer from '@/components/conversations/ConversationDrawer.vue';
import AgendaEventDialog from '@/features/agenda/components/AgendaEventDialog.vue';
@@ -193,6 +215,27 @@ const SECOES = {
'agenda-config': { label: 'Configurações da Agenda', icon: 'pi pi-calendar', descricao: 'Jornada (dias e horários), ritmo das sessões e agendamento online.' },
// Pagina nativa de formas de pagamento (MelissaPagamento) — saiu do MelissaConfiguracoes
pagamento: { label: 'Formas de Pagamento', icon: 'pi pi-wallet', descricao: 'Pix, depósito, dinheiro, cartão e convênio.' },
// Paginas nativas de configuracoes financeiras — saidas do MelissaConfiguracoes
'cfg-precificacao': { label: 'Precificação', icon: 'pi pi-tag', descricao: 'Valor padrão da sessão e preços por tipo de compromisso.' },
'cfg-descontos': { label: 'Descontos por Paciente', icon: 'pi pi-percentage', descricao: 'Descontos recorrentes aplicados automaticamente.' },
'cfg-excecoes': { label: 'Exceções Financeiras', icon: 'pi pi-exclamation-triangle', descricao: 'O que cobrar em faltas, cancelamentos e situações excepcionais.' },
'cfg-convenios': { label: 'Convênios', icon: 'pi pi-id-card', descricao: 'Cadastre os convênios que você atende e seus valores.' },
'cfg-wa': { label: 'Canal WhatsApp', icon: 'pi pi-whatsapp', descricao: 'Escolha o canal de envio: oficial AgenciaPSI ou pessoal.' },
'cfg-wa-pessoal': { label: 'WhatsApp Pessoal', icon: 'pi pi-mobile', descricao: 'Conecte seu próprio número via QR code.' },
'cfg-wa-oficial': { label: 'WhatsApp Oficial', icon: 'pi pi-verified', descricao: 'Número provisionado pela AgenciaPSI via API oficial Meta.' },
'cfg-wa-templates': { label: 'Templates WhatsApp', icon: 'pi pi-file-edit', descricao: 'Personalize os textos enviados ou volte ao padrão.' },
'cfg-conversas-tags': { label: 'Tags de Conversa', icon: 'pi pi-tag', descricao: 'Etiquetas custom pra classificar threads no CRM.' },
'cfg-conversas-autoreply': { label: 'Auto-reply WhatsApp', icon: 'pi pi-reply', descricao: 'Resposta automática quando paciente escreve fora do horário.' },
'cfg-conversas-optouts': { label: 'Opt-outs (LGPD)', icon: 'pi pi-ban', descricao: 'Números que pediram pra não receber mensagens. LGPD Art. 18.' },
'cfg-conversas-sla': { label: 'SLA de resposta', icon: 'pi pi-stopwatch', descricao: 'Tempo máximo pra responder. Alerta quando estourar.' },
'cfg-conversas-bots': { label: 'Bot de triagem', icon: 'pi pi-android', descricao: 'Coleta nome e motivo via WhatsApp antes do humano.' },
'cfg-lembretes': { label: 'Lembretes de Sessão', icon: 'pi pi-bell', descricao: 'WhatsApp automático antes das sessões agendadas.' },
'cfg-creditos-wa': { label: 'Créditos WhatsApp', icon: 'pi pi-credit-card', descricao: 'Compre pacotes de mensagens, veja saldo e extrato.' },
'cfg-sms': { label: 'SMS', icon: 'pi pi-comment', descricao: 'Backup quando WhatsApp falha. Gerencie créditos SMS.' },
'cfg-email-templates': { label: 'Templates de E-mail', icon: 'pi pi-envelope', descricao: 'Personalize os e-mails enviados aos pacientes.' },
'cfg-recursos-extras': { label: 'Recursos Extras', icon: 'pi pi-box', descricao: 'Amplie as funcionalidades com recursos adicionais.' },
'cfg-recursos-extras-extrato': { label: 'Extrato de Recursos Extras', icon: 'pi pi-list', descricao: 'Histórico de débitos e créditos exportável.' },
'cfg-auditoria': { label: 'Auditoria', icon: 'pi pi-shield', descricao: 'Registro imutável de operações (LGPD Art. 37).' },
// Pagina nativa de alterar plano (MelissaAlterarPlano) — substitui /therapist/upgrade
'alterar-plano': { label: 'Alterar Plano', icon: 'pi pi-arrow-up-right', descricao: 'Escolha um plano pessoal pra ativar todos os recursos.' },
// Onda 1 — pages embedadas via MelissaEmbed (1-coluna, hero glass)
@@ -221,6 +264,12 @@ const MELISSA_NON_CONFIG_SLUGS = new Set([
'documentos', 'documentos-templates', 'relatorios',
'perfil', 'plano', 'negocio', 'seguranca', 'bloqueios', 'alterar-plano',
'online-scheduling', 'agenda-config', 'pagamento',
'cfg-precificacao', 'cfg-descontos', 'cfg-excecoes', 'cfg-convenios',
'cfg-wa', 'cfg-wa-pessoal', 'cfg-wa-oficial', 'cfg-wa-templates',
'cfg-conversas-tags', 'cfg-conversas-autoreply', 'cfg-conversas-optouts',
'cfg-conversas-sla', 'cfg-conversas-bots',
'cfg-lembretes', 'cfg-creditos-wa', 'cfg-sms',
'cfg-email-templates', 'cfg-recursos-extras', 'cfg-recursos-extras-extrato', 'cfg-auditoria',
...MELISSA_EMBED_KEYS
]);
// Aliases "bonitos" + INLINE_KEYS reconhecidos pelo MelissaConfiguracoes.
@@ -283,6 +332,14 @@ function fecharSecao() {
router.push({ name: 'Melissa', params: {} });
}
// Click "Cores do Tema" no menu principal: fecha qualquer fake dialog
// aberto (perfil/plano/negocio/seguranca/pagamento/agendador/cfg-*) e
// abre o painel Personalizar (cog top-right).
function onMenuOpenSettings() {
fecharSecao();
settingsOpen.value = true;
}
// ── Pins dinâmicos do dock (híbrido: 4 fixos + 3 MRU) ──────────
const dockPins = useMelissaDockPins();
const pinContextMenu = ref(null);
@@ -423,6 +480,21 @@ const {
clearBg
} = useMelissaWallpaper();
// Nome do tema selecionado em "Personalizar > Temas" (Freud/Klein/Jung).
// E o ID persistido — wallpaper e' resolvido a partir dele no boot
// (sem guardar a data URL gigante no DB). null = wallpaper custom ou padrao.
const themeName = ref(null);
function setThemeName(name) {
if (name && !MELISSA_THEME_NAMES.has(name)) name = null;
themeName.value = name || null;
}
// Quando ON, os textos do hero (relogio, saudacao, resumo) ganham um
// fundo solido translucido + borda + padding. Util pra wallpapers
// com pouca transparencia onde o text-shadow nao da legibilidade.
const textBgEnabled = ref(false);
function setTextBgEnabled(v) { textBgEnabled.value = !!v; }
// ───────────────────────────────────────────────────────────────
// Tema (dark/light + cor primária) — usa a infra existente do app
// ───────────────────────────────────────────────────────────────
@@ -531,6 +603,7 @@ const eventoSelecionado = ref(null);
const eventoBusy = ref(false); // bloqueia botões enquanto UPDATE roda
const melissaAgendaRef = ref(null); // pra chamar openProntuario + setView
const toast = useToast();
const tenantStore = useTenantStore();
const conversationDrawerStore = useConversationDrawerStore();
// ───────────────────────────────────────────────────────────────
@@ -881,6 +954,73 @@ function fecharCronometro() {
cronoRef.value?.fechar();
}
// ── Cronometro: salvar tempo na sessao agendada ──────────────
// Quando o user para o cronometro com paciente selecionado, busca a
// sessao agendada do dia desse paciente em agenda_eventos e grava o
// tempo cronometrado em extra_fields.cronometro_*. Se nao encontrar
// sessao agendada hoje, avisa e nao falha silencioso.
async function onCronometroSessionEnd({ pacienteId, elapsedSec, stoppedAt }) {
if (!pacienteId || !Number.isFinite(elapsedSec) || elapsedSec <= 0) return;
const tenantId = tenantStore?.activeTenantId || tenantStore?.tenantId;
if (!tenantId) {
toast.add({ severity: 'warn', summary: 'Sem tenant ativo', detail: 'Não foi possível salvar o tempo.', life: 3500 });
return;
}
// Janela do dia em ISO (00:00 ate 23:59:59 local).
const now = new Date();
const todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate(), 0, 0, 0).toISOString();
const tomorrowStart = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1, 0, 0, 0).toISOString();
try {
const { data: sessao, error } = await supabase
.from('agenda_eventos')
.select('id, extra_fields, inicio_em')
.eq('tenant_id', tenantId)
.eq('patient_id', pacienteId)
.eq('tipo', 'sessao')
.gte('inicio_em', todayStart)
.lt('inicio_em', tomorrowStart)
.order('inicio_em', { ascending: false })
.limit(1)
.maybeSingle();
if (error) throw error;
if (!sessao) {
toast.add({
severity: 'warn',
summary: 'Sessão não encontrada',
detail: 'Não há sessão agendada hoje pra este paciente — tempo não foi salvo.',
life: 4500
});
return;
}
const newExtra = {
...(sessao.extra_fields && typeof sessao.extra_fields === 'object' ? sessao.extra_fields : {}),
cronometro_duracao_seg: Math.round(elapsedSec),
cronometro_parado_em: stoppedAt
};
const { error: upErr } = await supabase
.from('agenda_eventos')
.update({ extra_fields: newExtra })
.eq('id', sessao.id);
if (upErr) throw upErr;
const min = Math.round(elapsedSec / 60);
toast.add({
severity: 'success',
summary: 'Tempo registrado',
detail: `${min} min cronometrados salvos na sessão.`,
life: 3000
});
} catch (e) {
toast.add({
severity: 'error',
summary: 'Falha ao salvar tempo',
detail: e?.message || 'Tente novamente.',
life: 4500
});
}
}
// Provide das prefs/refs pro MelissaConfiguracoes (página interna de
// configs). Posicionado aqui pra que TODAS as refs/funções referenciadas
// já estejam definidas no momento do setup. A página lê/escreve direto
@@ -907,7 +1047,13 @@ provide('melissaSettings', {
use24h,
// cronômetro
toqueTermino,
testarToque
testarToque,
// tema (bundle wallpaper + cores) — Freud/Klein/Jung
themeName,
setThemeName,
// fundo nos textos do hero (relogio, saudacao, resumo)
textBgEnabled,
setTextBgEnabled
});
// ───────────────────────────────────────────────────────────────
@@ -948,6 +1094,14 @@ function applyPrefsPayload(prefs) {
if (prefs.cardsLayout === 'linha-unica' || prefs.cardsLayout === 'duas-linhas') {
cardsLayout.value = prefs.cardsLayout;
}
if (typeof prefs.themeName === 'string' && MELISSA_THEME_NAMES.has(prefs.themeName)) {
themeName.value = prefs.themeName;
} else if (prefs.themeName === null) {
themeName.value = null;
}
if (typeof prefs.textBgEnabled === 'boolean') {
textBgEnabled.value = prefs.textBgEnabled;
}
}
function currentPrefsSnapshot() {
@@ -957,7 +1111,9 @@ function currentPrefsSnapshot() {
bgImageOpacity: bgImageOpacity.value,
use24h: use24h.value,
cardsAtivos: cardsAtivos.value,
cardsLayout: cardsLayout.value
cardsLayout: cardsLayout.value,
themeName: themeName.value,
textBgEnabled: textBgEnabled.value
};
}
@@ -1036,6 +1192,29 @@ async function saveDbPrefs() {
}
}
// Quando themeName carregou mas bgUrl ainda esta vazio (ex: 1a vez em outro
// device), resolve a imagem do tema e gera a data URL. Idempotente: se ja
// tem bgUrl, nao mexe (custom upload ou data URL ja restaurada do storage).
async function resolveThemeWallpaperIfNeeded() {
if (!themeName.value) return;
if (bgUrl.value) return;
const t = findMelissaTheme(themeName.value);
if (!t) return;
try {
const res = await fetch(t.image);
const blob = await res.blob();
const dataUrl = await new Promise((resolve, reject) => {
const r = new FileReader();
r.onload = () => resolve(r.result);
r.onerror = reject;
r.readAsDataURL(blob);
});
bgUrl.value = dataUrl;
} catch {
bgUrl.value = t.image; // fallback: URL direta funciona na sessao
}
}
function queueDbSave() {
if (dbSaveTimer) clearTimeout(dbSaveTimer);
dbSaveTimer = setTimeout(saveDbPrefs, 600);
@@ -1043,7 +1222,7 @@ function queueDbSave() {
// Salva em qualquer mudança das prefs (deep no array de cardsAtivos pra pegar splice/push)
watch(
[toqueTermino, overlayOpacity, bgImageOpacity, use24h, bgUrl, cardsAtivos, cardsLayout],
[toqueTermino, overlayOpacity, bgImageOpacity, use24h, bgUrl, cardsAtivos, cardsLayout, themeName, textBgEnabled],
() => {
saveLayoutPrefs();
queueDbSave();
@@ -1055,6 +1234,9 @@ watch(
onMounted(async () => {
loadLocalPrefs(); // sync: paint imediato com valores cached
await loadDbPrefs(); // async: sobrescreve com valores autoritativos do DB
// Se o user logou em outro device, themeName vem do DB mas bgUrl
// (data URL) nao — resolvemos a imagem do tema agora.
await resolveThemeWallpaperIfNeeded();
});
// Auto-scroll inicial + ResizeObserver da timeline migrou pro
@@ -1128,6 +1310,12 @@ function onKeydown(e) {
<!-- PLANO DE TRÁS Resumo (recebe blur quando workspace abre) -->
<!-- -->
<div class="win11-summary" :class="{ 'is-behind': summaryDimmed }">
<!-- Faixa de fundo do topbar gradiente horizontal
(cor solida na direita -> transparente na esquerda)
pra dar legibilidade aos icones sem virar barra solida.
Cor flipa com light/dark via --m-band. -->
<div class="melissa-topbar-band" aria-hidden="true"></div>
<!-- Topbar Melissa (canto sup. direito): plan-DEV + notificações
+ ajuda + cog. Os 3 primeiros vêm do AppTopbar replicados
aqui porque a rota /melissa é fullscreen e não monta o
@@ -1196,6 +1384,7 @@ function onKeydown(e) {
:saudacao="saudacao"
:resumo-partes="resumoPartes"
:filtro-tipo="filtroTipo"
:text-bg="textBgEnabled"
@cronometro="abrirCronometro"
@toggle-filtro="toggleFiltro"
/>
@@ -1470,6 +1659,7 @@ function onKeydown(e) {
:secao-ativa="secaoAberta"
@select="abrirSecao"
@close="closeWorkspace"
@open-settings="onMenuOpenSettings"
/>
</Transition>
@@ -1710,6 +1900,91 @@ function onKeydown(e) {
@close="fecharSecao"
/>
<MelissaPrecificacao
v-if="layoutReady && secaoAberta === 'cfg-precificacao'"
@close="fecharSecao"
/>
<MelissaDescontos
v-if="layoutReady && secaoAberta === 'cfg-descontos'"
@close="fecharSecao"
/>
<MelissaExcecoes
v-if="layoutReady && secaoAberta === 'cfg-excecoes'"
@close="fecharSecao"
/>
<MelissaConvenios
v-if="layoutReady && secaoAberta === 'cfg-convenios'"
@close="fecharSecao"
/>
<MelissaCfgWa
v-if="layoutReady && secaoAberta === 'cfg-wa'"
@close="fecharSecao"
/>
<MelissaCfgWaPessoal
v-if="layoutReady && secaoAberta === 'cfg-wa-pessoal'"
@close="fecharSecao"
/>
<MelissaCfgWaOficial
v-if="layoutReady && secaoAberta === 'cfg-wa-oficial'"
@close="fecharSecao"
/>
<MelissaCfgWaTemplates
v-if="layoutReady && secaoAberta === 'cfg-wa-templates'"
@close="fecharSecao"
/>
<MelissaCfgConversasTags
v-if="layoutReady && secaoAberta === 'cfg-conversas-tags'"
@close="fecharSecao"
/>
<MelissaCfgConversasAutoreply
v-if="layoutReady && secaoAberta === 'cfg-conversas-autoreply'"
@close="fecharSecao"
/>
<MelissaCfgConversasOptouts
v-if="layoutReady && secaoAberta === 'cfg-conversas-optouts'"
@close="fecharSecao"
/>
<MelissaCfgConversasSla
v-if="layoutReady && secaoAberta === 'cfg-conversas-sla'"
@close="fecharSecao"
/>
<MelissaCfgConversasBots
v-if="layoutReady && secaoAberta === 'cfg-conversas-bots'"
@close="fecharSecao"
/>
<MelissaCfgLembretes
v-if="layoutReady && secaoAberta === 'cfg-lembretes'"
@close="fecharSecao"
/>
<MelissaCfgCreditosWa
v-if="layoutReady && secaoAberta === 'cfg-creditos-wa'"
@close="fecharSecao"
/>
<MelissaCfgSms
v-if="layoutReady && secaoAberta === 'cfg-sms'"
@close="fecharSecao"
/>
<MelissaCfgEmailTemplates
v-if="layoutReady && secaoAberta === 'cfg-email-templates'"
@close="fecharSecao"
/>
<MelissaCfgRecursosExtras
v-if="layoutReady && secaoAberta === 'cfg-recursos-extras'"
@close="fecharSecao"
/>
<MelissaCfgRecursosExtrasExtrato
v-if="layoutReady && secaoAberta === 'cfg-recursos-extras-extrato'"
@close="fecharSecao"
/>
<MelissaCfgAuditoria
v-if="layoutReady && secaoAberta === 'cfg-auditoria'"
@close="fecharSecao"
/>
<MelissaConfiguracoes
v-if="layoutReady && isMelissaConfigRoute(secaoAberta)"
:secao-rota="secaoAberta"
@@ -1780,6 +2055,7 @@ function onKeydown(e) {
:duracao-minutos="CONFIG_DURACAO_MIN"
:toque-termino="toqueTermino"
@visible-change="cronoVisible = $event"
@session-end="onCronometroSessionEnd"
/>
<!-- Drawer de conversas (WhatsApp): mesmo padrão do AppLayout.
@@ -2428,12 +2704,45 @@ function onKeydown(e) {
cronômetro, futuros). Páginas fullscreen reservam esse espaço
no inset bottom pra não ficar atrás do dock. */
--m-dock-h: 76px;
/* ─── Faixa de fundo (top + dock) ───────────────────────────────
Cor solida com 80% de opacidade pra fazer gradiente horizontal
e dar legibilidade aos icones sem virar barra hard. Default =
dark (preto); light flipa pra branco (override em html:not(.app-dark)). */
--m-band: rgba(0, 0, 0, 0.8);
/* ─── Fundo dos textos do hero (relogio/saudacao/resumo) ─────────
Toggle "fundo nos textos soltos" no painel Personalizar. 60% de
opacidade pra dar legibilidade sobre wallpapers complexos sem
sumir totalmente o look do bg. Default = dark; light flipa. */
--m-hero-text-bg: rgba(0, 0, 0, 0.6);
--m-hero-text-border: rgba(255, 255, 255, 0.12);
}
/* ─── Faixa de fundo do topbar (canto sup. direito) ──────────────
Gradiente horizontal: cor solida na direita (onde os icones vivem)
e fade pra transparente na esquerda. z-index abaixo do topbar
(z-30) e acima do conteudo principal. */
.melissa-topbar-band {
position: fixed;
top: 0;
left: 0;
right: 0;
height: 80px;
z-index: 25;
pointer-events: none;
background: linear-gradient(
to left,
var(--m-band) 0%,
var(--m-band) 25%,
transparent 75%
);
}
/* ─── Dock (global pra atravessar Teleport + evitar perda de scoped
em static-hoisted nodes). Faixa horizontal no bottom. Sem bg.
Items individuais (chip cronômetro, dock-actions, futuros) têm
visuais próprios e posicionam-se dentro do flexbox. */
em static-hoisted nodes). Faixa horizontal no bottom com gradiente
espelhado: cor solida na esquerda (onde os pins ficam) e fade
pra transparente na direita. */
.melissa-dock {
position: fixed;
bottom: 0;
@@ -2447,6 +2756,12 @@ function onKeydown(e) {
padding: 0 1.5rem 0 6rem;
gap: 12px;
pointer-events: none; /* só os items recebem clique */
background: linear-gradient(
to right,
var(--m-band) 0%,
var(--m-band) 35%,
transparent 85%
);
}
.melissa-dock > * { pointer-events: auto; }
@@ -2629,6 +2944,13 @@ html:not(.app-dark) .win11-root {
(NÃO usar var(--m-text) aqui — em light vira preto e fica preto sobre preto). */
--m-kbd-bg: color-mix(in srgb, var(--p-text-color) 78%, transparent);
--m-kbd-text: var(--p-content-background);
/* Faixa de fundo do topbar/dock em light: branco 80% (vs preto 80% em dark) */
--m-band: rgba(255, 255, 255, 0.8);
/* Fundo dos textos do hero em light: branco 60% (vs preto 60% em dark) */
--m-hero-text-bg: rgba(255, 255, 255, 0.6);
--m-hero-text-border: rgba(0, 0, 0, 0.08);
}
/* Light: dim escondido por padrão (sem foto, bloom claro já é leve).