Drawer mobile + footer colado + Menu nomeado + tenant ensureLoaded
Tres ajustes globais nas Melissa Pages com sidebar:
1) FOOTER "Limpar filtros" colado no bottom do drawer mobile
Problema: o sticky bottom precisa que algum container parent
tenha altura definida e overflow. No drawer, o `.xx-side` tinha
`height: auto` — entao o footer ficava no fluxo natural (logo
apos os cards) mesmo com pouco conteudo, em vez de empurrado pro
bottom do drawer.
Fix: `.xx-mobile-drawer__scroll .xx-side` ganha
`flex: 1; min-height: 0; display: flex; flex-direction: column`
pra ocupar altura disponivel; o `.xx-side__footer` ganha
`margin: auto -12px -24px` (margin-top: auto empurra pro fim).
Sticky bottom continua pro caso de scroll com muito conteudo.
Aplicado em: Compromissos, Grupos, Tags, Medicos, Conversas,
Recorrencias, Pacientes (caso especial — separa .mp-side de
.mp-quick), Cadastros Recebidos, FinanceiroLancamentos.
2) DRAWER MOBILE adicionado em Notificacoes, Documentos e
Relatorios (estavam com sidebar virando topo via max-height
50vh — faltava o pattern oficial das demais Melissa Pages).
Pattern aplicado:
- Aside host com id="<prefix>-mobile-drawer-target" + Transition
backdrop com fade
- Botao "Menu <Secao>" no header (esquerda do titulo)
- <Teleport :disabled="!isMobile"> envolvendo a sidebar
- Script: drawerOpen + isMobile + matchMedia listener registrado
no onMounted, removido no onBeforeUnmount
- CSS completo: .xx-mobile-drawer (fixed, transform translateX),
__scroll (overflow + padding), __backdrop (rgba 0.45 + blur),
overrides quando teleportada (sidebar perde bg/border-right,
footer vira sticky bottom com margin-top auto)
3) Botao "Menu" passa a ter sufixo da pagina:
- "Menu Lancamentos" (FinanceiroLancamentos)
- "Menu Notificacoes" (Notificacoes)
- "Menu Documentos" (Documentos)
- "Menu Relatorios" (Relatorios)
- "Menu Agendamentos" (AgendamentosRecebidos — corrigido tambem)
4) Bug de "lista vazia ao carregar via URL direto":
FinanceiroLancamentos e Relatorios usam composables que dependem
de tenantStore.activeTenantId. Quando aberta direto via URL
(sem navegar pelo menu), o tenantStore pode nao estar inicializado
ainda — entao fetchRecords() / loadSessions() retornam vazio.
Fix: adicionar `await tenantStore.ensureLoaded()` no onMounted
antes do fetch. Ja era pattern usado em outras Melissa Pages
(Compromissos, etc).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
||||
* RPCs + dialogs registrar pagamento e lançamento manual). Só o chrome
|
||||
* muda pra eliminar o triplo-header.
|
||||
*/
|
||||
import { ref, computed, watch, onMounted } from 'vue';
|
||||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConfirm } from 'primevue/useconfirm';
|
||||
import { supabase } from '@/lib/supabase/client';
|
||||
@@ -27,6 +27,17 @@ const toast = useToast();
|
||||
const confirm = useConfirm();
|
||||
const tenantStore = useTenantStore();
|
||||
|
||||
// ── Breakpoints + drawer mobile (mesmo pattern das demais Melissa Pages) ──
|
||||
const drawerOpen = ref(false);
|
||||
const isMobile = ref(false);
|
||||
let _mqMobile = null;
|
||||
function _onMqMobileChange(e) {
|
||||
isMobile.value = e.matches;
|
||||
if (!e.matches) drawerOpen.value = false;
|
||||
}
|
||||
function toggleDrawer() { drawerOpen.value = !drawerOpen.value; }
|
||||
function fecharDrawer() { drawerOpen.value = false; }
|
||||
|
||||
// ── Helpers de formatação ─────────────────────────────
|
||||
const _brl = new Intl.NumberFormat('pt-BR', { style: 'currency', currency: 'BRL' });
|
||||
function fmtBRL(v) { return _brl.format(v ?? 0); }
|
||||
@@ -258,15 +269,54 @@ async function saveManualRecord() {
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────
|
||||
onMounted(async () => {
|
||||
if (typeof window !== 'undefined' && window.matchMedia) {
|
||||
_mqMobile = window.matchMedia('(max-width: 1023px)');
|
||||
isMobile.value = _mqMobile.matches;
|
||||
_mqMobile.addEventListener('change', _onMqMobileChange);
|
||||
}
|
||||
// Garante tenant carregado antes de fetch — sem isso, o useFinancialRecords
|
||||
// retorna vazio na primeira render direta via URL (bug: lista vazia até
|
||||
// navegar pelo menu e remontar o componente).
|
||||
if (typeof tenantStore.ensureLoaded === 'function') {
|
||||
await tenantStore.ensureLoaded();
|
||||
}
|
||||
await Promise.all([loadPatients(), applyFilters()]);
|
||||
});
|
||||
onBeforeUnmount(() => {
|
||||
if (_mqMobile) _mqMobile.removeEventListener('change', _onMqMobileChange);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ConfirmDialog />
|
||||
|
||||
<!-- Drawer host (mobile) — sidebar é teleportada pra cá quando isMobile -->
|
||||
<aside
|
||||
class="mfl-mobile-drawer"
|
||||
:class="{ 'is-open': drawerOpen }"
|
||||
v-show="isMobile"
|
||||
aria-label="Estatísticas e filtros"
|
||||
>
|
||||
<div id="mfl-mobile-drawer-target" class="mfl-mobile-drawer__scroll" />
|
||||
</aside>
|
||||
<Transition name="mfl-drawer-fade">
|
||||
<div
|
||||
v-if="isMobile && drawerOpen"
|
||||
class="mfl-mobile-drawer__backdrop"
|
||||
@click="fecharDrawer"
|
||||
/>
|
||||
</Transition>
|
||||
|
||||
<section class="mfl-page">
|
||||
<header class="mfl-page__head">
|
||||
<button
|
||||
class="mfl-menu-btn mfl-menu-btn--mobile-only"
|
||||
v-tooltip.bottom="'Estatísticas & filtros'"
|
||||
@click="toggleDrawer"
|
||||
>
|
||||
<i class="pi pi-bars" />
|
||||
<span>Menu Lançamentos</span>
|
||||
</button>
|
||||
<div class="mfl-page__title">
|
||||
<i class="pi pi-list mfl-page__title-icon" />
|
||||
<span>Lançamentos financeiros</span>
|
||||
@@ -308,6 +358,7 @@ onMounted(async () => {
|
||||
|
||||
<div class="mfl-body">
|
||||
<!-- ═══ COL 1: Stats + filtros ═══ -->
|
||||
<Teleport to="#mfl-mobile-drawer-target" :disabled="!isMobile">
|
||||
<aside class="mfl-side">
|
||||
<div class="mfl-side__scroll">
|
||||
<!-- Stats -->
|
||||
@@ -461,6 +512,7 @@ onMounted(async () => {
|
||||
</div>
|
||||
</Transition>
|
||||
</aside>
|
||||
</Teleport>
|
||||
|
||||
<!-- ═══ COL 2: DataTable ═══ -->
|
||||
<div class="mfl-main">
|
||||
@@ -1507,17 +1559,124 @@ onMounted(async () => {
|
||||
color: white !important;
|
||||
}
|
||||
|
||||
/* ─── Botão "Menu" mobile (abre drawer com sidebar) ─── */
|
||||
.mfl-menu-btn {
|
||||
display: none;
|
||||
height: 32px;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-shrink: 0;
|
||||
background: var(--m-accent);
|
||||
border: 1px solid var(--m-accent);
|
||||
color: white;
|
||||
padding: 0 11px;
|
||||
border-radius: 9px;
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 600;
|
||||
transition: background-color 140ms ease, transform 140ms ease;
|
||||
}
|
||||
.mfl-menu-btn:hover {
|
||||
background: color-mix(in srgb, var(--m-accent) 88%, white);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.mfl-menu-btn > i { font-size: 0.85rem; }
|
||||
|
||||
/* ─── Drawer mobile (Teleport target) ─── */
|
||||
.mfl-mobile-drawer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
width: min(360px, 88vw);
|
||||
z-index: 80;
|
||||
background: var(--m-bg-medium);
|
||||
backdrop-filter: blur(28px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(28px) saturate(160%);
|
||||
border-right: 1px solid var(--m-border);
|
||||
transform: translateX(-100%);
|
||||
transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);
|
||||
color: var(--m-text);
|
||||
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
||||
}
|
||||
.mfl-mobile-drawer.is-open { transform: translateX(0); }
|
||||
.mfl-mobile-drawer__scroll {
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
padding: 12px 12px 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--m-border-strong) transparent;
|
||||
}
|
||||
.mfl-mobile-drawer__scroll::-webkit-scrollbar { width: 5px; }
|
||||
.mfl-mobile-drawer__scroll::-webkit-scrollbar-thumb {
|
||||
background: var(--m-border-strong);
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Sidebar teleportada perde bg/border-right (drawer já tem chrome próprio).
|
||||
Cards perdem margem lateral (drawer já tem padding). Footer vira sticky
|
||||
no bottom do drawer pra "Limpar filtros" sempre visível. */
|
||||
.mfl-mobile-drawer__scroll .mfl-side {
|
||||
width: 100%;
|
||||
flex: 1;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border-right: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.mfl-mobile-drawer__scroll .mfl-side__scroll {
|
||||
flex: none;
|
||||
min-height: 0;
|
||||
overflow: visible;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
.mfl-mobile-drawer__scroll .mfl-w--side {
|
||||
margin: 0;
|
||||
}
|
||||
.mfl-mobile-drawer__scroll .mfl-w--side:last-of-type { margin-bottom: 0; }
|
||||
.mfl-mobile-drawer__scroll .mfl-side__footer {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
margin: auto -12px -24px;
|
||||
padding: 12px;
|
||||
background: var(--m-bg-medium);
|
||||
border-top: 1px solid var(--m-border);
|
||||
backdrop-filter: blur(24px) saturate(160%);
|
||||
-webkit-backdrop-filter: blur(24px) saturate(160%);
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.mfl-mobile-drawer__backdrop {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.45);
|
||||
backdrop-filter: blur(4px);
|
||||
-webkit-backdrop-filter: blur(4px);
|
||||
z-index: 79;
|
||||
}
|
||||
.mfl-drawer-fade-enter-active,
|
||||
.mfl-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
||||
.mfl-drawer-fade-enter-from,
|
||||
.mfl-drawer-fade-leave-to { opacity: 0; }
|
||||
|
||||
/* ─── Mobile (<1024px) ─── */
|
||||
@media (max-width: 1023px) {
|
||||
/* Sidebar saiu pro drawer via Teleport — body fica só com .mfl-main. */
|
||||
.mfl-body { flex-direction: column; padding: 0; }
|
||||
.mfl-side {
|
||||
width: 100%;
|
||||
max-height: 50vh;
|
||||
border-right: none;
|
||||
border-bottom: 1px solid var(--m-border);
|
||||
}
|
||||
.mfl-main { padding: 8px; }
|
||||
.mfl-main { width: 100%; padding: 8px; }
|
||||
.mfl-page__title > span:first-of-type { display: none; }
|
||||
.mfl-menu-btn--mobile-only { display: inline-flex; }
|
||||
.mfl-act-btn--primary span { display: none; }
|
||||
.mfl-act-btn--primary { width: 32px; padding: 0; justify-content: center; }
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user