d8ce33f74f
Inverte a ordem dentro do .mr-charts-row via flex order — "Sessoes no periodo" (tabela) em 1o lugar, "Sessoes por mes/semana" (chart) em 2o. Vale pra desktop (esquerda/direita) e mobile (em cima/embaixo). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1217 lines
42 KiB
Vue
1217 lines
42 KiB
Vue
<script setup>
|
|
/*
|
|
* MelissaRelatorios — Página nativa Melissa pra relatórios de sessões
|
|
* (substitui o embed). Aplica blueprint melissa-table-page-blueprint.md.
|
|
*
|
|
* Layout 2-col:
|
|
* - COL 1 — Sidebar: stats clicáveis + filtros (Período / Status na
|
|
* tabela) + footer fixo "Limpar filtros"
|
|
* - COL 2 — Main: gráfico + DataTable de sessões filtradas
|
|
*
|
|
* Lógica idêntica à RelatoriosPage (query agenda_eventos + grouping
|
|
* isoWeek/isoMonth + Chart.js).
|
|
*/
|
|
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
|
import { supabase } from '@/lib/supabase/client';
|
|
import { useTenantStore } from '@/stores/tenantStore';
|
|
// Chart/DataTable/Column/Tag/Skeleton: auto via PrimeVueResolver
|
|
|
|
const emit = defineEmits(['close']);
|
|
const tenantStore = useTenantStore();
|
|
|
|
// ── Breakpoints + drawer mobile ────────────────────────
|
|
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; }
|
|
|
|
// ── Período ────────────────────────────────────────────
|
|
const PERIOD_OPTIONS = [
|
|
{ key: 'week', label: 'Esta semana', icon: 'pi pi-calendar' },
|
|
{ key: 'month', label: 'Este mês', icon: 'pi pi-calendar' },
|
|
{ key: '3months', label: 'Últimos 3 meses', icon: 'pi pi-calendar-clock' },
|
|
{ key: '6months', label: 'Últimos 6 meses', icon: 'pi pi-calendar-clock' }
|
|
];
|
|
|
|
const selectedPeriod = ref('month');
|
|
|
|
function periodRange(period) {
|
|
const now = new Date();
|
|
let start, end;
|
|
if (period === 'week') {
|
|
start = new Date(now);
|
|
start.setDate(now.getDate() - now.getDay());
|
|
start.setHours(0, 0, 0, 0);
|
|
end = new Date(now);
|
|
end.setHours(23, 59, 59, 999);
|
|
} else if (period === 'month') {
|
|
start = new Date(now.getFullYear(), now.getMonth(), 1, 0, 0, 0, 0);
|
|
end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
} else if (period === '3months') {
|
|
start = new Date(now.getFullYear(), now.getMonth() - 2, 1, 0, 0, 0, 0);
|
|
end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
} else if (period === '6months') {
|
|
start = new Date(now.getFullYear(), now.getMonth() - 5, 1, 0, 0, 0, 0);
|
|
end = new Date(now.getFullYear(), now.getMonth() + 1, 0, 23, 59, 59, 999);
|
|
}
|
|
return { start, end };
|
|
}
|
|
|
|
const periodLabel = computed(() =>
|
|
PERIOD_OPTIONS.find((p) => p.key === selectedPeriod.value)?.label ?? ''
|
|
);
|
|
|
|
// ── Dados ──────────────────────────────────────────────
|
|
const loading = ref(false);
|
|
const hasLoaded = ref(false);
|
|
const sessions = ref([]);
|
|
const loadError = ref('');
|
|
|
|
async function loadSessions() {
|
|
const uid = tenantStore.user?.id || null;
|
|
const tenantId = tenantStore.activeTenantId || null;
|
|
if (!uid || !tenantId) return;
|
|
|
|
const { start, end } = periodRange(selectedPeriod.value);
|
|
loading.value = true;
|
|
loadError.value = '';
|
|
sessions.value = [];
|
|
|
|
try {
|
|
const { data, error } = await supabase
|
|
.from('agenda_eventos')
|
|
.select('id, inicio_em, fim_em, status, modalidade, tipo, titulo, titulo_custom, patient_id, patients(nome_completo)')
|
|
.eq('tenant_id', tenantId)
|
|
.eq('owner_id', uid)
|
|
.gte('inicio_em', start.toISOString())
|
|
.lte('inicio_em', end.toISOString())
|
|
.order('inicio_em', { ascending: false })
|
|
.limit(500);
|
|
if (error) throw error;
|
|
sessions.value = data || [];
|
|
} catch (e) {
|
|
loadError.value = e?.message || 'Falha ao carregar relatório.';
|
|
} finally {
|
|
loading.value = false;
|
|
hasLoaded.value = true;
|
|
}
|
|
}
|
|
|
|
// ── Métricas agregadas ────────────────────────────────
|
|
const total = computed(() => sessions.value.length);
|
|
const realizadas = computed(() => sessions.value.filter((s) => s.status === 'realizado').length);
|
|
const faltas = computed(() => sessions.value.filter((s) => s.status === 'faltou').length);
|
|
const canceladas = computed(() => sessions.value.filter((s) => s.status === 'cancelado').length);
|
|
const agendadas = computed(() => sessions.value.filter((s) => !s.status || s.status === 'agendado').length);
|
|
const taxaRealizacao = computed(() => {
|
|
const denom = realizadas.value + faltas.value + canceladas.value;
|
|
if (!denom) return null;
|
|
return Math.round((realizadas.value / denom) * 100);
|
|
});
|
|
|
|
// ── Filtro de status na tabela ────────────────────────
|
|
const statusFilter = ref(null); // null | 'realizado' | 'faltou' | 'cancelado' | 'agendado'
|
|
|
|
const STATUS_FILTER_OPTIONS = [
|
|
{ key: 'realizado', label: 'Realizadas', icon: 'pi pi-check-circle' },
|
|
{ key: 'faltou', label: 'Faltas', icon: 'pi pi-times-circle' },
|
|
{ key: 'cancelado', label: 'Canceladas', icon: 'pi pi-ban' },
|
|
{ key: 'agendado', label: 'Agendadas', icon: 'pi pi-calendar' }
|
|
];
|
|
|
|
const sessionsFiltered = computed(() => {
|
|
if (!statusFilter.value) return sessions.value;
|
|
if (statusFilter.value === 'agendado') {
|
|
return sessions.value.filter((s) => !s.status || s.status === 'agendado');
|
|
}
|
|
return sessions.value.filter((s) => s.status === statusFilter.value);
|
|
});
|
|
|
|
function setStatusFilter(s) {
|
|
statusFilter.value = statusFilter.value === s ? null : s;
|
|
}
|
|
|
|
const hasActiveFilters = computed(() =>
|
|
selectedPeriod.value !== 'month' || statusFilter.value !== null
|
|
);
|
|
|
|
function clearAllFilters() {
|
|
statusFilter.value = null;
|
|
if (selectedPeriod.value !== 'month') {
|
|
selectedPeriod.value = 'month';
|
|
}
|
|
}
|
|
|
|
// ── Stats com classes pra cores ───────────────────────
|
|
const stats = computed(() => [
|
|
{ key: 'total', label: 'Total', value: total.value, cls: 'neutral' },
|
|
{ key: 'realizado', label: 'Realizadas', value: realizadas.value, cls: realizadas.value > 0 ? 'ok' : 'neutral' },
|
|
{ key: 'faltou', label: 'Faltas', value: faltas.value, cls: faltas.value > 0 ? 'danger' : 'neutral' },
|
|
{ key: 'cancelado', label: 'Canceladas', value: canceladas.value, cls: canceladas.value > 0 ? 'warn' : 'neutral' },
|
|
{ key: 'agendado', label: 'Agendadas', value: agendadas.value, cls: agendadas.value > 0 ? 'info' : 'neutral' },
|
|
{ key: 'taxa', label: 'Taxa realização', value: taxaRealizacao.value != null ? `${taxaRealizacao.value}%` : '—', cls: taxaRealizacao.value != null && taxaRealizacao.value >= 85 ? 'ok' : 'neutral' }
|
|
]);
|
|
|
|
// ── Gráfico ────────────────────────────────────────────
|
|
function isoWeek(d) {
|
|
const dt = new Date(d);
|
|
const day = dt.getDay() || 7;
|
|
dt.setDate(dt.getDate() + 4 - day);
|
|
const yearStart = new Date(dt.getFullYear(), 0, 1);
|
|
const wk = Math.ceil(((dt - yearStart) / 86400000 + 1) / 7);
|
|
return `${dt.getFullYear()}-S${String(wk).padStart(2, '0')}`;
|
|
}
|
|
function isoMonth(d) {
|
|
const dt = new Date(d);
|
|
return `${dt.getFullYear()}-${String(dt.getMonth() + 1).padStart(2, '0')}`;
|
|
}
|
|
function monthLabel(key) {
|
|
const [y, m] = key.split('-');
|
|
const names = ['Jan', 'Fev', 'Mar', 'Abr', 'Mai', 'Jun', 'Jul', 'Ago', 'Set', 'Out', 'Nov', 'Dez'];
|
|
return `${names[Number(m) - 1]}/${y}`;
|
|
}
|
|
|
|
const chartData = computed(() => {
|
|
const groupBy = selectedPeriod.value === 'week' ? isoWeek : isoMonth;
|
|
const labelFn = selectedPeriod.value === 'week' ? (k) => k : monthLabel;
|
|
const buckets = {};
|
|
for (const s of sessions.value) {
|
|
const key = groupBy(s.inicio_em);
|
|
if (!buckets[key]) buckets[key] = { realizado: 0, faltou: 0, cancelado: 0, outros: 0 };
|
|
const st = s.status || 'agendado';
|
|
if (st === 'realizado') buckets[key].realizado++;
|
|
else if (st === 'faltou') buckets[key].faltou++;
|
|
else if (st === 'cancelado') buckets[key].cancelado++;
|
|
else buckets[key].outros++;
|
|
}
|
|
const keys = Object.keys(buckets).sort();
|
|
return {
|
|
labels: keys.map(labelFn),
|
|
datasets: [
|
|
{ label: 'Realizadas', backgroundColor: 'rgb(22, 163, 74)', data: keys.map((k) => buckets[k].realizado), barThickness: 20 },
|
|
{ label: 'Faltas', backgroundColor: 'rgb(220, 38, 38)', data: keys.map((k) => buckets[k].faltou), barThickness: 20 },
|
|
{ label: 'Canceladas', backgroundColor: 'rgb(217, 119, 6)', data: keys.map((k) => buckets[k].cancelado), barThickness: 20 },
|
|
{ label: 'Outros', backgroundColor: 'rgb(2, 132, 199)', data: keys.map((k) => buckets[k].outros), barThickness: 20 }
|
|
]
|
|
};
|
|
});
|
|
|
|
const chartOptions = computed(() => {
|
|
const ds = getComputedStyle(document.documentElement);
|
|
const borderColor = ds.getPropertyValue('--p-content-border-color').trim() || '#e2e8f0';
|
|
const textMutedColor = ds.getPropertyValue('--p-text-muted-color').trim() || '#64748b';
|
|
return {
|
|
maintainAspectRatio: false,
|
|
plugins: { legend: { labels: { color: textMutedColor } } },
|
|
scales: {
|
|
x: { stacked: true, ticks: { color: textMutedColor }, grid: { color: 'transparent' } },
|
|
y: { stacked: true, ticks: { color: textMutedColor, precision: 0 }, grid: { color: borderColor, drawTicks: false } }
|
|
}
|
|
};
|
|
});
|
|
|
|
// ── Tabela helpers ─────────────────────────────────────
|
|
const STATUS_LABEL = {
|
|
agendado: 'Agendado',
|
|
realizado: 'Realizado',
|
|
faltou: 'Faltou',
|
|
cancelado: 'Cancelado',
|
|
remarcado: 'Remarcado',
|
|
bloqueado: 'Bloqueado'
|
|
};
|
|
const STATUS_SEVERITY = {
|
|
agendado: 'info',
|
|
realizado: 'success',
|
|
faltou: 'danger',
|
|
cancelado: 'warn',
|
|
remarcado: 'secondary',
|
|
bloqueado: 'secondary'
|
|
};
|
|
|
|
function fmtDateTimeBR(iso) {
|
|
if (!iso) return '—';
|
|
const d = new Date(iso);
|
|
if (Number.isNaN(d.getTime())) return iso;
|
|
const dd = String(d.getDate()).padStart(2, '0');
|
|
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
const yy = d.getFullYear();
|
|
const hh = String(d.getHours()).padStart(2, '0');
|
|
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
return `${dd}/${mm}/${yy} ${hh}:${mi}`;
|
|
}
|
|
function sessionTitle(s) {
|
|
return s.titulo_custom || s.titulo || (s.tipo ? s.tipo : 'Sessão');
|
|
}
|
|
function patientName(s) {
|
|
return s.patients?.nome_completo || '—';
|
|
}
|
|
|
|
watch(selectedPeriod, () => {
|
|
statusFilter.value = null;
|
|
loadSessions();
|
|
});
|
|
|
|
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 carregar sessoes — sem isso, a
|
|
// primeira render via URL direta pode pegar tenantStore vazio.
|
|
if (typeof tenantStore.ensureLoaded === 'function') {
|
|
await tenantStore.ensureLoaded();
|
|
}
|
|
await loadSessions();
|
|
});
|
|
onBeforeUnmount(() => {
|
|
if (_mqMobile) _mqMobile.removeEventListener('change', _onMqMobileChange);
|
|
});
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Drawer host (mobile) -->
|
|
<aside
|
|
class="mr-mobile-drawer"
|
|
:class="{ 'is-open': drawerOpen }"
|
|
v-show="isMobile"
|
|
aria-label="Estatísticas e filtros"
|
|
>
|
|
<div id="mr-mobile-drawer-target" class="mr-mobile-drawer__scroll" />
|
|
</aside>
|
|
<Transition name="mr-drawer-fade">
|
|
<div
|
|
v-if="isMobile && drawerOpen"
|
|
class="mr-mobile-drawer__backdrop"
|
|
@click="fecharDrawer"
|
|
/>
|
|
</Transition>
|
|
|
|
<section class="mr-page">
|
|
<header class="mr-page__head">
|
|
<button
|
|
class="mr-menu-btn mr-menu-btn--mobile-only"
|
|
v-tooltip.bottom="'Estatísticas & filtros'"
|
|
@click="toggleDrawer"
|
|
>
|
|
<i class="pi pi-bars" />
|
|
<span>Menu Relatórios</span>
|
|
</button>
|
|
<div class="mr-page__title">
|
|
<i class="pi pi-chart-bar mr-page__title-icon" />
|
|
<span>Relatórios</span>
|
|
<span class="mr-page__count">{{ periodLabel }}</span>
|
|
</div>
|
|
<div class="mr-page__actions">
|
|
<button
|
|
class="mr-head-btn"
|
|
v-tooltip.bottom="'Recarregar'"
|
|
:disabled="loading"
|
|
@click="loadSessions"
|
|
>
|
|
<i :class="loading ? 'pi pi-spin pi-spinner' : 'pi pi-refresh'" />
|
|
</button>
|
|
<button class="mr-close" v-tooltip.bottom="'Voltar (Esc)'" @click="emit('close')">
|
|
<i class="pi pi-times" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Subheader -->
|
|
<div class="mr-subheader">
|
|
<i class="pi pi-info-circle mr-subheader__icon" />
|
|
<span class="mr-subheader__text">
|
|
Visão geral de <strong>sessões</strong> no período selecionado.
|
|
Mude o período na sidebar à esquerda; os <strong>stats</strong>
|
|
e o <strong>gráfico</strong> se adaptam automaticamente.
|
|
</span>
|
|
</div>
|
|
|
|
<div class="mr-body">
|
|
<!-- ═══ COL 1: Stats + filtros ═══ -->
|
|
<Teleport to="#mr-mobile-drawer-target" :disabled="!isMobile">
|
|
<aside class="mr-side">
|
|
<div class="mr-side__scroll">
|
|
<!-- Stats -->
|
|
<div class="mr-w mr-w--side">
|
|
<div class="mr-w__head">
|
|
<span class="mr-w__title"><i class="pi pi-chart-bar" /> Estatísticas</span>
|
|
</div>
|
|
<div class="mr-stats">
|
|
<div
|
|
v-for="s in stats"
|
|
:key="s.key"
|
|
class="mr-stat"
|
|
:class="`is-${s.cls}`"
|
|
>
|
|
<div class="mr-stat__val">{{ s.value }}</div>
|
|
<div class="mr-stat__lbl">{{ s.label }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro Período -->
|
|
<div class="mr-w mr-w--side">
|
|
<div class="mr-w__head">
|
|
<span class="mr-w__title"><i class="pi pi-calendar" /> Período</span>
|
|
<button
|
|
v-if="selectedPeriod !== 'month'"
|
|
class="mr-side__clear-inline"
|
|
v-tooltip.top="'Voltar pro padrão (Este mês)'"
|
|
@click="selectedPeriod = 'month'"
|
|
>
|
|
<i class="pi pi-times" />
|
|
</button>
|
|
</div>
|
|
<div class="mr-side__list">
|
|
<button
|
|
v-for="o in PERIOD_OPTIONS"
|
|
:key="o.key"
|
|
class="mr-side__item"
|
|
:class="{ 'is-active is-period': selectedPeriod === o.key }"
|
|
@click="selectedPeriod = o.key"
|
|
>
|
|
<i :class="o.icon" />
|
|
<span>{{ o.label }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Filtro Status (na tabela) -->
|
|
<div class="mr-w mr-w--side">
|
|
<div class="mr-w__head">
|
|
<span class="mr-w__title"><i class="pi pi-filter" /> Status</span>
|
|
<button
|
|
v-if="statusFilter"
|
|
class="mr-side__clear-inline"
|
|
v-tooltip.top="'Limpar filtro de status'"
|
|
@click="statusFilter = null"
|
|
>
|
|
<i class="pi pi-times" />
|
|
</button>
|
|
</div>
|
|
<div class="mr-side__list">
|
|
<button
|
|
v-for="o in STATUS_FILTER_OPTIONS"
|
|
:key="o.key"
|
|
class="mr-side__item"
|
|
:class="[`is-status-${o.key}`, { 'is-active': statusFilter === o.key }]"
|
|
@click="setStatusFilter(o.key)"
|
|
>
|
|
<i :class="o.icon" />
|
|
<span>{{ o.label }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<Transition name="mr-clear">
|
|
<div v-if="hasActiveFilters" class="mr-side__footer">
|
|
<button class="mr-side__clear-all" @click="clearAllFilters">
|
|
<i class="pi pi-filter-slash" />
|
|
<span>Limpar filtros</span>
|
|
</button>
|
|
</div>
|
|
</Transition>
|
|
</aside>
|
|
</Teleport>
|
|
|
|
<!-- ═══ COL 2: Gráfico + Tabela ═══ -->
|
|
<div class="mr-main">
|
|
<!-- Erro -->
|
|
<div v-if="loadError" class="mr-error">
|
|
<i class="pi pi-exclamation-triangle" />
|
|
<span>{{ loadError }}</span>
|
|
</div>
|
|
|
|
<div class="mr-charts-row">
|
|
<!-- Gráfico -->
|
|
<div class="mr-card mr-card--chart" style="order: 2;">
|
|
<div class="mr-card__head">
|
|
<div class="mr-card__icon">
|
|
<i class="pi pi-chart-bar" />
|
|
</div>
|
|
<div class="mr-card__title">
|
|
<div class="mr-card__title-text">
|
|
Sessões por {{ selectedPeriod === 'week' ? 'semana' : 'mês' }}
|
|
</div>
|
|
<div class="mr-card__sub">{{ total }} sessão{{ total !== 1 ? 'ões' : '' }} no período</div>
|
|
</div>
|
|
</div>
|
|
<div class="mr-card__body">
|
|
<div v-if="loading" class="mr-chart-skel"><Skeleton height="100%" /></div>
|
|
<div v-else-if="total === 0" class="mr-empty-inline">
|
|
<i class="pi pi-info-circle" /> Sem sessões no período selecionado.
|
|
</div>
|
|
<div v-else class="mr-chart-wrap">
|
|
<Chart type="bar" :data="chartData" :options="chartOptions" style="height: 100%" />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tabela -->
|
|
<div class="mr-card mr-card--table" style="order: 1;">
|
|
<div class="mr-card__head">
|
|
<div class="mr-card__icon">
|
|
<i class="pi pi-table" />
|
|
</div>
|
|
<div class="mr-card__title">
|
|
<div class="mr-card__title-text">Sessões no período</div>
|
|
<div class="mr-card__sub">
|
|
<template v-if="statusFilter">
|
|
Filtradas por <strong>{{ STATUS_LABEL[statusFilter] || statusFilter }}</strong>
|
|
</template>
|
|
<template v-else>
|
|
Lista completa
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<span class="mr-card__count">{{ sessionsFiltered.length }}</span>
|
|
</div>
|
|
|
|
<div v-if="loading" class="mr-card__body">
|
|
<Skeleton v-for="n in 5" :key="`sk-${n}`" height="2.5rem" border-radius="6px" class="mb-2" />
|
|
</div>
|
|
|
|
<div v-else-if="!sessions.length" class="mr-empty">
|
|
<i class="pi pi-chart-bar mr-empty__icon" />
|
|
<div class="mr-empty__title">Nenhuma sessão no período</div>
|
|
<div class="mr-empty__hint">Tente selecionar um período diferente na sidebar.</div>
|
|
</div>
|
|
|
|
<div v-else-if="!sessionsFiltered.length" class="mr-empty">
|
|
<i class="pi pi-filter-slash mr-empty__icon" />
|
|
<div class="mr-empty__title">Nenhuma sessão com este status</div>
|
|
<button class="mr-act-btn mr-empty__btn" @click="statusFilter = null">
|
|
<i class="pi pi-times" />
|
|
<span>Limpar filtro</span>
|
|
</button>
|
|
</div>
|
|
|
|
<DataTable
|
|
v-else
|
|
:value="sessionsFiltered"
|
|
:rows="20"
|
|
paginator
|
|
:rows-per-page-options="[10, 20, 50]"
|
|
scrollable
|
|
scrollHeight="flex"
|
|
class="mr-table"
|
|
>
|
|
<Column field="inicio_em" header="Data / Hora" :sortable="true" style="min-width: 140px">
|
|
<template #body="{ data }">
|
|
<span class="font-medium">{{ fmtDateTimeBR(data.inicio_em) }}</span>
|
|
</template>
|
|
</Column>
|
|
<Column header="Paciente" style="min-width: 160px">
|
|
<template #body="{ data }">{{ patientName(data) }}</template>
|
|
</Column>
|
|
<Column header="Sessão" style="min-width: 160px">
|
|
<template #body="{ data }">{{ sessionTitle(data) }}</template>
|
|
</Column>
|
|
<Column field="modalidade" header="Modalidade" style="min-width: 110px">
|
|
<template #body="{ data }">
|
|
{{ data.modalidade === 'online' ? 'Online' : data.modalidade === 'presencial' ? 'Presencial' : data.modalidade || '—' }}
|
|
</template>
|
|
</Column>
|
|
<Column field="status" header="Status" style="min-width: 110px">
|
|
<template #body="{ data }">
|
|
<Tag
|
|
:value="STATUS_LABEL[data.status] || data.status || 'Agendado'"
|
|
:severity="STATUS_SEVERITY[data.status] || 'info'"
|
|
/>
|
|
</template>
|
|
</Column>
|
|
</DataTable>
|
|
</div>
|
|
</div><!-- /.mr-charts-row -->
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.mr-page {
|
|
position: absolute;
|
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) 6px;
|
|
z-index: 40;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--m-bg-medium);
|
|
backdrop-filter: blur(32px) saturate(160%);
|
|
-webkit-backdrop-filter: blur(32px) saturate(160%);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 18px;
|
|
box-shadow: 0 16px 48px rgba(0, 0, 0, 0.4);
|
|
overflow: hidden;
|
|
font-family: 'Segoe UI', system-ui, -apple-system, sans-serif;
|
|
color: var(--m-text);
|
|
animation: mr-page-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
|
}
|
|
@keyframes mr-page-enter {
|
|
from { opacity: 0; transform: scale(0.985); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
|
|
.mr-page__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 14px 18px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
flex-shrink: 0;
|
|
gap: 10px;
|
|
}
|
|
.mr-page__title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
font-size: 1rem;
|
|
font-weight: 500;
|
|
}
|
|
.mr-page__title-icon { color: var(--p-primary-color); font-size: 1.05rem; }
|
|
.mr-page__title > span:not(.mr-page__count) {
|
|
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
|
|
}
|
|
.mr-page__count {
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
color: var(--m-accent);
|
|
background: var(--m-accent-soft);
|
|
border: 1px solid color-mix(in srgb, var(--m-accent) 35%, transparent);
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
text-transform: capitalize;
|
|
}
|
|
.mr-page__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
|
|
|
.mr-close, .mr-head-btn {
|
|
width: 32px; height: 32px;
|
|
display: grid; place-items: center;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
border-radius: 9px;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: background-color 140ms ease;
|
|
}
|
|
.mr-close:hover, .mr-head-btn:hover { background: var(--m-bg-soft-hover); }
|
|
.mr-head-btn > i { font-size: 0.85rem; }
|
|
.mr-head-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
|
|
.mr-act-btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
height: 32px;
|
|
padding: 0 12px;
|
|
border-radius: 9px;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
transition: background-color 140ms ease;
|
|
}
|
|
.mr-act-btn:hover { background: var(--m-bg-soft-hover); }
|
|
.mr-act-btn > i { font-size: 0.78rem; }
|
|
|
|
/* Subheader */
|
|
.mr-subheader {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 10px 18px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
background: var(--m-bg-soft);
|
|
font-size: 0.78rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.45;
|
|
flex-shrink: 0;
|
|
}
|
|
.mr-subheader__icon { color: var(--p-primary-color); font-size: 0.92rem; flex-shrink: 0; margin-top: 1px; }
|
|
.mr-subheader__text { flex: 1; min-width: 0; }
|
|
.mr-subheader__text strong { color: var(--m-text); font-weight: 600; }
|
|
|
|
/* Body */
|
|
.mr-body {
|
|
flex: 1;
|
|
display: flex;
|
|
min-height: 0;
|
|
gap: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
/* ─── Sidebar ─── */
|
|
.mr-side {
|
|
width: 280px;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--m-bg-soft);
|
|
border-right: 1px solid var(--m-border);
|
|
overflow: hidden;
|
|
}
|
|
.mr-side__scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-side__scroll::-webkit-scrollbar { width: 5px; }
|
|
.mr-side__scroll::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; }
|
|
|
|
.mr-side__footer {
|
|
flex-shrink: 0;
|
|
padding: 12px;
|
|
background: var(--m-bg-soft);
|
|
border-top: 1px solid var(--m-border);
|
|
}
|
|
.mr-side__clear-all {
|
|
width: 100%;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 9px 12px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
transition: background-color 140ms ease;
|
|
}
|
|
.mr-side__clear-all:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mr-side__clear-all > i { font-size: 0.78rem; color: var(--m-text-muted); }
|
|
|
|
.mr-side__clear-inline {
|
|
width: 18px; height: 18px;
|
|
display: grid; place-items: center;
|
|
background: transparent;
|
|
border: 1px solid color-mix(in srgb, rgb(220, 38, 38) 30%, var(--m-border));
|
|
color: rgb(220, 38, 38);
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
transition: background-color 140ms ease, border-color 140ms ease;
|
|
}
|
|
.mr-side__clear-inline:hover {
|
|
background: rgba(220, 38, 38, 0.10);
|
|
border-color: rgba(220, 38, 38, 0.55);
|
|
}
|
|
.mr-side__clear-inline > i { font-size: 0.6rem; }
|
|
|
|
.mr-clear-enter-active,
|
|
.mr-clear-leave-active {
|
|
transition: opacity 220ms ease, transform 220ms ease, max-height 240ms ease;
|
|
overflow: hidden;
|
|
}
|
|
.mr-clear-enter-from, .mr-clear-leave-to {
|
|
opacity: 0; transform: translateY(6px); max-height: 0;
|
|
}
|
|
.mr-clear-enter-to, .mr-clear-leave-from {
|
|
opacity: 1; transform: translateY(0); max-height: 80px;
|
|
}
|
|
|
|
.mr-w {
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 12px;
|
|
padding: 12px;
|
|
}
|
|
.mr-w--side {
|
|
margin: 12px 12px 0;
|
|
flex-shrink: 0;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
}
|
|
.mr-w--side:last-of-type { margin-bottom: 12px; }
|
|
.mr-w__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 10px;
|
|
}
|
|
.mr-w__title {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 0.7rem;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.12em;
|
|
color: var(--m-text-muted);
|
|
font-weight: 600;
|
|
}
|
|
.mr-w__title > i { color: var(--m-text-muted); font-size: 0.7rem; }
|
|
|
|
.mr-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 6px;
|
|
}
|
|
.mr-stat {
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 10px;
|
|
padding: 8px 10px;
|
|
}
|
|
.mr-stat__val {
|
|
font-size: 1.1rem;
|
|
font-weight: 700;
|
|
line-height: 1.1;
|
|
color: var(--m-text);
|
|
}
|
|
.mr-stat__lbl {
|
|
font-size: 0.62rem;
|
|
color: var(--m-text-muted);
|
|
margin-top: 4px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
.mr-stat.is-ok .mr-stat__val { color: rgb(22, 163, 74); }
|
|
.mr-stat.is-danger .mr-stat__val { color: rgb(220, 38, 38); }
|
|
.mr-stat.is-warn .mr-stat__val { color: rgb(217, 119, 6); }
|
|
.mr-stat.is-info .mr-stat__val { color: rgb(2, 132, 199); }
|
|
|
|
/* Filter buttons */
|
|
.mr-side__list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.mr-side__item {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
color: var(--m-text);
|
|
border-radius: 10px;
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.82rem;
|
|
text-align: left;
|
|
transition: background-color 140ms ease, border-color 140ms ease, box-shadow 140ms ease;
|
|
}
|
|
.mr-side__item > i {
|
|
font-size: 0.78rem;
|
|
width: 14px;
|
|
text-align: center;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mr-side__item:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mr-side__item.is-active.is-period {
|
|
background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
|
|
border-color: color-mix(in srgb, var(--p-primary-color) 50%, transparent);
|
|
box-shadow: 0 0 0 1px color-mix(in srgb, var(--p-primary-color) 30%, transparent);
|
|
}
|
|
.mr-side__item.is-active.is-period > i,
|
|
.mr-side__item.is-active.is-period > span { color: var(--p-primary-color); }
|
|
|
|
/* Status colors: realizado green / faltou red / cancelado warn / agendado info */
|
|
.mr-side__item.is-status-realizado {
|
|
background: rgba(22, 163, 74, 0.05);
|
|
border-color: rgba(22, 163, 74, 0.18);
|
|
}
|
|
.mr-side__item.is-status-realizado > i { color: rgb(22, 163, 74); }
|
|
.mr-side__item.is-status-realizado:hover {
|
|
background: rgba(22, 163, 74, 0.10);
|
|
border-color: rgba(22, 163, 74, 0.30);
|
|
}
|
|
.mr-side__item.is-active.is-status-realizado {
|
|
background: rgba(22, 163, 74, 0.16);
|
|
border-color: rgba(22, 163, 74, 0.55);
|
|
box-shadow: 0 0 0 1px rgba(22, 163, 74, 0.35);
|
|
}
|
|
|
|
.mr-side__item.is-status-faltou {
|
|
background: rgba(220, 38, 38, 0.05);
|
|
border-color: rgba(220, 38, 38, 0.18);
|
|
}
|
|
.mr-side__item.is-status-faltou > i { color: rgb(220, 38, 38); }
|
|
.mr-side__item.is-status-faltou:hover {
|
|
background: rgba(220, 38, 38, 0.10);
|
|
border-color: rgba(220, 38, 38, 0.30);
|
|
}
|
|
.mr-side__item.is-active.is-status-faltou {
|
|
background: rgba(220, 38, 38, 0.16);
|
|
border-color: rgba(220, 38, 38, 0.55);
|
|
box-shadow: 0 0 0 1px rgba(220, 38, 38, 0.35);
|
|
}
|
|
|
|
.mr-side__item.is-status-cancelado {
|
|
background: rgba(217, 119, 6, 0.05);
|
|
border-color: rgba(217, 119, 6, 0.18);
|
|
}
|
|
.mr-side__item.is-status-cancelado > i { color: rgb(217, 119, 6); }
|
|
.mr-side__item.is-status-cancelado:hover {
|
|
background: rgba(217, 119, 6, 0.10);
|
|
border-color: rgba(217, 119, 6, 0.30);
|
|
}
|
|
.mr-side__item.is-active.is-status-cancelado {
|
|
background: rgba(217, 119, 6, 0.16);
|
|
border-color: rgba(217, 119, 6, 0.55);
|
|
box-shadow: 0 0 0 1px rgba(217, 119, 6, 0.35);
|
|
}
|
|
|
|
.mr-side__item.is-status-agendado {
|
|
background: rgba(2, 132, 199, 0.05);
|
|
border-color: rgba(2, 132, 199, 0.18);
|
|
}
|
|
.mr-side__item.is-status-agendado > i { color: rgb(2, 132, 199); }
|
|
.mr-side__item.is-status-agendado:hover {
|
|
background: rgba(2, 132, 199, 0.10);
|
|
border-color: rgba(2, 132, 199, 0.30);
|
|
}
|
|
.mr-side__item.is-active.is-status-agendado {
|
|
background: rgba(2, 132, 199, 0.16);
|
|
border-color: rgba(2, 132, 199, 0.55);
|
|
box-shadow: 0 0 0 1px rgba(2, 132, 199, 0.35);
|
|
}
|
|
|
|
/* ─── Main ─── */
|
|
.mr-main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
padding: 12px;
|
|
gap: 12px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.mr-error {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 10px 14px;
|
|
background: rgba(220, 38, 38, 0.10);
|
|
border: 1px solid rgba(220, 38, 38, 0.30);
|
|
border-radius: 10px;
|
|
color: rgb(220, 38, 38);
|
|
font-size: 0.82rem;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
/* Row 50/50 — chart + tabela lado a lado em desktop */
|
|
.mr-charts-row {
|
|
display: flex;
|
|
flex-direction: row;
|
|
gap: 12px;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
.mr-charts-row > .mr-card {
|
|
flex: 1 1 50%;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* Card-base */
|
|
.mr-card {
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
}
|
|
.mr-card--chart {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-card--chart .mr-card__body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-card--chart .mr-chart-wrap,
|
|
.mr-card--chart .mr-chart-skel {
|
|
flex: 1;
|
|
height: auto;
|
|
min-height: 200px;
|
|
}
|
|
.mr-card--table {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-card__head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 12px 14px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
.mr-card__icon {
|
|
width: 36px; height: 36px;
|
|
display: grid; place-items: center;
|
|
border-radius: 9px;
|
|
background: color-mix(in srgb, var(--p-primary-color) 15%, transparent);
|
|
color: var(--p-primary-color);
|
|
font-size: 1rem;
|
|
flex-shrink: 0;
|
|
}
|
|
.mr-card__title { flex: 1; min-width: 0; }
|
|
.mr-card__title-text {
|
|
font-size: 0.92rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
}
|
|
.mr-card__sub {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
margin-top: 2px;
|
|
}
|
|
.mr-card__sub strong { color: var(--m-text); font-weight: 600; }
|
|
.mr-card__count {
|
|
font-size: 0.72rem;
|
|
font-weight: 700;
|
|
color: white;
|
|
background: var(--p-primary-color);
|
|
padding: 3px 10px;
|
|
border-radius: 999px;
|
|
min-width: 28px;
|
|
text-align: center;
|
|
}
|
|
.mr-card__body {
|
|
padding: 14px 16px;
|
|
}
|
|
|
|
/* Chart */
|
|
.mr-chart-wrap { height: 260px; }
|
|
.mr-chart-skel { height: 260px; }
|
|
|
|
.mr-empty-inline {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 8px;
|
|
padding: 32px 20px;
|
|
color: var(--m-text-muted);
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
/* Empty */
|
|
.mr-empty {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 56px 28px;
|
|
text-align: center;
|
|
color: var(--m-text-muted);
|
|
gap: 8px;
|
|
flex: 1;
|
|
}
|
|
.mr-empty__icon { font-size: 2rem; color: var(--m-text-faint); margin-bottom: 4px; }
|
|
.mr-empty__title { font-size: 0.92rem; font-weight: 600; color: var(--m-text); }
|
|
.mr-empty__hint { font-size: 0.78rem; }
|
|
.mr-empty__btn { margin-top: 8px; }
|
|
|
|
/* DataTable */
|
|
.mr-table {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-table :deep(.p-datatable) {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: transparent;
|
|
border: none;
|
|
border-radius: 0;
|
|
overflow: hidden;
|
|
}
|
|
.mr-table :deep(.p-datatable-table-container) { flex: 1; min-height: 0; background: transparent; }
|
|
.mr-table :deep(.p-datatable-thead),
|
|
.mr-table :deep(.p-datatable-thead > tr) { background: transparent !important; }
|
|
.mr-table :deep(.p-datatable-thead > tr > th) {
|
|
background: var(--m-bg-medium) !important;
|
|
color: var(--m-text);
|
|
font-size: 0.74rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
.mr-table :deep(.p-datatable-tbody > tr) {
|
|
background: transparent;
|
|
transition: background-color 140ms ease;
|
|
}
|
|
.mr-table :deep(.p-datatable-tbody > tr > td) {
|
|
padding: 10px 14px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
background: transparent;
|
|
font-size: 0.82rem;
|
|
}
|
|
.mr-table :deep(.p-datatable-tbody > tr:hover) { background: var(--m-bg-soft-hover); }
|
|
|
|
.mr-table :deep(.p-paginator) {
|
|
background: var(--m-bg-medium);
|
|
border: none;
|
|
border-top: 1px solid var(--m-border);
|
|
padding: 8px 12px;
|
|
justify-content: center;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* ─── Botão Menu mobile ─── */
|
|
.mr-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;
|
|
}
|
|
.mr-menu-btn:hover {
|
|
background: color-mix(in srgb, var(--m-accent) 88%, white);
|
|
transform: translateY(-1px);
|
|
}
|
|
.mr-menu-btn > i { font-size: 0.85rem; }
|
|
|
|
/* ─── Drawer mobile ─── */
|
|
.mr-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;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-mobile-drawer.is-open { transform: translateX(0); }
|
|
.mr-mobile-drawer__scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-mobile-drawer__scroll .mr-side {
|
|
flex: 1;
|
|
min-height: 0;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
background: transparent;
|
|
border-right: none;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mr-mobile-drawer__scroll .mr-side__scroll {
|
|
flex: 1;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 12px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--m-border-strong) transparent;
|
|
}
|
|
.mr-mobile-drawer__scroll .mr-side__scroll::-webkit-scrollbar { width: 5px; }
|
|
.mr-mobile-drawer__scroll .mr-side__scroll::-webkit-scrollbar-thumb {
|
|
background: var(--m-border-strong);
|
|
border-radius: 3px;
|
|
}
|
|
.mr-mobile-drawer__scroll .mr-w--side {
|
|
margin: 0;
|
|
flex-shrink: 0;
|
|
}
|
|
.mr-mobile-drawer__scroll .mr-w--side:last-of-type { margin-bottom: 0; }
|
|
.mr-mobile-drawer__scroll .mr-side__footer {
|
|
flex-shrink: 0;
|
|
margin: 0;
|
|
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%);
|
|
}
|
|
|
|
.mr-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;
|
|
}
|
|
.mr-drawer-fade-enter-active,
|
|
.mr-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
|
.mr-drawer-fade-enter-from,
|
|
.mr-drawer-fade-leave-to { opacity: 0; }
|
|
|
|
/* Mobile */
|
|
@media (max-width: 1023px) {
|
|
.mr-body { flex-direction: column; padding: 0; }
|
|
.mr-main { width: 100%; padding: 8px; }
|
|
.mr-page__title > span:first-of-type { display: none; }
|
|
.mr-page__title-icon { display: none; }
|
|
.mr-menu-btn--mobile-only { display: inline-flex; }
|
|
.mr-stats { grid-template-columns: repeat(3, 1fr); }
|
|
/* Em mobile empilha chart + tabela; tabela ganha min-height pra
|
|
nao colapsar pra ~50px em telas curtas. */
|
|
.mr-charts-row {
|
|
flex-direction: column;
|
|
flex: 1 0 auto;
|
|
}
|
|
.mr-card--chart .mr-chart-wrap,
|
|
.mr-card--chart .mr-chart-skel {
|
|
height: 240px;
|
|
min-height: 240px;
|
|
}
|
|
.mr-card--table {
|
|
min-height: 360px;
|
|
flex: 1 0 360px;
|
|
}
|
|
}
|
|
</style>
|