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:
@@ -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).
|
||||
|
||||
Reference in New Issue
Block a user