diff --git a/Obsidian/Brain/log.md b/Obsidian/Brain/log.md index 7525ec4..fb27ad3 100644 --- a/Obsidian/Brain/log.md +++ b/Obsidian/Brain/log.md @@ -43,3 +43,6 @@ verde #00a884). Bug fix em MelissaPacientes: g.cor->g.color em 20 lugares (repository devolve camelCase, template lia PT-BR e cores nao apareciam). 5 commits criados: 957e912, 6d9b36d, 269b531, 98f7252, 15103ed. Working tree limpa. HANDOFF.md atualizado. + +## [2026-05-06 17:30] session | Melissa drawers: footer pattern AppMenu +Touched: none diff --git a/src/assets/Freud.png b/src/assets/Freud.png new file mode 100644 index 0000000..55382a2 Binary files /dev/null and b/src/assets/Freud.png differ diff --git a/src/assets/jung.png b/src/assets/jung.png new file mode 100644 index 0000000..1c1a033 Binary files /dev/null and b/src/assets/jung.png differ diff --git a/src/assets/melaine.png b/src/assets/melaine.png new file mode 100644 index 0000000..031ab5c Binary files /dev/null and b/src/assets/melaine.png differ diff --git a/src/assets/themes/freudwebp.webp b/src/assets/themes/freudwebp.webp new file mode 100644 index 0000000..82ad71b Binary files /dev/null and b/src/assets/themes/freudwebp.webp differ diff --git a/src/assets/themes/jungwebp.webp b/src/assets/themes/jungwebp.webp new file mode 100644 index 0000000..4391d7e Binary files /dev/null and b/src/assets/themes/jungwebp.webp differ diff --git a/src/assets/themes/melainewebp.webp b/src/assets/themes/melainewebp.webp new file mode 100644 index 0000000..cd5b36c Binary files /dev/null and b/src/assets/themes/melainewebp.webp differ diff --git a/src/assets/whatsapp-bg.jpg b/src/assets/whatsapp-bg.jpg new file mode 100644 index 0000000..b313422 Binary files /dev/null and b/src/assets/whatsapp-bg.jpg differ diff --git a/src/components/conversations/ConversationDrawer.vue b/src/components/conversations/ConversationDrawer.vue index 472d653..3dc6f90 100644 --- a/src/components/conversations/ConversationDrawer.vue +++ b/src/components/conversations/ConversationDrawer.vue @@ -1262,14 +1262,28 @@ function insertEmoji(emoji) { /* Light: bege esverdeado clássico do WA. Dark: cinza profundo tipo wallpaper de modo escuro. Adapta via CSS variable `--p-content-background` pra harmonizar com o tema do app. */ +/* Background do chat estilo WhatsApp. + Light: cor bege classica (#efeae2) com a imagem doodle por cima usando + mix-blend multiply pra integrar. + Dark: cor #0b141a (mesma do WhatsApp Web dark) + camada escura translucida + por cima da imagem pra dimming. */ .cd-msgs { - background-color: color-mix(in srgb, var(--p-content-background) 85%, #efeae2); - background-image: - radial-gradient(circle at 1px 1px, color-mix(in srgb, var(--p-text-color) 4%, transparent) 1px, transparent 0); - background-size: 18px 18px; + background-color: #efeae2; + background-image: url('../../assets/whatsapp-bg.jpg'); + background-size: 380px auto; + background-repeat: repeat; + background-blend-mode: multiply; border-radius: 8px; margin: 0 -2px; } +:where(.p-dark, html.dark, [data-theme="dark"]) .cd-msgs, +.cd-msgs:where(.p-dark *, html.dark *, [data-theme="dark"] *) { + background-color: #0b141a; + background-image: + linear-gradient(rgba(11, 20, 26, 0.78), rgba(11, 20, 26, 0.78)), + url('../../assets/whatsapp-bg.jpg'); + background-blend-mode: normal; +} /* ─── Bolha (wrapper + content + meta) ─── */ .cd-bubble-wrap { diff --git a/src/components/ui/JoditTextEditor.vue b/src/components/ui/JoditTextEditor.vue new file mode 100644 index 0000000..aeb2234 --- /dev/null +++ b/src/components/ui/JoditTextEditor.vue @@ -0,0 +1,118 @@ + + + + diff --git a/src/layout/configuracoes/AddonsExtratoPage.vue b/src/layout/configuracoes/AddonsExtratoPage.vue index b336f51..ac8e401 100644 --- a/src/layout/configuracoes/AddonsExtratoPage.vue +++ b/src/layout/configuracoes/AddonsExtratoPage.vue @@ -16,7 +16,7 @@ --> + + diff --git a/src/layout/melissa/MelissaCfgConversasAutoreply.vue b/src/layout/melissa/MelissaCfgConversasAutoreply.vue new file mode 100644 index 0000000..5f488ad --- /dev/null +++ b/src/layout/melissa/MelissaCfgConversasAutoreply.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgConversasBots.vue b/src/layout/melissa/MelissaCfgConversasBots.vue new file mode 100644 index 0000000..38ea1ac --- /dev/null +++ b/src/layout/melissa/MelissaCfgConversasBots.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgConversasOptouts.vue b/src/layout/melissa/MelissaCfgConversasOptouts.vue new file mode 100644 index 0000000..66db177 --- /dev/null +++ b/src/layout/melissa/MelissaCfgConversasOptouts.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgConversasSla.vue b/src/layout/melissa/MelissaCfgConversasSla.vue new file mode 100644 index 0000000..e1fe38a --- /dev/null +++ b/src/layout/melissa/MelissaCfgConversasSla.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgConversasTags.vue b/src/layout/melissa/MelissaCfgConversasTags.vue new file mode 100644 index 0000000..f96033d --- /dev/null +++ b/src/layout/melissa/MelissaCfgConversasTags.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgCreditosWa.vue b/src/layout/melissa/MelissaCfgCreditosWa.vue new file mode 100644 index 0000000..cd95975 --- /dev/null +++ b/src/layout/melissa/MelissaCfgCreditosWa.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgEmailTemplates.vue b/src/layout/melissa/MelissaCfgEmailTemplates.vue new file mode 100644 index 0000000..7c11470 --- /dev/null +++ b/src/layout/melissa/MelissaCfgEmailTemplates.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgLembretes.vue b/src/layout/melissa/MelissaCfgLembretes.vue new file mode 100644 index 0000000..d97558e --- /dev/null +++ b/src/layout/melissa/MelissaCfgLembretes.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgRecursosExtras.vue b/src/layout/melissa/MelissaCfgRecursosExtras.vue new file mode 100644 index 0000000..668c77b --- /dev/null +++ b/src/layout/melissa/MelissaCfgRecursosExtras.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgRecursosExtrasExtrato.vue b/src/layout/melissa/MelissaCfgRecursosExtrasExtrato.vue new file mode 100644 index 0000000..953ae4c --- /dev/null +++ b/src/layout/melissa/MelissaCfgRecursosExtrasExtrato.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgSms.vue b/src/layout/melissa/MelissaCfgSms.vue new file mode 100644 index 0000000..9bfb193 --- /dev/null +++ b/src/layout/melissa/MelissaCfgSms.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgWa.vue b/src/layout/melissa/MelissaCfgWa.vue new file mode 100644 index 0000000..4b994a6 --- /dev/null +++ b/src/layout/melissa/MelissaCfgWa.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgWaOficial.vue b/src/layout/melissa/MelissaCfgWaOficial.vue new file mode 100644 index 0000000..6980c52 --- /dev/null +++ b/src/layout/melissa/MelissaCfgWaOficial.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgWaPessoal.vue b/src/layout/melissa/MelissaCfgWaPessoal.vue new file mode 100644 index 0000000..c334130 --- /dev/null +++ b/src/layout/melissa/MelissaCfgWaPessoal.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaCfgWaTemplates.vue b/src/layout/melissa/MelissaCfgWaTemplates.vue new file mode 100644 index 0000000..0975a34 --- /dev/null +++ b/src/layout/melissa/MelissaCfgWaTemplates.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/layout/melissa/MelissaConfigList.vue b/src/layout/melissa/MelissaConfigList.vue new file mode 100644 index 0000000..bc7592c --- /dev/null +++ b/src/layout/melissa/MelissaConfigList.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/src/layout/melissa/MelissaConfigPage.vue b/src/layout/melissa/MelissaConfigPage.vue new file mode 100644 index 0000000..1671451 --- /dev/null +++ b/src/layout/melissa/MelissaConfigPage.vue @@ -0,0 +1,404 @@ + + + + + diff --git a/src/layout/melissa/MelissaConfiguracoes.vue b/src/layout/melissa/MelissaConfiguracoes.vue index 11724fd..5e384c5 100644 --- a/src/layout/melissa/MelissaConfiguracoes.vue +++ b/src/layout/melissa/MelissaConfiguracoes.vue @@ -22,8 +22,9 @@ * ainda é responsabilidade do MelissaLayout (watchers já existentes). */ import { computed, defineAsyncComponent, inject, ref, watch, onMounted, onBeforeUnmount } from 'vue'; -import { useRouter, useRoute } from 'vue-router'; +// useRouter/useRoute removidos — navegacao migrou pro MelissaConfigSidebar import { TOQUES } from './melissaToques'; +import MelissaConfigList from './MelissaConfigList.vue'; const props = defineProps({ // Key da rota /melissa/:secao — usada pra pré-selecionar uma seção interna @@ -48,9 +49,7 @@ const DEPRECATED_ALIASES = { relogio: 'aparencia', cronometro: 'aparencia' }; -const SECAO_ALIASES = Object.fromEntries( - Object.entries(ROUTE_ALIASES).map(([slug, key]) => [key, slug]) -); +// SECAO_ALIASES removido — era usado por secaoToRota (tambem removido). function rotaToSecao(rota) { if (!rota) return 'aparencia'; @@ -61,10 +60,8 @@ function rotaToSecao(rota) { if (COMPONENT_MAP[r]) return r; return 'aparencia'; } -function secaoToRota(key) { - if (!key) return 'aparencia'; - return SECAO_ALIASES[key] || key; -} +// secaoToRota removido junto com selecionar (navegacao agora vive +// no MelissaConfigSidebar). SECAO_ALIASES tambem nao e mais usado. // ── Componentes externos embedados (todas as páginas de /configuracoes/) ─ @@ -90,13 +87,13 @@ const COMPONENT_MAP = { 'cfg-creditos-wa': defineAsyncComponent(() => import('@/layout/configuracoes/ConfiguracoesCreditosWhatsappPage.vue')), 'cfg-sms': defineAsyncComponent(() => import('@/layout/configuracoes/ConfiguracoesSmsPage.vue')), 'cfg-email-templates': defineAsyncComponent(() => import('@/layout/configuracoes/ConfiguracoesEmailTemplatesPage.vue')), - 'cfg-empresa': defineAsyncComponent(() => import('@/layout/configuracoes/ConfiguracoesMinhaEmpresaPage.vue')), 'cfg-recursos-extras': defineAsyncComponent(() => import('@/layout/configuracoes/ConfiguracoesRecursosExtrasPage.vue')), 'cfg-auditoria': defineAsyncComponent(() => import('@/layout/configuracoes/AuditoriaPage.vue')), // Conta (páginas pessoais que vivem em /account/*) // 'cfg-perfil' removido — virou pagina nativa MelissaPerfil em /melissa/perfil // 'cfg-plano' removido — virou pagina nativa MelissaPlano em /melissa/plano - // 'cfg-negocio' removido — virou pagina nativa MelissaNegocio em /melissa/negocio + // 'cfg-negocio' removido — virou pagina nativa MelissaNegocio em /melissa/negocio (engloba os campos do antigo cfg-empresa) + // 'cfg-empresa' removido — unificado em MelissaNegocio (mesma tabela company_profiles) // 'cfg-seguranca' removido — virou pagina nativa MelissaSeguranca em /melissa/seguranca }; @@ -214,20 +211,19 @@ const grupos = [ }, { key: 'plataforma', - label: 'Empresa & Plataforma', - desc: 'Dados da empresa, recursos extras e auditoria.', - icon: 'pi pi-building', + label: 'Plataforma', + desc: 'Recursos extras e auditoria.', + icon: 'pi pi-server', items: [ - { key: 'cfg-empresa', label: 'Minha Empresa', desc: 'CNPJ, endereço, logomarca e redes sociais.', icon: 'pi pi-building' }, - { key: 'cfg-recursos-extras', label: 'Recursos Extras', desc: 'Amplíe as funcionalidades com recursos adicionais.', icon: 'pi pi-box' }, - { key: 'cfg-auditoria', label: 'Auditoria', desc: 'Registro imutável de operações (LGPD Art. 37).', icon: 'pi pi-shield' } + { key: 'cfg-recursos-extras', label: 'Recursos Extras', desc: 'Amplíe as funcionalidades com recursos adicionais.', icon: 'pi pi-box' }, + { key: 'cfg-recursos-extras-extrato', label: 'Extrato de Recursos Extras', desc: 'Histórico de débitos e créditos exportável (CSV/PDF).', icon: 'pi pi-list' }, + { key: 'cfg-auditoria', label: 'Auditoria', desc: 'Registro imutável de operações (LGPD Art. 37).', icon: 'pi pi-shield' } ] } ]; const secoesFlat = computed(() => grupos.flatMap((g) => g.items)); -const router = useRouter(); -const route = useRoute(); +// router/route removidos — navegacao migrou pro MelissaConfigSidebar. const secaoAtiva = ref(rotaToSecao(props.secaoRota)); const secaoAtivaInfo = computed(() => secoesFlat.value.find((s) => s.key === secaoAtiva.value) || secoesFlat.value[0]); @@ -246,18 +242,8 @@ watch(() => props.secaoRota, (v) => { if (gk && !openGroups.value.includes(gk)) openGroups.value = [...openGroups.value, gk]; }); -function selecionar(item) { - secaoAtiva.value = item.key; - fecharDrawer(); - const gk = grupoDaSecao(item.key); - if (gk && !openGroups.value.includes(gk)) openGroups.value = [...openGroups.value, gk]; - // Atualiza a URL pra refletir a seção atual — sem isso, /melissa/aparencia - // ficava fixo no path enquanto o user navegava em outras seções da config. - const slug = secaoToRota(item.key); - if (route.params?.secao !== slug) { - router.push({ name: 'Melissa', params: { secao: slug } }); - } -} +// `selecionar` removido — navegacao agora vive no MelissaConfigSidebar +// (compartilhado), que faz router.push direto pelo seu proprio handler. // Componente embedado da seção ativa (null → renderiza inline do Layout Melissa) const embedComp = computed(() => COMPONENT_MAP[secaoAtiva.value] || null); @@ -274,6 +260,11 @@ function _onMqMobileChange(e) { function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } function fecharDrawer() { drawerOpen.value = false; } +// Toggle entre embed (default) e lista de configs (alterna inline na sidebar) +const cfgOpen = ref(false); +function toggleCfg() { cfgOpen.value = !cfgOpen.value; } +function fecharCfg() { cfgOpen.value = false; } + onMounted(() => { if (typeof window !== 'undefined' && window.matchMedia) { _mqMobile = window.matchMedia('(max-width: 1023px)'); @@ -309,7 +300,9 @@ function resetCores() { v-show="isMobile" aria-label="Seções de configuração" > -
+
+ +
{{ secaoAtivaInfo?.label }}
+ @@ -343,46 +345,17 @@ function resetCores() {
- - - - + +
+ +
+ -
+
@@ -651,6 +624,53 @@ function resetCores() { } .mcfg-page__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; } +/* Botao "Outras configs" no header da MelissaConfiguracoes. Click alterna + entre o embed (default) e a lista de configs no body. */ +.mcfg-cfg-btn { + height: 32px; + display: inline-flex; + align-items: center; + gap: 6px; + padding: 0 11px; + background: var(--m-bg-soft); + border: 1px solid var(--m-border); + color: var(--m-text); + border-radius: 9px; + cursor: pointer; + font-family: inherit; + font-size: 0.78rem; + font-weight: 600; + transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease; +} +.mcfg-cfg-btn:hover { + background: var(--m-bg-soft-hover); + border-color: var(--m-border-strong); +} +.mcfg-cfg-btn.is-open { + background: color-mix(in srgb, var(--p-primary-color) 14%, transparent); + border-color: color-mix(in srgb, var(--p-primary-color) 38%, transparent); + color: var(--p-primary-color); +} +.mcfg-cfg-btn > i { font-size: 0.85rem; } + +.mcfg-cfg-list-wrap { + flex: 1; + min-height: 0; + overflow-y: auto; + overflow-x: hidden; + padding: 16px; + background: var(--m-bg-soft); + border-radius: 12px; + margin: 12px; + scrollbar-width: thin; + scrollbar-color: var(--m-border-strong) transparent; +} +.mcfg-cfg-list-wrap::-webkit-scrollbar { width: 5px; } +.mcfg-cfg-list-wrap::-webkit-scrollbar-thumb { + background: var(--m-border-strong); + border-radius: 3px; +} + .mcfg-close { width: 32px; height: 32px; display: grid; place-items: center; @@ -957,6 +977,24 @@ function resetCores() { .mcfg-embed-wrap { padding: 16px 18px 28px; } + +/* ═══════ Desktop (>=1024px) ═══════ + Fake dialog: largura adaptativa, alinhada a esquerda (apos o + config-aside global). Espelha o pattern de MelissaSeguranca/Pagamento: + - 1024px–1012px : full-width (right: 6px) — overlap minimo + - 1012px–2012px : width = 1000px fixo (right cresce com viewport) + - >= 2012px : width = ~50% do viewport (right: 50%) + Aplicado no .mcfg-page que renderiza TODOS os 17 embeds cfg-* + (precificacao, descontos, excecoes, convenios, wa, wa-templates, + conversas-tags/autoreply/optouts/sla/bots, lembretes, creditos-wa, + sms, email-templates, recursos-extras, auditoria + empresa). + Uma fix pega todos. */ +@media (min-width: 1024px) { + .mcfg-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} + @media (max-width: 1023px) { .mcfg-embed-wrap { padding: 12px 12px 24px; } .mcfg-embed-hero { padding: 10px 12px; } @@ -1250,25 +1288,19 @@ function resetCores() { .mcfg-mobile-drawer__scroll { height: 100%; overflow-y: auto; - padding: 12px 12px 24px; + overflow-x: hidden; + padding: 12px; display: flex; flex-direction: column; - gap: 12px; + scrollbar-width: thin; + scrollbar-color: var(--m-border-strong) transparent; } .mcfg-mobile-drawer__scroll::-webkit-scrollbar { width: 5px; } .mcfg-mobile-drawer__scroll::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; } -/* Aside teleportada perde os adornos próprios dentro do drawer */ -.mcfg-mobile-drawer__scroll .mcfg-side { - width: 100%; - height: auto; - overflow: visible; - border-right: none; - background: transparent; - padding: 0; -} +/* MelissaConfigList dentro do drawer — herda layout flat e ocupa o scroll. */ .mcfg-mobile-drawer__backdrop { position: fixed; inset: 0; @@ -1290,6 +1322,8 @@ function resetCores() { .mcfg-page__title > i, .mcfg-page__title > span:first-of-type { display: none; } .mcfg-menu-btn--mobile-only { display: inline-flex; } + /* No mobile o drawer ja da acesso via "Seções" — ocultar btn redundante. */ + .mcfg-cfg-btn { display: none; } .mcfg-main__inner { padding: 14px; } .mcfg-bg-pick { flex-direction: column; } .mcfg-bg-preview { width: 100%; height: 120px; } diff --git a/src/layout/melissa/MelissaConvenios.vue b/src/layout/melissa/MelissaConvenios.vue new file mode 100644 index 0000000..ed51396 --- /dev/null +++ b/src/layout/melissa/MelissaConvenios.vue @@ -0,0 +1,379 @@ + + + + + diff --git a/src/layout/melissa/MelissaCronometro.vue b/src/layout/melissa/MelissaCronometro.vue index 2b11c02..c1ae701 100644 --- a/src/layout/melissa/MelissaCronometro.vue +++ b/src/layout/melissa/MelissaCronometro.vue @@ -47,7 +47,7 @@ const props = defineProps({ } }); -const emit = defineEmits(['visible-change', 'close', 'complete']); +const emit = defineEmits(['visible-change', 'close', 'complete', 'session-end']); // ── Estado interno ───────────────────────────────────────────── const exists = ref(false); @@ -112,8 +112,24 @@ function toggle() { if (timer) clearInterval(timer); timer = null; running.value = false; + + // Tempo total cronometrado antes do reset. Pode estourar a duracao + // (seconds < 0) — somamos o overshoot. Ex: 50min config, parou em + // 53min => seconds=-180 => elapsed = 3000-(-180) = 3180s. + const elapsedSec = (props.duracaoMinutos * 60) - seconds.value; + // Zera ao parar — sessão acabou, deixa pronto pra próxima seconds.value = props.duracaoMinutos * 60; + + // Emite pro pai gravar no DB. Threshold de 5s pra ignorar + // start/stop acidentais. So' faz sentido se ha paciente. + if (pacienteId.value && elapsedSec > 5) { + emit('session-end', { + pacienteId: pacienteId.value, + elapsedSec, + stoppedAt: new Date().toISOString() + }); + } } else { if (timer) clearInterval(timer); timer = setInterval(() => { diff --git a/src/layout/melissa/MelissaDescontos.vue b/src/layout/melissa/MelissaDescontos.vue new file mode 100644 index 0000000..c7a2bad --- /dev/null +++ b/src/layout/melissa/MelissaDescontos.vue @@ -0,0 +1,427 @@ + + + + + diff --git a/src/layout/melissa/MelissaExcecoes.vue b/src/layout/melissa/MelissaExcecoes.vue new file mode 100644 index 0000000..91361ca --- /dev/null +++ b/src/layout/melissa/MelissaExcecoes.vue @@ -0,0 +1,386 @@ + + + + + diff --git a/src/layout/melissa/MelissaHeroClock.vue b/src/layout/melissa/MelissaHeroClock.vue index 2e80235..59c2f23 100644 --- a/src/layout/melissa/MelissaHeroClock.vue +++ b/src/layout/melissa/MelissaHeroClock.vue @@ -28,16 +28,20 @@ defineProps({ saudacao: { type: String, required: true }, usuario: { type: String, default: 'Dr. Leonardo' }, resumoPartes: { type: Array, default: () => [] }, - filtroTipo: { type: String, default: null } + filtroTipo: { type: String, default: null }, + // Quando true, cada bloco de texto (relogio, data, saudacao, resumo) + // ganha um fundo solido translucido + borda + padding. Util pra quem + // usa wallpapers com pouca transparencia onde o text-shadow nao basta. + textBg: { type: Boolean, default: false } }); const emit = defineEmits(['cronometro', 'toggle-filtro']); @@ -128,4 +134,28 @@ const emit = defineEmits(['cronometro', 'toggle-filtro']); font-weight: 500; border-bottom: 1px solid var(--m-text); } + +/* ─── Modo "fundo nos textos soltos" ────────────────────────── + Toggle no painel Personalizar. Cada bloco de texto (.hero-text) + ganha um fundo solido translucido + borda + padding pra ficar + legivel sobre wallpapers com pouca transparencia. Cor flipa + com light/dark via --m-hero-text-bg / --m-hero-text-border. */ +.has-text-bg .hero-text { + display: inline-block; + background: var(--m-hero-text-bg, rgba(0, 0, 0, 0.6)); + border: 1px solid var(--m-hero-text-border, rgba(255, 255, 255, 0.12)); + border-radius: 12px; + padding: 4px 14px; + backdrop-filter: blur(6px); + -webkit-backdrop-filter: blur(6px); +} +/* Relogio: padding maior porque o texto e' gigante */ +.has-text-bg .clock-display .hero-text { + padding: 6px 22px; + border-radius: 18px; +} +/* Linhas inferiores (data, saudacao, resumo) ganham margem extra + pra o fundo nao colar visualmente no de cima. */ +.has-text-bg .hero-line { margin-top: 12px; } +.has-text-bg .hero-line:first-of-type { margin-top: 8px; } diff --git a/src/layout/melissa/MelissaLayout.vue b/src/layout/melissa/MelissaLayout.vue index 59108bb..f22826e 100644 --- a/src/layout/melissa/MelissaLayout.vue +++ b/src/layout/melissa/MelissaLayout.vue @@ -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) {
+ + + + +
+
+ +
+
@@ -617,6 +701,10 @@ onBeforeUnmount(() => {
+ + +
@@ -907,6 +995,101 @@ onBeforeUnmount(() => {
+ + + +
+
+
+
+
Pré-visualização
+
Como seu negócio aparece
+
+
+
+
+
+ +

Preencha os dados
pra ver o preview

+
+ +
+
+
+
diff --git a/src/layout/melissa/MelissaPagamento.vue b/src/layout/melissa/MelissaPagamento.vue index 0912338..a01e9ab 100644 --- a/src/layout/melissa/MelissaPagamento.vue +++ b/src/layout/melissa/MelissaPagamento.vue @@ -14,6 +14,7 @@ * Logica espelhada do ConfiguracoesPagamentoPage (tabela payment_settings). */ import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; +import MelissaConfigList from './MelissaConfigList.vue'; import { useToast } from 'primevue/usetoast'; import { supabase } from '@/lib/supabase/client'; import { useTenantStore } from '@/stores/tenantStore'; @@ -35,6 +36,11 @@ function _onMqMobileChange(e) { function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } function fecharDrawer() { drawerOpen.value = false; } +// Toggle entre cards (default) e lista de configs (alterna inline na sidebar) +const cfgOpen = ref(false); +function toggleCfg() { cfgOpen.value = !cfgOpen.value; } +function fecharCfg() { cfgOpen.value = false; } + // ── Estado ───────────────────────────────────────────────── const loading = ref(true); const ownerId = ref(null); @@ -253,7 +259,15 @@ onBeforeUnmount(() => {
- +
@@ -479,18 +481,6 @@ onBeforeUnmount(() => {
-
-
- -
-
@@ -502,26 +492,13 @@ onBeforeUnmount(() => { >
Dinheiro (espécie)
-
Pagamento presencial em dinheiro
-
- -
-
-

- {{ cfg.dinheiro_ativo - ? 'Aceita pagamento em espécie nas sessões presenciais.' - : 'Não aceita pagamento em espécie.' }} -

-
- +
+ {{ cfg.dinheiro_ativo + ? 'Aceita pagamento em espécie nas sessões presenciais.' + : 'Não aceita pagamento em espécie.' }} +
+
@@ -536,7 +513,7 @@ onBeforeUnmount(() => {
Cartão (maquininha)
Crédito e débito presencial
- +
@@ -560,18 +537,6 @@ onBeforeUnmount(() => {
-
-
- -
-
@@ -589,7 +554,7 @@ onBeforeUnmount(() => { : 'Atendimento por convênio' }} - +
@@ -616,18 +581,6 @@ onBeforeUnmount(() => {
-
-
- -
-
@@ -800,6 +753,50 @@ onBeforeUnmount(() => { border-radius: 3px; } +/* Botao "Configuracoes" no topo da .mpg-side. Click alterna entre + cards (default) e lista de configs (estado is-open: vira "Voltar"). */ +.mpg-cfg-btn { + display: flex; + align-items: center; + gap: 8px; + width: calc(100% - 24px); + margin: 12px 12px 0; + padding: 10px 12px; + background: var(--m-bg-medium); + border: 1px solid var(--m-border); + border-radius: 9px; + color: var(--m-text); + cursor: pointer; + font-family: inherit; + font-size: 0.84rem; + font-weight: 600; + text-align: left; + transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease; + flex-shrink: 0; +} +.mpg-cfg-btn:hover { + background: var(--m-bg-soft-hover); + border-color: var(--m-border-strong); +} +.mpg-cfg-btn.is-open { + background: color-mix(in srgb, var(--p-primary-color) 14%, transparent); + border-color: color-mix(in srgb, var(--p-primary-color) 38%, transparent); + color: var(--p-primary-color); +} +.mpg-cfg-btn > i:first-child { + color: var(--p-primary-color); + font-size: 0.92rem; +} +.mpg-cfg-btn > span { flex: 1; } +.mpg-cfg-btn__chev { + color: var(--m-text-muted); + font-size: 0.7rem; +} +.mpg-side__scroll--cfg { + padding: 8px; + gap: 0; +} + .mpg-main { flex: 1; min-width: 0; @@ -823,6 +820,16 @@ onBeforeUnmount(() => { pq aqui tambem o conteudo varia muito). flex-shrink: 0 evita compressao quando o total passa do .mpg-main; scroll externo. */ @media (min-width: 1024px) { + /* Fake dialog: largura adaptativa, alinhada a esquerda (apos o + config-aside global). Espelha o pattern de MelissaSeguranca: + - 1024px–1012px : full-width (right: 6px) — overlap minimo + - 1012px–2012px : width = 1000px fixo (right cresce com viewport) + - >= 2012px : width = ~50% do viewport (right: 50%) + Formula max-min-calc resolve os 3 casos numa expressao so. */ + .mpg-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } + .mpg-main > .mpg-w, .mpg-side > .mpg-side__scroll > .mpg-w--side { flex-shrink: 0; @@ -1068,23 +1075,6 @@ onBeforeUnmount(() => { } .mpg-mobile-drawer.is-open { transform: translateX(0); } .mpg-mobile-drawer__scroll { - flex: 1; - min-height: 0; - overflow: hidden; - display: flex; - flex-direction: column; -} -.mpg-mobile-drawer__scroll .mpg-side { - flex: 1; - min-height: 0; - width: 100%; - overflow: hidden; - background: transparent; - border-right: none; - display: flex; - flex-direction: column; -} -.mpg-mobile-drawer__scroll .mpg-side__scroll { flex: 1; min-height: 0; overflow-y: auto; @@ -1096,14 +1086,15 @@ onBeforeUnmount(() => { scrollbar-width: thin; scrollbar-color: var(--m-border-strong) transparent; } -.mpg-mobile-drawer__scroll .mpg-side__scroll::-webkit-scrollbar { width: 5px; } -.mpg-mobile-drawer__scroll .mpg-side__scroll::-webkit-scrollbar-thumb { +.mpg-mobile-drawer__scroll::-webkit-scrollbar { width: 5px; } +.mpg-mobile-drawer__scroll::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; } -.mpg-mobile-drawer__scroll .mpg-w--side { - margin: 0; - flex-shrink: 0; +/* No mobile a .mpg-side e teleportada pra dentro do drawer scroll. */ +.mpg-mobile-drawer__scroll .mpg-side { + width: 100%; + border-right: none; } .mpg-mobile-drawer__backdrop { @@ -1122,7 +1113,7 @@ onBeforeUnmount(() => { /* ═══════ Mobile (<1024px) ═══════ */ @media (max-width: 1023px) { .mpg-body { flex-direction: column; padding: 0; } - .mpg-side { display: none; } + .mpg-body > .mpg-side { display: none; } .mpg-main { width: 100%; padding: 8px; } .mpg-main .mpg-w { height: auto; diff --git a/src/layout/melissa/MelissaPerfil.vue b/src/layout/melissa/MelissaPerfil.vue index 8837eef..180a245 100644 --- a/src/layout/melissa/MelissaPerfil.vue +++ b/src/layout/melissa/MelissaPerfil.vue @@ -21,7 +21,7 @@ import { useConfirm } from 'primevue/useconfirm'; import { useRouter } from 'vue-router'; import { supabase } from '@/lib/supabase/client'; import { useTenantStore } from '@/stores/tenantStore'; -import MelissaConfigSidebar from './MelissaConfigSidebar.vue'; +import MelissaConfigList from './MelissaConfigList.vue'; // InputText/Select/Textarea/InputMask/Skeleton/Tag/Button: auto via PrimeVueResolver const emit = defineEmits(['close']); @@ -44,6 +44,12 @@ function _onMqMobileChange(e) { function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } function fecharDrawer() { drawerOpen.value = false; } +// Toggle entre cards contextuais (default) e lista de configs (alterna +// inline na .mpr-side via v-if — sem overlay/popover, zero overhead). +const cfgOpen = ref(false); +function toggleCfg() { cfgOpen.value = !cfgOpen.value; } +function fecharCfg() { cfgOpen.value = false; } + // ── Estado ───────────────────────────────────────────────── const loading = ref(true); const saving = ref(false); @@ -465,21 +471,17 @@ onBeforeUnmount(() => {