bad828cab3
No mobile o botao "Menu Lancamentos/Notificacoes/etc" ja indica a sessao,
entao o pi-list/pi-bell ao lado do contador era redundante. Adiciona
.<prefix>-page__title-icon { display: none; } no @media max-width: 1023px.
Em MelissaConversas usa > i:first-child (icone nao tem classe dedicada).
Pages: FinanceiroLancamentos, Compromissos, Documentos, CadastrosRecebidos,
Conversas, AgendamentosRecebidos, Financeiro, Grupos, Notificacoes, Tags,
Medicos, Relatorios, Recorrencias.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1926 lines
67 KiB
Vue
1926 lines
67 KiB
Vue
<script setup>
|
||
/*
|
||
* MelissaCompromissos — Tipos de compromissos determinados.
|
||
* Segue blueprint melissa-table-page-blueprint.md (DataTable + sidebar com
|
||
* stats e filtros coloridos, view toggle list/grade, subheader explicativo).
|
||
*
|
||
* Diferença vs. blueprint canônico (MelissaCadastrosRecebidos):
|
||
* - Row design preservado: o "Compromisso" é uma única coluna larga com
|
||
* color-stripe vertical + name + badges + descrição + meta inline,
|
||
* espelhando o card antigo. Coluna "Ações" frozen é alargada pra 140px
|
||
* pra caber os 3 controles (ToggleSwitch + Editar + Excluir).
|
||
* - Dois grupos de filtros na sidebar: Status (Ativos/Inativos) e
|
||
* Tipo (Nativos/Meus), cada um com botão "Limpar filtro" próprio.
|
||
* - Stats: Total / Ativos (verde) / Inativos (amber) / Tempo total.
|
||
*/
|
||
import { ref, computed, watch, onMounted, onBeforeUnmount } from 'vue';
|
||
import { useToast } from 'primevue/usetoast';
|
||
import { supabase } from '@/lib/supabase/client';
|
||
import { useTenantStore } from '@/stores/tenantStore';
|
||
import DeterminedCommitmentDialog from '@/features/agenda/components/DeterminedCommitmentDialog.vue';
|
||
// DataTable/Column/Paginator/ToggleSwitch/Dialog: auto-importados via PrimeVueResolver.
|
||
|
||
const emit = defineEmits(['close']);
|
||
|
||
const toast = useToast();
|
||
const tenantStore = useTenantStore();
|
||
|
||
// ── Breakpoints + drawer ──────────────────────────────────
|
||
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; }
|
||
|
||
// ── Estado ─────────────────────────────────────────────────
|
||
const loading = ref(false);
|
||
const saving = ref(false);
|
||
|
||
const commitments = ref([]);
|
||
const totalsByCommitmentId = ref({});
|
||
|
||
const busca = ref('');
|
||
const statusFilter = ref(''); // '' | 'active' | 'inactive'
|
||
const typeFilter = ref(''); // '' | 'native' | 'custom'
|
||
|
||
// ── Helpers de status ─────────────────────────────────────
|
||
function rowStatusClass(c) {
|
||
if (!c) return '';
|
||
return c.active ? 'is-ok' : 'is-warn';
|
||
}
|
||
|
||
// ── Stats ─────────────────────────────────────────────────
|
||
const stats = computed(() => {
|
||
const all = commitments.value;
|
||
const ativos = all.filter((c) => c.active).length;
|
||
const inativos = all.filter((c) => !c.active).length;
|
||
const totalMin = Object.values(totalsByCommitmentId.value).reduce((a, b) => a + b, 0);
|
||
return [
|
||
{ key: 'total', label: 'Total', value: all.length, cls: 'neutral' },
|
||
{ key: 'ativos', label: 'Ativos', value: ativos, cls: ativos > 0 ? 'ok' : 'neutral' },
|
||
{ key: 'inativos', label: 'Inativos', value: inativos, cls: inativos > 0 ? 'warn' : 'neutral' },
|
||
{ key: 'tempo', label: 'Tempo', value: formatMinutes(totalMin), cls: 'neutral' }
|
||
];
|
||
});
|
||
|
||
const STATUS_FILTER_OPTIONS = [
|
||
{ key: 'active', label: 'Ativos', icon: 'pi pi-check-circle' },
|
||
{ key: 'inactive', label: 'Inativos', icon: 'pi pi-pause-circle' }
|
||
];
|
||
const TYPE_FILTER_OPTIONS = [
|
||
{ key: 'native', label: 'Nativos', icon: 'pi pi-shield' },
|
||
{ key: 'custom', label: 'Meus', icon: 'pi pi-user' }
|
||
];
|
||
|
||
function toggleStatusFilter(s) {
|
||
statusFilter.value = statusFilter.value === s ? '' : s;
|
||
}
|
||
function toggleTypeFilter(t) {
|
||
typeFilter.value = typeFilter.value === t ? '' : t;
|
||
}
|
||
|
||
const hasActiveFilters = computed(() =>
|
||
!!(busca.value || statusFilter.value || typeFilter.value)
|
||
);
|
||
function clearAllFilters() {
|
||
busca.value = '';
|
||
statusFilter.value = '';
|
||
typeFilter.value = '';
|
||
}
|
||
|
||
// ── Filtragem ─────────────────────────────────────────────
|
||
const filtered = computed(() => {
|
||
let list = commitments.value;
|
||
if (statusFilter.value === 'active') list = list.filter((c) => !!c.active);
|
||
if (statusFilter.value === 'inactive') list = list.filter((c) => !c.active);
|
||
if (typeFilter.value === 'native') list = list.filter((c) => !!c.is_native);
|
||
if (typeFilter.value === 'custom') list = list.filter((c) => !c.is_native);
|
||
const q = String(busca.value || '').trim().toLowerCase();
|
||
if (q) {
|
||
list = list.filter((c) =>
|
||
String(c.name || '').toLowerCase().includes(q) ||
|
||
String(c.description || '').toLowerCase().includes(q)
|
||
);
|
||
}
|
||
return list;
|
||
});
|
||
|
||
// ── Paginação compartilhada (DataTable + grid) ────────────
|
||
const PAGE_SIZE_OPTIONS = [10, 20, 50, 100];
|
||
const rowsMC = ref(10);
|
||
const firstMC = ref(0);
|
||
function onPage(event) {
|
||
firstMC.value = event.first;
|
||
rowsMC.value = event.rows;
|
||
}
|
||
watch([busca, statusFilter, typeFilter], () => { firstMC.value = 0; });
|
||
|
||
const pagedItems = computed(() =>
|
||
filtered.value.slice(firstMC.value, firstMC.value + rowsMC.value)
|
||
);
|
||
|
||
// ── View mode (list / grid) ───────────────────────────────
|
||
const VIEW_MODE_KEY = 'mc.viewMode.v1';
|
||
const viewMode = ref('list');
|
||
try {
|
||
const saved = localStorage.getItem(VIEW_MODE_KEY);
|
||
if (saved === 'list' || saved === 'grid') viewMode.value = saved;
|
||
} catch (_) { /* localStorage indisponível — mantém default */ }
|
||
function setViewMode(m) {
|
||
if (m !== 'list' && m !== 'grid') return;
|
||
viewMode.value = m;
|
||
try { localStorage.setItem(VIEW_MODE_KEY, m); } catch (_) { /* noop */ }
|
||
}
|
||
|
||
// ── Helpers de tempo ──────────────────────────────────────
|
||
function formatMinutes(minutes) {
|
||
const m = Math.max(0, Number(minutes) || 0);
|
||
const h = Math.floor(m / 60);
|
||
const mm = m % 60;
|
||
if (h <= 0) return `${mm}m`;
|
||
return `${h}h${String(mm).padStart(2, '0')}`;
|
||
}
|
||
function getTotalMinutes(id) {
|
||
return Number(totalsByCommitmentId.value?.[id] ?? 0);
|
||
}
|
||
|
||
// ── Fetch ─────────────────────────────────────────────────
|
||
function getTenantId() {
|
||
return tenantStore.activeTenantId || tenantStore.tenantId || null;
|
||
}
|
||
|
||
async function fetchAll() {
|
||
const tenantId = getTenantId();
|
||
if (!tenantId) {
|
||
toast.add({ severity: 'warn', summary: 'Atenção', detail: 'Tenant inválido.', life: 3000 });
|
||
return;
|
||
}
|
||
loading.value = true;
|
||
try {
|
||
const { data: cData, error: cErr } = await supabase
|
||
.from('determined_commitments')
|
||
.select('id, tenant_id, is_native, native_key, is_locked, active, name, description, bg_color, text_color, created_at, updated_at')
|
||
.eq('tenant_id', tenantId)
|
||
.order('is_native', { ascending: false })
|
||
.order('created_at', { ascending: false });
|
||
if (cErr) throw cErr;
|
||
|
||
const ids = (cData || []).map((x) => x.id);
|
||
let fieldsByCommitmentId = {};
|
||
if (ids.length > 0) {
|
||
const { data: fData, error: fErr } = await supabase
|
||
.from('determined_commitment_fields')
|
||
.select('id, tenant_id, commitment_id, key, label, field_type, required, sort_order')
|
||
.eq('tenant_id', tenantId)
|
||
.in('commitment_id', ids)
|
||
.order('sort_order', { ascending: true });
|
||
if (fErr) throw fErr;
|
||
fieldsByCommitmentId = (fData || []).reduce((acc, row) => {
|
||
if (!acc[row.commitment_id]) acc[row.commitment_id] = [];
|
||
acc[row.commitment_id].push({
|
||
id: row.id, key: row.key, label: row.label,
|
||
type: row.field_type, required: !!row.required, sort_order: row.sort_order
|
||
});
|
||
return acc;
|
||
}, {});
|
||
}
|
||
|
||
const { data: lData, error: lErr } = await supabase
|
||
.from('commitment_time_logs').select('commitment_id, minutes').eq('tenant_id', tenantId);
|
||
if (lErr) throw lErr;
|
||
const totals = {};
|
||
for (const row of lData || []) {
|
||
const cid = row.commitment_id;
|
||
totals[cid] = (totals[cid] || 0) + (Number(row.minutes ?? 0) || 0);
|
||
}
|
||
totalsByCommitmentId.value = totals;
|
||
|
||
commitments.value = (cData || []).map((c) => ({
|
||
...c,
|
||
fields: fieldsByCommitmentId[c.id] || []
|
||
}));
|
||
} catch (e) {
|
||
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao carregar.', life: 4500 });
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
|
||
onMounted(async () => {
|
||
if (typeof window !== 'undefined' && window.matchMedia) {
|
||
_mqMobile = window.matchMedia('(max-width: 1023px)');
|
||
isMobile.value = _mqMobile.matches;
|
||
_mqMobile.addEventListener('change', _onMqMobileChange);
|
||
}
|
||
if (typeof tenantStore.loadSessionAndTenant === 'function') {
|
||
await tenantStore.loadSessionAndTenant();
|
||
}
|
||
await fetchAll();
|
||
});
|
||
onBeforeUnmount(() => {
|
||
if (_mqMobile) _mqMobile.removeEventListener('change', _onMqMobileChange);
|
||
});
|
||
|
||
// ── CRUD ──────────────────────────────────────────────────
|
||
const dlgOpen = ref(false);
|
||
const dlgMode = ref('create');
|
||
const editing = ref(null);
|
||
|
||
function openCreate() {
|
||
dlgMode.value = 'create';
|
||
editing.value = null;
|
||
dlgOpen.value = true;
|
||
}
|
||
function openEdit(c) {
|
||
dlgMode.value = 'edit';
|
||
editing.value = JSON.parse(JSON.stringify(c));
|
||
dlgOpen.value = true;
|
||
}
|
||
function onRowClick(event) {
|
||
if (event?.data) openEdit(event.data);
|
||
}
|
||
|
||
function isActiveLocked(c) { return !!c.is_locked; }
|
||
function isDeleteLocked(c) { return !!c.is_native; }
|
||
|
||
async function onToggleActive(c) {
|
||
if (isActiveLocked(c)) return;
|
||
const tenantId = getTenantId();
|
||
if (!tenantId) return;
|
||
saving.value = true;
|
||
try {
|
||
const { error } = await supabase.from('determined_commitments').update({ active: !!c.active }).eq('tenant_id', tenantId).eq('id', c.id);
|
||
if (error) throw error;
|
||
toast.add({ severity: 'success', summary: 'Atualizado', detail: `"${c.name}" ${c.active ? 'ativo' : 'inativo'}.`, life: 2200 });
|
||
} catch (e) {
|
||
c.active = !c.active;
|
||
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao atualizar.', life: 4500 });
|
||
} finally {
|
||
saving.value = false;
|
||
}
|
||
}
|
||
|
||
async function onSave(payload) {
|
||
const tenantId = getTenantId();
|
||
if (!tenantId) return;
|
||
saving.value = true;
|
||
try {
|
||
if (dlgMode.value === 'create') {
|
||
const { data: newC, error: cErr } = await supabase
|
||
.from('determined_commitments')
|
||
.insert({
|
||
tenant_id: tenantId,
|
||
is_native: false,
|
||
native_key: null,
|
||
is_locked: false,
|
||
active: !!payload.active,
|
||
name: payload.name,
|
||
description: payload.description,
|
||
bg_color: payload.bg_color || null,
|
||
text_color: payload.text_color || null
|
||
})
|
||
.select('id').single();
|
||
if (cErr) throw cErr;
|
||
|
||
const fields = Array.isArray(payload.fields) ? payload.fields : [];
|
||
if (fields.length > 0) {
|
||
const { error: fErr } = await supabase.from('determined_commitment_fields').insert(
|
||
fields.map((f, idx) => ({
|
||
tenant_id: tenantId,
|
||
commitment_id: newC.id,
|
||
key: f.key,
|
||
label: f.label,
|
||
field_type: f.type,
|
||
required: !!f.required,
|
||
sort_order: f.sort_order ?? idx
|
||
}))
|
||
);
|
||
if (fErr) throw fErr;
|
||
}
|
||
toast.add({ severity: 'success', summary: 'Criado', detail: 'Compromisso criado.', life: 2200 });
|
||
} else if (editing.value?.id) {
|
||
const cid = editing.value.id;
|
||
const { error: uErr } = await supabase.from('determined_commitments')
|
||
.update({
|
||
active: !!payload.active,
|
||
name: payload.name,
|
||
description: payload.description,
|
||
bg_color: payload.bg_color || null,
|
||
text_color: payload.text_color || null
|
||
})
|
||
.eq('tenant_id', tenantId).eq('id', cid);
|
||
if (uErr) throw uErr;
|
||
|
||
const { error: dErr } = await supabase.from('determined_commitment_fields')
|
||
.delete().eq('tenant_id', tenantId).eq('commitment_id', cid);
|
||
if (dErr) throw dErr;
|
||
const fields = Array.isArray(payload.fields) ? payload.fields : [];
|
||
if (fields.length > 0) {
|
||
const { error: fErr } = await supabase.from('determined_commitment_fields').insert(
|
||
fields.map((f, idx) => ({
|
||
tenant_id: tenantId,
|
||
commitment_id: cid,
|
||
key: f.key,
|
||
label: f.label,
|
||
field_type: f.type,
|
||
required: !!f.required,
|
||
sort_order: f.sort_order ?? idx
|
||
}))
|
||
);
|
||
if (fErr) throw fErr;
|
||
}
|
||
toast.add({ severity: 'success', summary: 'Atualizado', detail: 'Compromisso atualizado.', life: 2200 });
|
||
}
|
||
dlgOpen.value = false;
|
||
await fetchAll();
|
||
} catch (e) {
|
||
const msg = e?.message || '';
|
||
const detail = e?.code === '23505' || /duplicate key value/i.test(msg)
|
||
? 'Já existe um compromisso com esse nome. Escolha outro.'
|
||
: msg || 'Falha ao salvar.';
|
||
toast.add({ severity: 'error', summary: 'Erro', detail, life: 4500 });
|
||
} finally {
|
||
saving.value = false;
|
||
}
|
||
}
|
||
|
||
function confirmDelete(c) {
|
||
if (isDeleteLocked(c)) return;
|
||
if (!window.confirm(`Excluir "${c.name}"? Essa ação não pode ser desfeita.`)) return;
|
||
onDelete(c);
|
||
}
|
||
|
||
async function onDelete(c) {
|
||
if (isDeleteLocked(c)) return;
|
||
const tenantId = getTenantId();
|
||
if (!tenantId) return;
|
||
saving.value = true;
|
||
try {
|
||
const { error: fErr } = await supabase.from('determined_commitment_fields')
|
||
.delete().eq('tenant_id', tenantId).eq('commitment_id', c.id);
|
||
if (fErr) throw fErr;
|
||
const { error: lErr } = await supabase.from('commitment_time_logs')
|
||
.delete().eq('tenant_id', tenantId).eq('commitment_id', c.id);
|
||
if (lErr) throw lErr;
|
||
const { data: delRows, error: dErr } = await supabase.from('determined_commitments')
|
||
.delete().eq('tenant_id', tenantId).eq('id', c.id).eq('is_native', false).select('id');
|
||
if (dErr) throw dErr;
|
||
if (!delRows?.length) throw new Error('DELETE bloqueado por RLS.');
|
||
toast.add({ severity: 'success', summary: 'Excluído', detail: 'Compromisso removido.', life: 2200 });
|
||
dlgOpen.value = false;
|
||
await fetchAll();
|
||
} catch (e) {
|
||
toast.add({ severity: 'error', summary: 'Erro', detail: e?.message || 'Falha ao excluir.', life: 4500 });
|
||
} finally {
|
||
saving.value = false;
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<template>
|
||
<aside
|
||
class="mc-mobile-drawer"
|
||
:class="{ 'is-open': drawerOpen }"
|
||
v-show="isMobile"
|
||
aria-label="Estatísticas e filtros"
|
||
>
|
||
<div id="mc-mobile-drawer-target" class="mc-mobile-drawer__scroll" />
|
||
</aside>
|
||
<Transition name="mc-drawer-fade">
|
||
<div
|
||
v-if="isMobile && drawerOpen"
|
||
class="mc-mobile-drawer__backdrop"
|
||
@click="fecharDrawer"
|
||
/>
|
||
</Transition>
|
||
|
||
<section class="mc-page">
|
||
<header class="mc-page__head">
|
||
<button
|
||
class="mc-menu-btn mc-menu-btn--mobile-only"
|
||
v-tooltip.bottom="'Estatísticas & filtros'"
|
||
@click="toggleDrawer"
|
||
>
|
||
<i class="pi pi-bars" />
|
||
<span>Menu Compromissos</span>
|
||
</button>
|
||
<div class="mc-page__title">
|
||
<i class="pi pi-list mc-page__title-icon" />
|
||
<span>Compromissos</span>
|
||
<span class="mc-page__count">{{ filtered.length }}</span>
|
||
</div>
|
||
<div class="mc-page__actions">
|
||
<button
|
||
class="mc-act-btn mc-act-btn--primary"
|
||
v-tooltip.bottom="'Novo compromisso'"
|
||
:disabled="loading"
|
||
@click="openCreate()"
|
||
>
|
||
<i :class="saving ? 'pi pi-spin pi-spinner' : 'pi pi-plus'" />
|
||
<span>Novo</span>
|
||
</button>
|
||
<button
|
||
class="mc-head-btn"
|
||
v-tooltip.bottom="'Recarregar'"
|
||
:disabled="loading"
|
||
@click="fetchAll"
|
||
>
|
||
<i :class="loading ? 'pi pi-spin pi-spinner' : 'pi pi-refresh'" />
|
||
</button>
|
||
<button class="mc-close" v-tooltip.bottom="'Voltar (Esc)'" @click="emit('close')">
|
||
<i class="pi pi-times" />
|
||
</button>
|
||
</div>
|
||
</header>
|
||
|
||
<!-- Subheader explicativo (blueprint §9) -->
|
||
<div class="mc-subheader">
|
||
<i class="pi pi-info-circle mc-subheader__icon" />
|
||
<span class="mc-subheader__text">
|
||
Modelos de compromissos que aparecem na agenda além das sessões.
|
||
<strong>Crie tipos próprios</strong> com campos personalizados ou
|
||
<strong>edite/desative</strong> os nativos disponíveis pra todos os usuários.
|
||
</span>
|
||
</div>
|
||
|
||
<div class="mc-body">
|
||
<Teleport to="#mc-mobile-drawer-target" :disabled="!isMobile">
|
||
<aside class="mc-side">
|
||
<!-- Conteúdo scrollável: cards de estatísticas e filtros.
|
||
O footer com "Limpar filtros" fica fora desse scroll
|
||
(flex-shrink: 0 ancorado no bottom da .mc-side). -->
|
||
<div class="mc-side__scroll">
|
||
<!-- Stats -->
|
||
<div class="mc-w mc-w--side">
|
||
<div class="mc-w__head">
|
||
<span class="mc-w__title"><i class="pi pi-chart-bar" /> Estatísticas</span>
|
||
</div>
|
||
<div class="mc-stats">
|
||
<div
|
||
v-for="s in stats"
|
||
:key="s.key"
|
||
class="mc-stat"
|
||
:class="`is-${s.cls}`"
|
||
>
|
||
<div class="mc-stat__val">{{ s.value }}</div>
|
||
<div class="mc-stat__lbl">{{ s.label }}</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filtro de status -->
|
||
<div class="mc-w mc-w--side">
|
||
<div class="mc-w__head">
|
||
<span class="mc-w__title"><i class="pi pi-filter" /> Status</span>
|
||
<button
|
||
v-if="statusFilter"
|
||
class="mc-side__clear-inline"
|
||
v-tooltip.top="'Limpar filtro de status'"
|
||
aria-label="Limpar filtro de status"
|
||
@click="statusFilter = ''"
|
||
>
|
||
<i class="pi pi-times" />
|
||
</button>
|
||
</div>
|
||
<div class="mc-side__list">
|
||
<button
|
||
v-for="o in STATUS_FILTER_OPTIONS"
|
||
:key="o.key"
|
||
class="mc-side__item"
|
||
:class="[`is-status-${o.key}`, { 'is-active': statusFilter === o.key }]"
|
||
@click="toggleStatusFilter(o.key)"
|
||
>
|
||
<i :class="o.icon" />
|
||
<span>{{ o.label }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Filtro de tipo -->
|
||
<div class="mc-w mc-w--side">
|
||
<div class="mc-w__head">
|
||
<span class="mc-w__title"><i class="pi pi-tag" /> Tipo</span>
|
||
<button
|
||
v-if="typeFilter"
|
||
class="mc-side__clear-inline"
|
||
v-tooltip.top="'Limpar filtro de tipo'"
|
||
aria-label="Limpar filtro de tipo"
|
||
@click="typeFilter = ''"
|
||
>
|
||
<i class="pi pi-times" />
|
||
</button>
|
||
</div>
|
||
<div class="mc-side__list">
|
||
<button
|
||
v-for="o in TYPE_FILTER_OPTIONS"
|
||
:key="o.key"
|
||
class="mc-side__item"
|
||
:class="[`is-type-${o.key}`, { 'is-active': typeFilter === o.key }]"
|
||
@click="toggleTypeFilter(o.key)"
|
||
>
|
||
<i :class="o.icon" />
|
||
<span>{{ o.label }}</span>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Footer fixo no bottom da sidebar (fora do scroll).
|
||
Botão "Limpar filtros" só aparece se algum filtro
|
||
estiver ativo (busca, status, ou tipo). Em desktop
|
||
fica ancorado no fundo da .mc-side via flex; em
|
||
mobile teleportado fica sticky no bottom do drawer. -->
|
||
<Transition name="mc-clear">
|
||
<div v-if="hasActiveFilters" class="mc-side__footer">
|
||
<button class="mc-side__clear-all" @click="clearAllFilters">
|
||
<i class="pi pi-filter-slash" />
|
||
<span>Limpar filtros</span>
|
||
</button>
|
||
</div>
|
||
</Transition>
|
||
</aside>
|
||
</Teleport>
|
||
|
||
<div class="mc-main">
|
||
<!-- Toolbar (busca + view toggle) -->
|
||
<div class="mc-toolbar">
|
||
<div class="mc-search">
|
||
<i class="pi pi-search mc-search__icon" />
|
||
<input
|
||
v-model="busca"
|
||
type="text"
|
||
placeholder="Buscar por nome ou descrição…"
|
||
class="mc-search__input"
|
||
/>
|
||
<button
|
||
v-if="busca"
|
||
class="mc-search__clear"
|
||
v-tooltip.bottom="'Limpar busca'"
|
||
@click="busca = ''"
|
||
>
|
||
<i class="pi pi-times" />
|
||
</button>
|
||
</div>
|
||
<div class="mc-view-toggle" role="group" aria-label="Visualização">
|
||
<button
|
||
class="mc-view-toggle__btn"
|
||
:class="{ 'is-active': viewMode === 'list' }"
|
||
v-tooltip.bottom="'Lista'"
|
||
aria-label="Lista"
|
||
@click="setViewMode('list')"
|
||
>
|
||
<i class="pi pi-list" />
|
||
</button>
|
||
<button
|
||
class="mc-view-toggle__btn"
|
||
:class="{ 'is-active': viewMode === 'grid' }"
|
||
v-tooltip.bottom="'Grade'"
|
||
aria-label="Grade"
|
||
@click="setViewMode('grid')"
|
||
>
|
||
<i class="pi pi-th-large" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- View Lista (DataTable) — row design preservado:
|
||
coluna "Compromisso" larga renderiza color-stripe + name +
|
||
badges + descrição + meta inline (mesmo conteúdo do card
|
||
antigo); coluna "Ações" frozen 140px com 3 controles. -->
|
||
<DataTable
|
||
v-if="viewMode === 'list'"
|
||
:value="filtered"
|
||
:loading="loading"
|
||
dataKey="id"
|
||
paginator
|
||
:rows="rowsMC"
|
||
:first="firstMC"
|
||
:rowsPerPageOptions="PAGE_SIZE_OPTIONS"
|
||
paginatorTemplate="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
|
||
currentPageReportTemplate="{first}–{last} de {totalRecords}"
|
||
:rowClass="rowStatusClass"
|
||
selectionMode="single"
|
||
scrollable
|
||
scrollHeight="flex"
|
||
tableStyle="min-width: 720px"
|
||
class="mc-table"
|
||
@row-click="onRowClick"
|
||
@page="onPage"
|
||
>
|
||
<Column header="Compromisso" style="min-width: 320px">
|
||
<template #body="{ data }">
|
||
<div class="mc-row__commit" :class="{ 'is-inactive': !data.active }">
|
||
<span
|
||
class="mc-row__color"
|
||
:style="{ background: data.bg_color ? `#${data.bg_color}` : 'var(--m-bg-soft-hover)' }"
|
||
/>
|
||
<div class="mc-row__main">
|
||
<div class="mc-row__name-row">
|
||
<span class="mc-row__name">{{ data.name }}</span>
|
||
<span v-if="data.is_native" class="mc-row__badge mc-row__badge--native">
|
||
<i class="pi pi-shield" /> Nativo
|
||
</span>
|
||
<span v-if="!data.active" class="mc-row__badge mc-row__badge--off">Inativo</span>
|
||
</div>
|
||
<div v-if="data.description" class="mc-row__desc">{{ data.description }}</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
</Column>
|
||
|
||
<Column header="Atividade" style="width: 220px; min-width: 200px">
|
||
<template #body="{ data }">
|
||
<div class="mc-row__meta">
|
||
<span class="mc-row__meta-item" v-tooltip.top="'Tempo total registrado'">
|
||
<i class="pi pi-clock" />
|
||
{{ formatMinutes(getTotalMinutes(data.id)) }}
|
||
</span>
|
||
<span class="mc-row__meta-item" v-tooltip.top="'Campos extras'">
|
||
<i class="pi pi-list" />
|
||
{{ (data.fields || []).length }}
|
||
{{ (data.fields || []).length === 1 ? 'campo' : 'campos' }}
|
||
</span>
|
||
</div>
|
||
</template>
|
||
</Column>
|
||
|
||
<Column
|
||
header=""
|
||
:style="{ width: '140px', maxWidth: '140px', minWidth: '140px' }"
|
||
frozen
|
||
alignFrozen="right"
|
||
class="mc-col-acoes"
|
||
>
|
||
<template #body="{ data }">
|
||
<div class="mc-row__actions" @click.stop>
|
||
<ToggleSwitch
|
||
v-model="data.active"
|
||
:disabled="isActiveLocked(data) || saving"
|
||
@change="onToggleActive(data)"
|
||
v-tooltip.left="isActiveLocked(data) ? 'Bloqueado' : (data.active ? 'Desativar' : 'Ativar')"
|
||
/>
|
||
<button
|
||
class="mc-row__btn"
|
||
v-tooltip.left="'Editar'"
|
||
aria-label="Editar"
|
||
@click.stop="openEdit(data)"
|
||
>
|
||
<i class="pi pi-pencil" />
|
||
</button>
|
||
<button
|
||
class="mc-row__btn mc-row__btn--danger"
|
||
v-tooltip.left="isDeleteLocked(data) ? 'Não pode excluir nativos' : 'Excluir'"
|
||
aria-label="Excluir"
|
||
:disabled="isDeleteLocked(data)"
|
||
@click.stop="confirmDelete(data)"
|
||
>
|
||
<i class="pi pi-trash" />
|
||
</button>
|
||
</div>
|
||
</template>
|
||
</Column>
|
||
|
||
<template #empty>
|
||
<div class="mc-empty">
|
||
<i class="pi pi-list mc-empty__icon" />
|
||
<div class="mc-empty__title">Nenhum compromisso encontrado</div>
|
||
<div class="mc-empty__hint">
|
||
<template v-if="busca || statusFilter || typeFilter">
|
||
Ajuste os filtros pra ver mais resultados.
|
||
</template>
|
||
<template v-else>
|
||
Crie seu primeiro tipo de compromisso.
|
||
</template>
|
||
</div>
|
||
<button class="mc-act-btn mc-act-btn--primary mc-empty__btn" @click="openCreate()">
|
||
<i class="pi pi-plus" />
|
||
<span>Novo compromisso</span>
|
||
</button>
|
||
</div>
|
||
</template>
|
||
|
||
<template #loading>
|
||
<div class="mc-table__loading">
|
||
<i class="pi pi-spin pi-spinner" />
|
||
<span>Carregando compromissos…</span>
|
||
</div>
|
||
</template>
|
||
</DataTable>
|
||
|
||
<!-- View Grade — cards num CSS grid; mesmo conteúdo do row
|
||
preservado em formato vertical. Paginator standalone
|
||
compartilha rowsMC/firstMC com a list view. -->
|
||
<div v-else-if="viewMode === 'grid'" class="mc-grid-wrap">
|
||
<div v-if="loading && filtered.length === 0" class="mc-table__loading mc-grid__loading">
|
||
<i class="pi pi-spin pi-spinner" />
|
||
<span>Carregando compromissos…</span>
|
||
</div>
|
||
<div v-else-if="filtered.length === 0" class="mc-empty">
|
||
<i class="pi pi-list mc-empty__icon" />
|
||
<div class="mc-empty__title">Nenhum compromisso encontrado</div>
|
||
<div class="mc-empty__hint">
|
||
<template v-if="busca || statusFilter || typeFilter">
|
||
Ajuste os filtros pra ver mais resultados.
|
||
</template>
|
||
<template v-else>
|
||
Crie seu primeiro tipo de compromisso.
|
||
</template>
|
||
</div>
|
||
</div>
|
||
<div v-else class="mc-grid">
|
||
<div
|
||
v-for="c in pagedItems"
|
||
:key="c.id"
|
||
class="mc-grid__card"
|
||
:class="[rowStatusClass(c), { 'is-inactive': !c.active }]"
|
||
role="button"
|
||
tabindex="0"
|
||
@click="openEdit(c)"
|
||
@keydown.enter.prevent="openEdit(c)"
|
||
@keydown.space.prevent="openEdit(c)"
|
||
>
|
||
<div class="mc-grid__top">
|
||
<span
|
||
class="mc-grid__color"
|
||
:style="{ background: c.bg_color ? `#${c.bg_color}` : 'var(--m-bg-soft-hover)' }"
|
||
/>
|
||
<div class="mc-grid__top-right" @click.stop>
|
||
<ToggleSwitch
|
||
v-model="c.active"
|
||
:disabled="isActiveLocked(c) || saving"
|
||
@change="onToggleActive(c)"
|
||
v-tooltip.left="isActiveLocked(c) ? 'Bloqueado' : (c.active ? 'Desativar' : 'Ativar')"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<div class="mc-grid__name-row">
|
||
<span class="mc-grid__name">{{ c.name }}</span>
|
||
<span v-if="c.is_native" class="mc-row__badge mc-row__badge--native">
|
||
<i class="pi pi-shield" /> Nativo
|
||
</span>
|
||
<span v-if="!c.active" class="mc-row__badge mc-row__badge--off">Inativo</span>
|
||
</div>
|
||
<div v-if="c.description" class="mc-grid__desc">{{ c.description }}</div>
|
||
<div class="mc-grid__meta">
|
||
<span class="mc-row__meta-item" v-tooltip.top="'Tempo total registrado'">
|
||
<i class="pi pi-clock" />
|
||
{{ formatMinutes(getTotalMinutes(c.id)) }}
|
||
</span>
|
||
<span class="mc-row__meta-item" v-tooltip.top="'Campos extras'">
|
||
<i class="pi pi-list" />
|
||
{{ (c.fields || []).length }}
|
||
{{ (c.fields || []).length === 1 ? 'campo' : 'campos' }}
|
||
</span>
|
||
</div>
|
||
<div class="mc-grid__footer" @click.stop>
|
||
<button
|
||
class="mc-row__btn"
|
||
v-tooltip.top="'Editar'"
|
||
aria-label="Editar"
|
||
@click.stop="openEdit(c)"
|
||
>
|
||
<i class="pi pi-pencil" />
|
||
</button>
|
||
<button
|
||
class="mc-row__btn mc-row__btn--danger"
|
||
v-tooltip.top="isDeleteLocked(c) ? 'Não pode excluir nativos' : 'Excluir'"
|
||
aria-label="Excluir"
|
||
:disabled="isDeleteLocked(c)"
|
||
@click.stop="confirmDelete(c)"
|
||
>
|
||
<i class="pi pi-trash" />
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<Paginator
|
||
v-if="filtered.length > 0"
|
||
class="mc-paginator"
|
||
:rows="rowsMC"
|
||
:totalRecords="filtered.length"
|
||
:first="firstMC"
|
||
:rowsPerPageOptions="PAGE_SIZE_OPTIONS"
|
||
template="RowsPerPageDropdown FirstPageLink PrevPageLink CurrentPageReport NextPageLink LastPageLink"
|
||
currentPageReportTemplate="{first}–{last} de {totalRecords}"
|
||
@page="onPage"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Dialog de criar/editar — reutiliza o componente existente -->
|
||
<DeterminedCommitmentDialog
|
||
v-model="dlgOpen"
|
||
:mode="dlgMode"
|
||
:commitment="editing"
|
||
:saving="saving"
|
||
@save="onSave"
|
||
@delete="onDelete"
|
||
/>
|
||
</section>
|
||
</template>
|
||
|
||
<style scoped>
|
||
/* ─── Page chrome (espelha MelissaCadastrosRecebidos) ─── */
|
||
.mc-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: mc-page-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
||
}
|
||
@keyframes mc-page-enter {
|
||
from { opacity: 0; transform: scale(0.985); }
|
||
to { opacity: 1; transform: scale(1); }
|
||
}
|
||
|
||
.mc-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;
|
||
}
|
||
.mc-page__title {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
font-size: 1rem;
|
||
font-weight: 500;
|
||
}
|
||
.mc-page__title-icon {
|
||
color: var(--p-primary-color);
|
||
font-size: 1.05rem;
|
||
}
|
||
.mc-page__title > span:not(.mc-page__count) {
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.mc-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;
|
||
}
|
||
.mc-page__actions { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
|
||
|
||
.mc-close, .mc-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;
|
||
}
|
||
.mc-close:hover, .mc-head-btn:hover { background: var(--m-bg-soft-hover); }
|
||
.mc-head-btn > i { font-size: 0.85rem; }
|
||
|
||
.mc-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;
|
||
transition: background-color 140ms ease, transform 140ms ease;
|
||
background: var(--m-bg-soft);
|
||
border: 1px solid var(--m-border);
|
||
color: var(--m-text);
|
||
}
|
||
.mc-act-btn--primary {
|
||
background: var(--m-accent);
|
||
border-color: var(--m-accent);
|
||
color: white;
|
||
}
|
||
.mc-act-btn--primary:hover {
|
||
background: color-mix(in srgb, var(--m-accent) 88%, white);
|
||
transform: translateY(-1px);
|
||
}
|
||
.mc-act-btn:disabled { opacity: 0.5; cursor: not-allowed; transform: none; }
|
||
.mc-act-btn > i { font-size: 0.78rem; }
|
||
|
||
.mc-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;
|
||
}
|
||
.mc-menu-btn:hover {
|
||
background: color-mix(in srgb, var(--m-accent) 88%, white);
|
||
transform: translateY(-1px);
|
||
}
|
||
|
||
/* Subheader (blueprint §9) */
|
||
.mc-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;
|
||
}
|
||
.mc-subheader__icon {
|
||
color: var(--p-primary-color);
|
||
font-size: 0.92rem;
|
||
flex-shrink: 0;
|
||
margin-top: 1px;
|
||
}
|
||
.mc-subheader__text { flex: 1; min-width: 0; }
|
||
.mc-subheader__text strong { color: var(--m-text); font-weight: 600; }
|
||
|
||
.mc-body {
|
||
flex: 1;
|
||
display: flex;
|
||
min-height: 0;
|
||
gap: 0;
|
||
padding: 0;
|
||
}
|
||
|
||
/* ─── Sidebar ─── */
|
||
/* Layout flex column com 2 zonas: __scroll (cards, scrollável) e
|
||
__footer (Limpar filtros, fixo no bottom). overflow: hidden no
|
||
container externo evita que o scroll vaze pra fora. */
|
||
.mc-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;
|
||
}
|
||
.mc-side__scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.mc-side__scroll::-webkit-scrollbar { width: 5px; }
|
||
.mc-side__scroll::-webkit-scrollbar-thumb {
|
||
background: var(--m-border-strong);
|
||
border-radius: 3px;
|
||
}
|
||
|
||
/* Footer da sidebar — sempre ancorado no bottom (fora do scroll).
|
||
Bg um pouco mais opaco que --m-bg-soft pra "ler" como faixa fixa
|
||
separada. Border-top marca o limite com a área scrollável acima. */
|
||
.mc-side__footer {
|
||
flex-shrink: 0;
|
||
padding: 12px;
|
||
background: var(--m-bg-soft);
|
||
border-top: 1px solid var(--m-border);
|
||
}
|
||
.mc-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, border-color 140ms ease, color 140ms ease;
|
||
}
|
||
.mc-side__clear-all:hover {
|
||
background: var(--m-bg-soft-hover);
|
||
border-color: var(--m-border-strong);
|
||
color: var(--m-text);
|
||
}
|
||
.mc-side__clear-all > i {
|
||
font-size: 0.78rem;
|
||
color: var(--m-text-muted);
|
||
transition: color 140ms ease;
|
||
}
|
||
.mc-side__clear-all:hover > i { color: var(--m-text); }
|
||
|
||
.mc-w {
|
||
background: var(--m-bg-medium);
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 12px;
|
||
padding: 12px;
|
||
}
|
||
.mc-w--side {
|
||
margin: 12px 12px 0;
|
||
flex-shrink: 0;
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
||
}
|
||
.mc-w--side:last-of-type { margin-bottom: 12px; }
|
||
|
||
.mc-w__head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 10px;
|
||
}
|
||
.mc-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;
|
||
}
|
||
.mc-w__title > i { color: var(--m-text-muted); font-size: 0.7rem; }
|
||
.mc-w__count {
|
||
font-size: 0.65rem;
|
||
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: 1px 7px;
|
||
border-radius: 999px;
|
||
}
|
||
|
||
/* Botão X inline ao lado do título do filter card — limpa o filtro
|
||
individual (espelha .mp-side__clear do MelissaPacientes). Cor vermelha
|
||
pra indicar ação destrutiva (remover filtro ativo). Aparece apenas
|
||
quando o filtro correspondente está ativo (v-if). */
|
||
.mc-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;
|
||
font-family: inherit;
|
||
transition: background-color 140ms ease, border-color 140ms ease;
|
||
}
|
||
.mc-side__clear-inline:hover {
|
||
background: rgba(220, 38, 38, 0.10);
|
||
border-color: rgba(220, 38, 38, 0.55);
|
||
}
|
||
.mc-side__clear-inline > i { font-size: 0.6rem; }
|
||
|
||
.mc-stats {
|
||
display: grid;
|
||
grid-template-columns: repeat(2, 1fr);
|
||
gap: 6px;
|
||
}
|
||
.mc-stat {
|
||
background: var(--m-bg-soft);
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 10px;
|
||
padding: 8px 10px;
|
||
}
|
||
.mc-stat__val {
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
line-height: 1.1;
|
||
}
|
||
.mc-stat__lbl {
|
||
font-size: 0.65rem;
|
||
color: var(--m-text-muted);
|
||
margin-top: 4px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.06em;
|
||
}
|
||
.mc-stat.is-ok .mc-stat__val { color: rgb(22, 163, 74); } /* green-600 */
|
||
.mc-stat.is-warn .mc-stat__val { color: rgb(217, 119, 6); } /* amber-600 */
|
||
|
||
/* ─── Filtros (blueprint §8) ─── */
|
||
.mc-side__list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
}
|
||
.mc-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;
|
||
}
|
||
.mc-side__item > i {
|
||
font-size: 0.78rem;
|
||
width: 14px;
|
||
text-align: center;
|
||
}
|
||
|
||
/* Status: Ativos = green-600, Inativos = amber-600 */
|
||
.mc-side__item.is-status-active {
|
||
background: rgba(22, 163, 74, 0.05);
|
||
border-color: rgba(22, 163, 74, 0.18);
|
||
}
|
||
.mc-side__item.is-status-active > i { color: rgb(22, 163, 74); }
|
||
.mc-side__item.is-status-active:hover {
|
||
background: rgba(22, 163, 74, 0.10);
|
||
border-color: rgba(22, 163, 74, 0.30);
|
||
}
|
||
.mc-side__item.is-active.is-status-active {
|
||
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);
|
||
}
|
||
|
||
.mc-side__item.is-status-inactive {
|
||
background: rgba(217, 119, 6, 0.05);
|
||
border-color: rgba(217, 119, 6, 0.18);
|
||
}
|
||
.mc-side__item.is-status-inactive > i { color: rgb(217, 119, 6); }
|
||
.mc-side__item.is-status-inactive:hover {
|
||
background: rgba(217, 119, 6, 0.10);
|
||
border-color: rgba(217, 119, 6, 0.30);
|
||
}
|
||
.mc-side__item.is-active.is-status-inactive {
|
||
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);
|
||
}
|
||
|
||
/* Tipo: Nativos = blue-600 (info), Meus = primary-tinted */
|
||
.mc-side__item.is-type-native {
|
||
background: rgba(37, 99, 235, 0.05);
|
||
border-color: rgba(37, 99, 235, 0.18);
|
||
}
|
||
.mc-side__item.is-type-native > i { color: rgb(37, 99, 235); }
|
||
.mc-side__item.is-type-native:hover {
|
||
background: rgba(37, 99, 235, 0.10);
|
||
border-color: rgba(37, 99, 235, 0.30);
|
||
}
|
||
.mc-side__item.is-active.is-type-native {
|
||
background: rgba(37, 99, 235, 0.16);
|
||
border-color: rgba(37, 99, 235, 0.55);
|
||
box-shadow: 0 0 0 1px rgba(37, 99, 235, 0.35);
|
||
}
|
||
|
||
.mc-side__item.is-type-custom {
|
||
background: color-mix(in srgb, var(--m-accent) 5%, transparent);
|
||
border-color: color-mix(in srgb, var(--m-accent) 18%, transparent);
|
||
}
|
||
.mc-side__item.is-type-custom > i { color: var(--m-accent); }
|
||
.mc-side__item.is-type-custom:hover {
|
||
background: color-mix(in srgb, var(--m-accent) 10%, transparent);
|
||
border-color: color-mix(in srgb, var(--m-accent) 30%, transparent);
|
||
}
|
||
.mc-side__item.is-active.is-type-custom {
|
||
background: color-mix(in srgb, var(--m-accent) 16%, transparent);
|
||
border-color: color-mix(in srgb, var(--m-accent) 55%, transparent);
|
||
box-shadow: 0 0 0 1px color-mix(in srgb, var(--m-accent) 35%, transparent);
|
||
}
|
||
|
||
/* Transition do footer "Limpar filtros" — fade + collapse vertical
|
||
pra entrada/saída suave quando o user ativa/desativa o último filtro. */
|
||
.mc-clear-enter-active,
|
||
.mc-clear-leave-active {
|
||
transition: opacity 220ms ease, transform 220ms ease, max-height 240ms ease;
|
||
overflow: hidden;
|
||
}
|
||
.mc-clear-enter-from,
|
||
.mc-clear-leave-to {
|
||
opacity: 0;
|
||
transform: translateY(6px);
|
||
max-height: 0;
|
||
}
|
||
.mc-clear-enter-to,
|
||
.mc-clear-leave-from {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
max-height: 80px;
|
||
}
|
||
|
||
/* ─── Main column ─── */
|
||
.mc-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 12px;
|
||
gap: 10px;
|
||
}
|
||
|
||
.mc-toolbar {
|
||
flex-shrink: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 10px;
|
||
}
|
||
.mc-search {
|
||
position: relative;
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
.mc-search__icon {
|
||
position: absolute;
|
||
left: 12px;
|
||
color: var(--m-text-muted);
|
||
font-size: 0.78rem;
|
||
pointer-events: none;
|
||
}
|
||
.mc-search__input {
|
||
width: 100%;
|
||
background: var(--m-bg-medium);
|
||
border: 1px solid var(--m-border);
|
||
color: var(--m-text);
|
||
padding: 9px 36px 9px 34px;
|
||
border-radius: 10px;
|
||
font-size: 0.85rem;
|
||
font-family: inherit;
|
||
outline: none;
|
||
transition: background-color 140ms ease, border-color 140ms ease;
|
||
}
|
||
.mc-search__input::placeholder { color: var(--m-text-faint); }
|
||
.mc-search__input:focus {
|
||
border-color: var(--m-border-strong);
|
||
background: var(--m-bg-medium);
|
||
}
|
||
.mc-search__clear {
|
||
position: absolute;
|
||
right: 8px;
|
||
width: 22px;
|
||
height: 22px;
|
||
display: grid;
|
||
place-items: center;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--m-text-muted);
|
||
border-radius: 6px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: background-color 140ms ease, color 140ms ease;
|
||
}
|
||
.mc-search__clear:hover {
|
||
background: var(--m-bg-soft-hover);
|
||
color: var(--m-text);
|
||
}
|
||
.mc-search__clear > i { font-size: 0.7rem; }
|
||
|
||
.mc-view-toggle {
|
||
flex-shrink: 0;
|
||
display: inline-flex;
|
||
background: var(--m-bg-medium);
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 10px;
|
||
padding: 2px;
|
||
gap: 2px;
|
||
}
|
||
.mc-view-toggle__btn {
|
||
width: 32px;
|
||
height: 32px;
|
||
display: grid;
|
||
place-items: center;
|
||
background: transparent;
|
||
border: none;
|
||
color: var(--m-text-muted);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: background-color 140ms ease, color 140ms ease;
|
||
}
|
||
.mc-view-toggle__btn:hover {
|
||
background: var(--m-bg-soft-hover);
|
||
color: var(--m-text);
|
||
}
|
||
.mc-view-toggle__btn.is-active {
|
||
background: var(--m-accent-soft);
|
||
color: var(--m-accent);
|
||
}
|
||
.mc-view-toggle__btn > i { font-size: 0.85rem; }
|
||
|
||
/* ─── DataTable ─── */
|
||
.mc-table {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.mc-table :deep(.p-datatable) {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
background: transparent;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
}
|
||
.mc-table :deep(.p-datatable-table-container) {
|
||
flex: 1;
|
||
min-height: 0;
|
||
background: transparent;
|
||
}
|
||
.mc-table :deep(.p-datatable-thead),
|
||
.mc-table :deep(.p-datatable-thead > tr) {
|
||
background: transparent !important;
|
||
}
|
||
.mc-table :deep(.p-datatable-thead > tr > th) {
|
||
background: var(--p-content-background) !important;
|
||
color: var(--m-text);
|
||
font-size: 0.78rem;
|
||
font-weight: 700;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.08em;
|
||
padding: 10px 14px;
|
||
border-bottom: 1px solid var(--m-border);
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr) {
|
||
background: transparent;
|
||
color: var(--m-text);
|
||
cursor: pointer;
|
||
transition: background-color 140ms ease;
|
||
border-left: 3px solid var(--m-border);
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr > td) {
|
||
padding: 10px 14px;
|
||
border-bottom: 1px solid var(--m-border);
|
||
background: transparent;
|
||
vertical-align: middle;
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr:hover) {
|
||
background: var(--m-bg-soft-hover);
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr.p-datatable-row-selected) {
|
||
background: var(--m-accent-soft);
|
||
}
|
||
/* Border-left por status */
|
||
.mc-table :deep(.p-datatable-tbody > tr.is-ok) { border-left-color: rgb(22, 163, 74); }
|
||
.mc-table :deep(.p-datatable-tbody > tr.is-warn) { border-left-color: rgb(217, 119, 6); opacity: 0.85; }
|
||
|
||
.mc-table :deep(.p-datatable-loading-overlay) {
|
||
background: color-mix(in srgb, var(--m-bg-medium) 70%, transparent);
|
||
backdrop-filter: blur(2px);
|
||
}
|
||
.mc-table__loading {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
color: var(--m-text);
|
||
font-size: 0.85rem;
|
||
}
|
||
|
||
/* Paginator (DataTable interno) */
|
||
.mc-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;
|
||
}
|
||
.mc-table :deep(.p-paginator-current) {
|
||
color: var(--m-text-muted);
|
||
font-size: 0.78rem;
|
||
background: transparent;
|
||
border: none;
|
||
padding: 0 6px;
|
||
}
|
||
.mc-table :deep(.p-paginator-first),
|
||
.mc-table :deep(.p-paginator-prev),
|
||
.mc-table :deep(.p-paginator-next),
|
||
.mc-table :deep(.p-paginator-last),
|
||
.mc-table :deep(.p-paginator-page) {
|
||
min-width: 30px;
|
||
height: 30px;
|
||
color: var(--m-text);
|
||
background: transparent;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 8px;
|
||
font-size: 0.8rem;
|
||
transition: background-color 140ms ease, border-color 140ms ease;
|
||
}
|
||
.mc-table :deep(.p-paginator-page.p-paginator-page-selected) {
|
||
background: var(--m-accent-soft);
|
||
border-color: var(--m-accent-strong);
|
||
color: var(--m-accent);
|
||
}
|
||
.mc-table :deep(.p-paginator-first:not(.p-disabled):hover),
|
||
.mc-table :deep(.p-paginator-prev:not(.p-disabled):hover),
|
||
.mc-table :deep(.p-paginator-next:not(.p-disabled):hover),
|
||
.mc-table :deep(.p-paginator-last:not(.p-disabled):hover),
|
||
.mc-table :deep(.p-paginator-page:not(.p-paginator-page-selected):hover) {
|
||
background: var(--m-bg-soft-hover);
|
||
border-color: var(--m-border-strong);
|
||
}
|
||
.mc-table :deep(.p-select) {
|
||
background: transparent;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 8px;
|
||
height: 30px;
|
||
font-size: 0.78rem;
|
||
color: var(--m-text);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
.mc-table :deep(.p-select-label) {
|
||
padding: 0 8px;
|
||
color: var(--m-text);
|
||
font-size: 0.78rem;
|
||
display: flex;
|
||
align-items: center;
|
||
line-height: 1;
|
||
height: 100%;
|
||
background: transparent;
|
||
}
|
||
|
||
/* Coluna frozen "Ações" — bg sólido em ambos modos */
|
||
.mc-table :deep(td.p-datatable-frozen-column),
|
||
.mc-table :deep(th.p-datatable-frozen-column) {
|
||
background: var(--p-content-background) !important;
|
||
box-shadow: -3px 0 6px -3px rgba(0, 0, 0, 0.18);
|
||
z-index: 1;
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr:hover td.p-datatable-frozen-column) {
|
||
background: var(--m-bg-soft-hover);
|
||
}
|
||
.mc-table :deep(.p-datatable-tbody > tr.p-datatable-row-selected td.p-datatable-frozen-column) {
|
||
background: var(--m-accent-soft);
|
||
}
|
||
|
||
/* ─── Row content (design preservado do card antigo) ─── */
|
||
.mc-row__commit {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
min-width: 0;
|
||
}
|
||
.mc-row__commit.is-inactive { opacity: 0.65; }
|
||
.mc-row__color {
|
||
width: 6px;
|
||
height: 40px;
|
||
border-radius: 3px;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-row__main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 2px;
|
||
}
|
||
.mc-row__name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.mc-row__name {
|
||
font-size: 0.92rem;
|
||
font-weight: 600;
|
||
color: var(--m-text);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.mc-row__desc {
|
||
font-size: 0.78rem;
|
||
color: var(--m-text-muted);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
margin-top: 2px;
|
||
}
|
||
.mc-row__meta {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 4px;
|
||
font-size: 0.75rem;
|
||
color: var(--m-text-muted);
|
||
}
|
||
.mc-row__meta-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 5px;
|
||
}
|
||
.mc-row__meta-item > i { font-size: 0.7rem; }
|
||
|
||
.mc-row__badge {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
font-size: 0.62rem;
|
||
font-weight: 600;
|
||
padding: 2px 8px;
|
||
border-radius: 999px;
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.04em;
|
||
border: 1px solid;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-row__badge--native {
|
||
color: rgb(37, 99, 235);
|
||
background: rgba(37, 99, 235, 0.12);
|
||
border-color: rgba(37, 99, 235, 0.3);
|
||
}
|
||
.mc-row__badge--off {
|
||
color: var(--m-text-muted);
|
||
background: var(--m-bg-medium);
|
||
border-color: var(--m-border);
|
||
}
|
||
.mc-row__badge > i { font-size: 0.55rem; }
|
||
|
||
/* Coluna de ações — toggle + 2 botões pequenos */
|
||
.mc-row__actions {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-row__btn {
|
||
width: 28px;
|
||
height: 28px;
|
||
display: grid;
|
||
place-items: center;
|
||
background: var(--p-content-background);
|
||
border: 1px solid var(--m-border);
|
||
color: var(--m-text-muted);
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: background-color 140ms ease, color 140ms ease, border-color 140ms ease;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-row__btn:hover {
|
||
background: color-mix(in srgb, var(--p-primary-color) 12%, var(--p-content-background));
|
||
border-color: var(--p-primary-color);
|
||
color: var(--p-primary-color);
|
||
}
|
||
.mc-row__btn--danger:hover {
|
||
background: rgba(220, 38, 38, 0.12);
|
||
border-color: rgba(220, 38, 38, 0.5);
|
||
color: rgb(220, 38, 38);
|
||
}
|
||
.mc-row__btn:disabled {
|
||
opacity: 0.4;
|
||
cursor: not-allowed;
|
||
background: transparent;
|
||
border-color: var(--m-border);
|
||
color: var(--m-text-muted);
|
||
}
|
||
.mc-row__btn:disabled:hover {
|
||
background: transparent;
|
||
border-color: var(--m-border);
|
||
color: var(--m-text-muted);
|
||
}
|
||
.mc-row__btn > i { font-size: 0.7rem; }
|
||
|
||
/* ─── Grid view ─── */
|
||
.mc-grid-wrap {
|
||
flex: 1;
|
||
min-height: 0;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 10px;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 10px;
|
||
overflow: hidden;
|
||
background: transparent;
|
||
}
|
||
.mc-grid {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow-y: auto;
|
||
padding: 12px;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
||
gap: 10px;
|
||
align-content: start;
|
||
}
|
||
.mc-grid::-webkit-scrollbar { width: 5px; }
|
||
.mc-grid::-webkit-scrollbar-thumb { background: var(--m-border-strong); border-radius: 3px; }
|
||
.mc-grid__loading {
|
||
flex: 1;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 32px;
|
||
}
|
||
.mc-grid__card {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8px;
|
||
padding: 12px;
|
||
background: var(--m-bg-soft);
|
||
border: 1px solid var(--m-border);
|
||
border-left: 3px solid var(--m-border-strong);
|
||
border-radius: 10px;
|
||
color: var(--m-text);
|
||
text-align: left;
|
||
cursor: pointer;
|
||
font-family: inherit;
|
||
transition: background-color 140ms ease, border-color 140ms ease, transform 140ms ease;
|
||
}
|
||
.mc-grid__card:hover {
|
||
background: var(--m-bg-soft-hover);
|
||
border-color: var(--m-border-strong);
|
||
transform: translateY(-1px);
|
||
}
|
||
.mc-grid__card:focus-visible {
|
||
outline: 2px solid var(--p-primary-color);
|
||
outline-offset: 2px;
|
||
}
|
||
.mc-grid__card.is-ok { border-left-color: rgb(22, 163, 74); }
|
||
.mc-grid__card.is-warn { border-left-color: rgb(217, 119, 6); }
|
||
.mc-grid__card.is-inactive { opacity: 0.7; }
|
||
|
||
.mc-grid__top {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 8px;
|
||
}
|
||
.mc-grid__color {
|
||
width: 28px;
|
||
height: 28px;
|
||
border-radius: 6px;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-grid__top-right {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
}
|
||
.mc-grid__name-row {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
flex-wrap: wrap;
|
||
}
|
||
.mc-grid__name {
|
||
font-size: 0.92rem;
|
||
font-weight: 600;
|
||
color: var(--m-text);
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.mc-grid__desc {
|
||
font-size: 0.78rem;
|
||
color: var(--m-text-muted);
|
||
line-height: 1.45;
|
||
display: -webkit-box;
|
||
-webkit-line-clamp: 2;
|
||
-webkit-box-orient: vertical;
|
||
overflow: hidden;
|
||
}
|
||
.mc-grid__meta {
|
||
display: flex;
|
||
gap: 12px;
|
||
font-size: 0.75rem;
|
||
color: var(--m-text-muted);
|
||
margin-top: auto;
|
||
padding-top: 4px;
|
||
}
|
||
.mc-grid__footer {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: flex-end;
|
||
gap: 6px;
|
||
border-top: 1px solid var(--m-border);
|
||
padding-top: 8px;
|
||
margin-top: 4px;
|
||
}
|
||
|
||
/* Paginator standalone (grid view) */
|
||
.mc-paginator.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;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-paginator.p-paginator .p-paginator-current {
|
||
color: var(--m-text-muted);
|
||
font-size: 0.78rem;
|
||
background: transparent;
|
||
border: none;
|
||
padding: 0 6px;
|
||
}
|
||
.mc-paginator.p-paginator .p-paginator-first,
|
||
.mc-paginator.p-paginator .p-paginator-prev,
|
||
.mc-paginator.p-paginator .p-paginator-next,
|
||
.mc-paginator.p-paginator .p-paginator-last,
|
||
.mc-paginator.p-paginator .p-paginator-page {
|
||
min-width: 30px;
|
||
height: 30px;
|
||
color: var(--m-text);
|
||
background: transparent;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 8px;
|
||
font-size: 0.8rem;
|
||
transition: background-color 140ms ease, border-color 140ms ease;
|
||
}
|
||
.mc-paginator.p-paginator .p-paginator-page.p-paginator-page-selected {
|
||
background: var(--m-accent-soft);
|
||
border-color: var(--m-accent-strong);
|
||
color: var(--m-accent);
|
||
}
|
||
.mc-paginator.p-paginator .p-paginator-first:not(.p-disabled):hover,
|
||
.mc-paginator.p-paginator .p-paginator-prev:not(.p-disabled):hover,
|
||
.mc-paginator.p-paginator .p-paginator-next:not(.p-disabled):hover,
|
||
.mc-paginator.p-paginator .p-paginator-last:not(.p-disabled):hover,
|
||
.mc-paginator.p-paginator .p-paginator-page:not(.p-paginator-page-selected):hover {
|
||
background: var(--m-bg-soft-hover);
|
||
border-color: var(--m-border-strong);
|
||
}
|
||
.mc-paginator.p-paginator .p-select {
|
||
background: transparent;
|
||
border: 1px solid var(--m-border);
|
||
border-radius: 8px;
|
||
height: 30px;
|
||
font-size: 0.78rem;
|
||
color: var(--m-text);
|
||
display: inline-flex;
|
||
align-items: center;
|
||
}
|
||
.mc-paginator.p-paginator .p-select-label {
|
||
padding: 0 8px;
|
||
color: var(--m-text);
|
||
font-size: 0.78rem;
|
||
display: flex;
|
||
align-items: center;
|
||
line-height: 1;
|
||
height: 100%;
|
||
background: transparent;
|
||
}
|
||
|
||
/* ─── Empty state ─── */
|
||
.mc-empty {
|
||
margin: 24px 0;
|
||
padding: 56px 28px;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
text-align: center;
|
||
color: var(--m-text-muted);
|
||
border: 2px dashed var(--m-border-strong);
|
||
border-radius: 12px;
|
||
background: color-mix(in srgb, var(--m-bg-soft) 40%, transparent);
|
||
gap: 8px;
|
||
}
|
||
.mc-empty__icon { font-size: 2rem; color: var(--m-text-faint); margin-bottom: 4px; }
|
||
.mc-empty__title { font-size: 0.92rem; font-weight: 600; color: var(--m-text); }
|
||
.mc-empty__hint { font-size: 0.78rem; }
|
||
.mc-empty__btn { margin-top: 8px; }
|
||
|
||
/* ─── Drawer mobile ─── */
|
||
/* Pattern espelhado do AppMenu/layout-sidebar do Rail:
|
||
- .xx-mobile-drawer = flex column, altura 100vh
|
||
- __scroll = passagem (flex: 1, sem scroll proprio, overflow hidden)
|
||
- .xx-side teleportada = flex column ocupando todo o espaco
|
||
- .xx-side__scroll interno = scroll real (flex: 1, overflow-y auto)
|
||
- .xx-side__footer = flex-shrink 0, sempre colado no bottom */
|
||
.mc-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;
|
||
}
|
||
.mc-mobile-drawer.is-open { transform: translateX(0); }
|
||
.mc-mobile-drawer__scroll {
|
||
flex: 1;
|
||
min-height: 0;
|
||
overflow: hidden;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.mc-mobile-drawer__scroll .mc-side {
|
||
flex: 1;
|
||
min-height: 0;
|
||
width: 100%;
|
||
overflow: hidden;
|
||
background: transparent;
|
||
border-right: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
.mc-mobile-drawer__scroll .mc-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;
|
||
}
|
||
.mc-mobile-drawer__scroll .mc-side__scroll::-webkit-scrollbar { width: 5px; }
|
||
.mc-mobile-drawer__scroll .mc-side__scroll::-webkit-scrollbar-thumb {
|
||
background: var(--m-border-strong);
|
||
border-radius: 3px;
|
||
}
|
||
.mc-mobile-drawer__scroll .mc-w--side {
|
||
margin: 0;
|
||
flex-shrink: 0;
|
||
}
|
||
.mc-mobile-drawer__scroll .mc-w--side:last-of-type { margin-bottom: 0; }
|
||
.mc-mobile-drawer__scroll .mc-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%);
|
||
}
|
||
.mc-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;
|
||
}
|
||
.mc-drawer-fade-enter-active,
|
||
.mc-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
||
.mc-drawer-fade-enter-from,
|
||
.mc-drawer-fade-leave-to { opacity: 0; }
|
||
|
||
/* ─── Mobile (<1024px) ─── */
|
||
@media (max-width: 1023px) {
|
||
.mc-body { flex-direction: column; padding: 0; }
|
||
.mc-main { width: 100%; padding: 8px; }
|
||
.mc-page__title > span:first-of-type { display: none; }
|
||
.mc-page__title-icon { display: none; }
|
||
.mc-menu-btn--mobile-only { display: inline-flex; }
|
||
/* Em mobile o "Novo" só ícone pra economizar espaço */
|
||
.mc-act-btn--primary span { display: none; }
|
||
.mc-act-btn--primary { width: 32px; padding: 0; justify-content: center; }
|
||
}
|
||
</style>
|