diff --git a/Obsidian/Brain/log.md b/Obsidian/Brain/log.md index 11d69be..060f773 100644 --- a/Obsidian/Brain/log.md +++ b/Obsidian/Brain/log.md @@ -49,3 +49,53 @@ Touched: none ## [2026-05-08 00:00] session | Melissa cfg-* nativas + temas + cronometro DB Touched: none + +## [2026-05-08 09:30] session | Chrome+preview em 7 paginas Melissa (LinkExterno preview novo) +Touched: none (sem mudanca de wiki - aplicacao do pattern existente) +Detalhes: Aplicou o chrome `right: max(6px, min(50%, calc(100% - 1006px)))` +em 6 paginas tabulares (CadastrosRecebidos .mcr / Recorrencias .mr / Grupos +.mg / Tags .mt / Compromissos .mc / Medicos .mm) - so o tamanho de janela, +sem preview, conforme pedido pelo user. Adicionou novo @media (min-width: +1024px) ao final de cada arquivo (cada um nao tinha esse breakpoint ainda). + +MelissaLinkExterno (.ml) ganhou tratamento completo: chrome + sidebar +restruturada (2-col com aside agora a ESQUERDA, antes era a direita) + +mobile drawer pattern (Teleport pro #ml-mobile-drawer-target, transitions, +backdrop, botao Menu mobile-only) + 3-way teleport do preview (mobile=topo +do main / mid-desktop=bottom da sidebar / wide-desktop>=1340=floating glass). +Sidebar agora com Como funciona + Boas praticas (movidos da .ml-side direita) ++ scroll proprio. Sem cfg toggle (nao havia necessidade conceitual). + +Componente novo: src/components/cadastro/CadastroExternoPreview.vue (~350L). +Phone-frame 260px estilo AgendadorPreview replicando o CadastroPacienteExterno +publico: nav (logo Psi + chip verificado), hero (avatar 38px + nome split +firstName/lastName em accent + work_description label + clinic name), stepper +4 dots (1 active), card etapa 1 (numero decorativo + tag "Etapa 1 de 4" + +title "Sobre voce" + 3 input bars + CTA "Continuar"), powered by. Recebe +:token prop e busca info do convite via mesma edge function que o publico +(get-intake-invite-info), watch refetcha quando token rotaciona. Sem token +ou sem dados, fallbacks pra "Profissional" + iniciais. + +ESLint: 0 errors da minha mudanca. 2 errors pre-existentes em +MelissaRecorrencias.vue (totalDone unused L235, v-for/v-bind:key L584) - +nao toquei aquelas linhas. Working tree: 9 arquivos modificados + +src/components/cadastro/ (untracked). Nao commitado, nao testado em browser. + +## [2026-05-08 07:55] session | MelissaAgendador preview celular teleport 3-way +Touched: none (aplicacao do padrao MelissaNegocio - sem mudanca de wiki) +Detalhes: Replicou o padrao floating preview do MelissaNegocio em +MelissaAgendador.vue (+145L). Importou AgendadorPreview (phone-frame ja +existente do legacy ConfiguracoesAgendadorPage). Adicionou ref isWideDesktop ++ matchMedia('(min-width: 1340px)') + computed previewTarget com 3-way +branching: mobile -> #mag-main-preview-target (topo do main, acima de tudo, +DIFERENTE do MelissaNegocio que vai pro drawer); mid-desktop (1024-1339) -> +#mag-sidebar-preview-target (dentro da sidebar apos Status/Resumos); +wide-desktop (>=1340) -> #mag-floating-preview-target (painel flutuante glass +fora do fake dialog, 320px de largura, ancorado a +14px do right edge da +.mag-page). Adicionou regra `right: max(6px, min(50%, calc(100% - 1006px)))` +em .mag-page no @media >=1024px (necessario pra abrir espaco a direita pro +floating). CSS: .mag-floating-preview com glass igual ao fake dialog; +placeholders com display:contents; hide rules por breakpoint. Card de preview +usa mag-w--side e perde fundo/borda no floating (glass do painel ja faz papel). +ESLint 0 errors. Working tree: src/auto-imports.d.ts (auto-gerado) + +MelissaAgendador.vue. Nao commitado, nao testado em browser ainda. diff --git a/src/components/cadastro/CadastroExternoPreview.vue b/src/components/cadastro/CadastroExternoPreview.vue new file mode 100644 index 0000000..e1cd641 --- /dev/null +++ b/src/components/cadastro/CadastroExternoPreview.vue @@ -0,0 +1,476 @@ + + + + + + diff --git a/src/layout/melissa/MelissaAgendador.vue b/src/layout/melissa/MelissaAgendador.vue index 494a0bc..da82ddd 100644 --- a/src/layout/melissa/MelissaAgendador.vue +++ b/src/layout/melissa/MelissaAgendador.vue @@ -17,6 +17,7 @@ import { ref, computed, onMounted, onBeforeUnmount, nextTick } from 'vue'; import MelissaConfigList from './MelissaConfigList.vue'; import JoditTextEditor from '@/components/ui/JoditTextEditor.vue'; +import AgendadorPreview from '@/components/agendador/AgendadorPreview.vue'; import { useToast } from 'primevue/usetoast'; import { supabase } from '@/lib/supabase/client'; import { useTenantStore } from '@/stores/tenantStore'; @@ -41,14 +42,27 @@ const AGENDADOR_BUCKET = 'agendador'; // ── Breakpoints + drawer ─────────────────────────────────── const drawerOpen = ref(false); const isMobile = ref(false); +const isWideDesktop = ref(false); // >= 1340px — preview vira painel flutuante fora do fake dialog let _mqMobile = null; +let _mqWide = null; function _onMqMobileChange(e) { isMobile.value = e.matches; if (!e.matches) drawerOpen.value = false; } +function _onMqWideChange(e) { isWideDesktop.value = e.matches; } function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } function fecharDrawer() { drawerOpen.value = false; } +// Onde o preview do celular e renderizado: +// - mobile: dentro do main, no topo do conteudo (acima de tudo) +// - mid-desktop (1024-1339): dentro da sidebar inline (apos Status/Resumos) +// - wide-desktop (>=1340): painel flutuante fora do fake dialog +const previewTarget = computed(() => { + if (isMobile.value) return '#mag-main-preview-target'; + if (isWideDesktop.value) return '#mag-floating-preview-target'; + return '#mag-sidebar-preview-target'; +}); + // Toggle entre cards (default) e lista de configs (alterna inline na sidebar) const cfgOpen = ref(false); function toggleCfg() { cfgOpen.value = !cfgOpen.value; } @@ -533,6 +547,11 @@ onMounted(async () => { isMobile.value = _mqMobile.matches; try { _mqMobile.addEventListener('change', _onMqMobileChange); } catch { _mqMobile.addListener(_onMqMobileChange); } + + _mqWide = window.matchMedia('(min-width: 1340px)'); + isWideDesktop.value = _mqWide.matches; + try { _mqWide.addEventListener('change', _onMqWideChange); } + catch { _mqWide.addListener(_onMqWideChange); } } await tenantStore.ensureLoaded(); await load(); @@ -543,6 +562,10 @@ onBeforeUnmount(() => { try { _mqMobile.removeEventListener('change', _onMqMobileChange); } catch { _mqMobile.removeListener(_onMqMobileChange); } } + if (_mqWide) { + try { _mqWide.removeEventListener('change', _onMqWideChange); } + catch { _mqWide.removeListener(_onMqWideChange); } + } clearTimeout(_copyTimer); }); @@ -574,6 +597,12 @@ const summaryItems = computed(() => [ /> + + +
+ + +
+ +
+ diff --git a/src/layout/melissa/MelissaCadastrosRecebidos.vue b/src/layout/melissa/MelissaCadastrosRecebidos.vue index 9ca50f4..3bebf5f 100644 --- a/src/layout/melissa/MelissaCadastrosRecebidos.vue +++ b/src/layout/melissa/MelissaCadastrosRecebidos.vue @@ -1887,4 +1887,16 @@ onBeforeUnmount(() => { via tableStyle min-width:640px. Coluna "Ação" frozen à direita continua fixa enquanto o user scrolla horizontalmente. */ } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita pra dock e contexto. + - 1024–1012px : full-width (right: 6px) — overlap minimo + - 1012–2012px : width = 1000px fixo (right cresce com viewport) + - >= 2012px : width = ~50% do viewport (right: 50%) */ +@media (min-width: 1024px) { + .mcr-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} diff --git a/src/layout/melissa/MelissaCompromissos.vue b/src/layout/melissa/MelissaCompromissos.vue index 8ba3edf..3d3821a 100644 --- a/src/layout/melissa/MelissaCompromissos.vue +++ b/src/layout/melissa/MelissaCompromissos.vue @@ -1922,4 +1922,13 @@ async function onDelete(c) { .mc-act-btn--primary span { display: none; } .mc-act-btn--primary { width: 32px; padding: 0; justify-content: center; } } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita. */ +@media (min-width: 1024px) { + .mc-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} diff --git a/src/layout/melissa/MelissaGrupos.vue b/src/layout/melissa/MelissaGrupos.vue index 0c7cb6a..6d5257a 100644 --- a/src/layout/melissa/MelissaGrupos.vue +++ b/src/layout/melissa/MelissaGrupos.vue @@ -2406,4 +2406,13 @@ watch(editPatientDialog, (isOpen) => { .mg-act-btn--primary span { display: none; } .mg-act-btn--primary { width: 32px; padding: 0; justify-content: center; } } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita. */ +@media (min-width: 1024px) { + .mg-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} diff --git a/src/layout/melissa/MelissaLinkExterno.vue b/src/layout/melissa/MelissaLinkExterno.vue index 5eac771..70ebe41 100644 --- a/src/layout/melissa/MelissaLinkExterno.vue +++ b/src/layout/melissa/MelissaLinkExterno.vue @@ -12,9 +12,10 @@ * rotate_patient_invite_token_v2 + copy/openLink). Só o chrome muda pra * casar com o blueprint Melissa (1 header só, sem hero sticky redundante). */ -import { ref, computed, onMounted } from 'vue'; +import { ref, computed, onMounted, onBeforeUnmount } from 'vue'; import { useToast } from 'primevue/usetoast'; import { supabase } from '@/lib/supabase/client'; +import CadastroExternoPreview from '@/components/cadastro/CadastroExternoPreview.vue'; // Button/InputText/InputGroup/InputGroupAddon/Message: auto via PrimeVueResolver const emit = defineEmits(['close']); @@ -25,6 +26,30 @@ const inviteToken = ref(''); const rotating = ref(false); const loading = ref(false); +// ── Breakpoints + drawer ─────────────────────────────────── +const drawerOpen = ref(false); +const isMobile = ref(false); +const isWideDesktop = ref(false); // >= 1340px — preview vira painel flutuante fora do fake dialog +let _mqMobile = null; +let _mqWide = null; +function _onMqMobileChange(e) { + isMobile.value = e.matches; + if (!e.matches) drawerOpen.value = false; +} +function _onMqWideChange(e) { isWideDesktop.value = e.matches; } +function toggleDrawer() { drawerOpen.value = !drawerOpen.value; } +function fecharDrawer() { drawerOpen.value = false; } + +// Onde o preview do celular e renderizado: +// - mobile: dentro do main, no topo do conteudo (acima de tudo) +// - mid-desktop (1024-1339): dentro da sidebar inline (apos cards info) +// - wide-desktop (>=1340): painel flutuante fora do fake dialog +const previewTarget = computed(() => { + if (isMobile.value) return '#ml-main-preview-target'; + if (isWideDesktop.value) return '#ml-floating-preview-target'; + return '#ml-sidebar-preview-target'; +}); + // ── Conteúdo estático ───────────────────────────────────── const howItWorks = [ { n: 1, title: 'Você envia o link', desc: 'Por WhatsApp, e-mail ou mensagem direta.' }, @@ -107,14 +132,67 @@ async function copyInviteMessage() { // ── Lifecycle ────────────────────────────────────────────── onMounted(async () => { + if (typeof window !== 'undefined' && window.matchMedia) { + _mqMobile = window.matchMedia('(max-width: 1023px)'); + isMobile.value = _mqMobile.matches; + try { _mqMobile.addEventListener('change', _onMqMobileChange); } + catch { _mqMobile.addListener(_onMqMobileChange); } + + _mqWide = window.matchMedia('(min-width: 1340px)'); + isWideDesktop.value = _mqWide.matches; + try { _mqWide.addEventListener('change', _onMqWideChange); } + catch { _mqWide.addListener(_onMqWideChange); } + } await loadOrCreateInvite(); }); + +onBeforeUnmount(() => { + if (_mqMobile) { + try { _mqMobile.removeEventListener('change', _onMqMobileChange); } + catch { _mqMobile.removeListener(_onMqMobileChange); } + } + if (_mqWide) { + try { _mqWide.removeEventListener('change', _onMqWideChange); } + catch { _mqWide.removeListener(_onMqWideChange); } + } +}); diff --git a/src/layout/melissa/MelissaMedicos.vue b/src/layout/melissa/MelissaMedicos.vue index 135a8d5..84954b3 100644 --- a/src/layout/melissa/MelissaMedicos.vue +++ b/src/layout/melissa/MelissaMedicos.vue @@ -2546,4 +2546,13 @@ watch(editPatientDialog, (isOpen) => { .mm-act-btn--primary span { display: none; } .mm-act-btn--primary { width: 32px; padding: 0; justify-content: center; } } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita. */ +@media (min-width: 1024px) { + .mm-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} diff --git a/src/layout/melissa/MelissaRecorrencias.vue b/src/layout/melissa/MelissaRecorrencias.vue index 4611c60..ac7affe 100644 --- a/src/layout/melissa/MelissaRecorrencias.vue +++ b/src/layout/melissa/MelissaRecorrencias.vue @@ -1445,4 +1445,13 @@ onBeforeUnmount(() => { .mr-card__btn { font-size: 0.72rem; padding: 5px 9px; } .mr-stats-row { padding: 0 12px 6px; } } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita. */ +@media (min-width: 1024px) { + .mr-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +} diff --git a/src/layout/melissa/MelissaTags.vue b/src/layout/melissa/MelissaTags.vue index 6565db3..1386a1b 100644 --- a/src/layout/melissa/MelissaTags.vue +++ b/src/layout/melissa/MelissaTags.vue @@ -2368,4 +2368,13 @@ watch(editPatientDialog, (isOpen) => { .mt-act-btn--primary span { display: none; } .mt-act-btn--primary { width: 32px; padding: 0; justify-content: center; } } + +/* ═══════ Fake dialog: largura adaptativa (>=1024px) ═══════ + Espelha pattern de MelissaAgendador/Negocio — fica mesma janela, + drawer a esquerda, sobra espaco a direita. */ +@media (min-width: 1024px) { + .mt-page { + right: max(6px, min(50%, calc(100% - 1006px))); + } +}