5d2c389486
DOIS BUGS DE LAYOUT corrigidos via CSS (post-Fase 8 polish):
1. CARDS DA SIDEBAR sendo encolhidos
.mpa-side__scroll eh display:flex flex-direction:column. Os cards
.mpa-w filhos NAO tinham flex-shrink:0, entao quando havia muitos
cards stacked (Acoes + Nav 7 tabs + Sub-nav Perfil 6 + Vinculos),
o flex shrink default (1) reduzia cada card proporcionalmente.
Combinado com .mpa-w { overflow:hidden } (necessario pro radius),
itens internos das listas eram cortados/escondidos.
FIX: .mpa-side__scroll > .mpa-w { flex-shrink: 0; height: auto; }
Agora cada card cresce ate o tamanho real do conteudo, e o scroll
vertical do .mpa-side__scroll lida com overflow.
2. ABAS DO MAIN sem gap entre elementos
<div class="mpa-tab"> nao tinha CSS definido. Os filhos (KPIs grid,
panels, cards) ficavam colados. .mpa-main eh flex-col com gap, mas
como cada aba envolve seus elementos num <div .mpa-tab>, esse div
precisa replicar o spacing.
FIX: .mpa-tab { display: flex; flex-direction: column; gap: 12px; }
Visivel em todas as 7 abas. Fase 1 ja deveria ter incluido — passou
despercebido.
ESLint: 0 errors.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
3633 lines
156 KiB
Vue
3633 lines
156 KiB
Vue
<script setup>
|
|
/*
|
|
* MelissaPaciente — Pagina nativa Melissa pra Prontuario do Paciente.
|
|
*
|
|
* FASE 1 (foundation): chrome glass + drawer mobile + sidebar (avatar +
|
|
* acoes + nav 7 tabs) + main com 7 abas placeholders. Os composables
|
|
* de dados (usePatientDetail/Sessions/Financial/Messages/Documents) ja
|
|
* carregam o paciente quando :patientId muda.
|
|
*
|
|
* Fase 1 NAO substitui ainda o PatientProntuario.vue legado (3593L) —
|
|
* os 4 callsites continuam funcionando. Vai sendo migrado nas fases
|
|
* 2-8 (cada tab uma sessao dedicada).
|
|
*
|
|
* Layout:
|
|
* - sidebar (320px, ESQUERDA, drawer no mobile) — header avatar +
|
|
* acoes rapidas + nav 7 tabs + sub-nav perfil
|
|
* - main (flex 1) — tab content
|
|
*
|
|
* Prefixo CSS: .mpa-* (Melissa PAciente).
|
|
*/
|
|
import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue';
|
|
import { useRouter } from 'vue-router';
|
|
import { useToast } from 'primevue/usetoast';
|
|
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
|
import DocumentsListPage from '@/features/documents/DocumentsListPage.vue';
|
|
import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue';
|
|
import { usePatientDetail } from '@/features/patients/composables/usePatientDetail';
|
|
import { usePatientSessions } from '@/features/patients/composables/usePatientSessions';
|
|
import { usePatientFinancial } from '@/features/patients/composables/usePatientFinancial';
|
|
import { usePatientMessages } from '@/features/patients/composables/usePatientMessages';
|
|
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
|
|
import {
|
|
pickField,
|
|
calcAge,
|
|
fmtRelative,
|
|
fmtDateBR,
|
|
fmtDateTimeBR,
|
|
fmtCurrency,
|
|
fmtHourShort,
|
|
fmtDayShort,
|
|
fmtCPF,
|
|
fmtRG,
|
|
fmtGender,
|
|
fmtMarital,
|
|
fmtPhoneMobile,
|
|
sessionDuration,
|
|
chConvLabel,
|
|
recordStatus,
|
|
RECORD_STATUS_LABEL,
|
|
fmtPaymentMethod,
|
|
STATUS_LABEL,
|
|
STATUS_SEVERITY,
|
|
tagStyle as tagStyleHelper
|
|
} from '@/features/patients/utils/patientFormatters';
|
|
// Tag/Skeleton: auto via PrimeVueResolver
|
|
|
|
const props = defineProps({
|
|
patientId: { type: String, default: '' }
|
|
});
|
|
const emit = defineEmits(['close', 'edit', 'add-financial', 'open-whatsapp']);
|
|
|
|
const router = useRouter();
|
|
const toast = useToast();
|
|
const conversationDrawerStore = useConversationDrawerStore();
|
|
|
|
// ── Composables de dados ───────────────────────────────────
|
|
const detail = usePatientDetail();
|
|
const sessionsHook = usePatientSessions();
|
|
const financialHook = usePatientFinancial();
|
|
const messagesHook = usePatientMessages();
|
|
const documentsHook = usePatientDocuments();
|
|
|
|
// ── 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; }
|
|
|
|
|
|
// ── Tabs ───────────────────────────────────────────────────
|
|
const TABS = [
|
|
{ key: 'overview', label: 'Visão Geral', icon: 'pi pi-chart-line', color: '#a855f7' },
|
|
{ key: 'perfil', label: 'Perfil', icon: 'pi pi-user', color: '#3b82f6' },
|
|
{ key: 'pron', label: 'Prontuário', icon: 'pi pi-file-edit', color: '#06b6d4' },
|
|
{ key: 'agenda', label: 'Agenda', icon: 'pi pi-calendar', color: '#10b981' },
|
|
{ key: 'financ', label: 'Financeiro', icon: 'pi pi-wallet', color: '#f59e0b' },
|
|
{ key: 'doc', label: 'Documentos', icon: 'pi pi-folder', color: '#f97316' },
|
|
{ key: 'conv', label: 'Conversas', icon: 'pi pi-whatsapp', color: '#22c55e' }
|
|
];
|
|
const activeTab = ref('overview');
|
|
function selectTab(key) {
|
|
activeTab.value = key;
|
|
if (isMobile.value) drawerOpen.value = false;
|
|
}
|
|
|
|
// Filtros da aba Prontuario (MVP — usa session.observacoes como evolucao)
|
|
const pronFilter = ref('com-evolucao'); // com-evolucao | todas | realiz | falt | cancel
|
|
const PRON_FILTERS = [
|
|
{ value: 'com-evolucao', label: 'Com evolução', icon: 'pi pi-file-edit' },
|
|
{ value: 'todas', label: 'Todas', icon: 'pi pi-list' },
|
|
{ value: 'realiz', label: 'Realizadas', icon: 'pi pi-check-circle' },
|
|
{ value: 'falt', label: 'Faltas', icon: 'pi pi-user-minus' },
|
|
{ value: 'cancel', label: 'Cancelamentos', icon: 'pi pi-ban' }
|
|
];
|
|
|
|
// Filtros da aba Agenda (lista cronologica + agrupamento por mes)
|
|
const agendaFilter = ref('all'); // all | future | past | realiz | falt | cancel
|
|
const AGENDA_FILTERS = [
|
|
{ value: 'all', label: 'Todas', icon: 'pi pi-list' },
|
|
{ value: 'future', label: 'Próximas', icon: 'pi pi-calendar-plus' },
|
|
{ value: 'past', label: 'Passadas', icon: 'pi pi-history' },
|
|
{ value: 'realiz', label: 'Realizadas', icon: 'pi pi-check-circle' },
|
|
{ value: 'falt', label: 'Faltas', icon: 'pi pi-user-minus' },
|
|
{ value: 'cancel', label: 'Canceladas', icon: 'pi pi-ban' }
|
|
];
|
|
|
|
// Sub-nav da aba Perfil
|
|
const PROFILE_SECTIONS = [
|
|
{ key: 'pessoais', label: 'Informações Pessoais', icon: 'pi pi-pencil' },
|
|
{ key: 'endereco', label: 'Endereço', icon: 'pi pi-map-marker' },
|
|
{ key: 'adicional', label: 'Dados Adicionais', icon: 'pi pi-tags' },
|
|
{ key: 'resp', label: 'Responsável', icon: 'pi pi-users' },
|
|
{ key: 'anot', label: 'Anotações', icon: 'pi pi-file-edit' },
|
|
{ key: 'sess', label: 'Sessões', icon: 'pi pi-calendar' }
|
|
];
|
|
const activeProfileSection = ref('pessoais');
|
|
function scrollToProfileSection(key) {
|
|
activeProfileSection.value = key;
|
|
if (isMobile.value) drawerOpen.value = false;
|
|
nextTick(() => {
|
|
const el = document.getElementById(`mpa-perfil-${key}`);
|
|
if (el && typeof el.scrollIntoView === 'function') {
|
|
el.scrollIntoView({ behavior: 'smooth', block: 'start' });
|
|
}
|
|
});
|
|
}
|
|
|
|
// ── Helpers de exibicao ────────────────────────────────────
|
|
function dash(v) {
|
|
const s = String(v ?? '').trim();
|
|
return s || '—';
|
|
}
|
|
|
|
const patientData = computed(() => detail.patient.value || {});
|
|
const nomeCompleto = computed(() => patientData.value?.nome_completo || patientData.value?.nome || 'Paciente');
|
|
const avatarUrl = computed(() =>
|
|
patientData.value?.avatar_url || patientData.value?.avatar || null
|
|
);
|
|
const avatarInitials = computed(() => {
|
|
const parts = String(nomeCompleto.value || '').trim().split(/\s+/).filter(Boolean);
|
|
if (!parts.length) return '·';
|
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
});
|
|
|
|
const ageLabel = computed(() => {
|
|
const age = calcAge(patientData.value?.data_nascimento);
|
|
return age == null ? '' : `${age} anos`;
|
|
});
|
|
|
|
const convenio = computed(() => patientData.value?.convenio || patientData.value?.plano_saude || '');
|
|
const statusPaciente = computed(() => patientData.value?.status || '');
|
|
const riscoElevado = computed(() => !!patientData.value?.risco_elevado);
|
|
|
|
function tagStyle(t) {
|
|
return tagStyleHelper(t);
|
|
}
|
|
|
|
// ── Field computeds (cobre snake_case e camelCase do schema) ─
|
|
const birthValue = computed(() => pickField(patientData.value, ['data_nascimento', 'birth_date']));
|
|
const telefone = computed(() => pickField(patientData.value, ['telefone', 'phone']));
|
|
const telefoneAlternativo = computed(() => pickField(patientData.value, ['telefone_alternativo', 'phone_alt', 'phoneAlt']));
|
|
const emailPrincipal = computed(() => pickField(patientData.value, ['email_principal', 'email']));
|
|
const emailAlternativo = computed(() => pickField(patientData.value, ['email_alternativo', 'email_alt', 'emailAlt']));
|
|
const genero = computed(() => pickField(patientData.value, ['genero', 'gender']));
|
|
const estadoCivil = computed(() => pickField(patientData.value, ['estado_civil', 'marital_status']));
|
|
const naturalidade = computed(() => pickField(patientData.value, ['naturalidade', 'birthplace', 'place_of_birth']));
|
|
const ondeNosConheceu = computed(() => pickField(patientData.value, ['onde_nos_conheceu', 'lead_source']));
|
|
const encaminhadoPor = computed(() => pickField(patientData.value, ['encaminhado_por', 'referred_by']));
|
|
const observacoes = computed(() => pickField(patientData.value, ['observacoes', 'notes_short']));
|
|
const notasInternas = computed(() => pickField(patientData.value, ['notas_internas', 'notes']));
|
|
|
|
// Endereço
|
|
const cep = computed(() => pickField(patientData.value, ['cep', 'postal_code']));
|
|
const pais = computed(() => pickField(patientData.value, ['pais', 'country']));
|
|
const cidade = computed(() => pickField(patientData.value, ['cidade', 'city']));
|
|
const estado = computed(() => pickField(patientData.value, ['estado', 'state']));
|
|
const endereco = computed(() => pickField(patientData.value, ['endereco', 'address_line']));
|
|
const numero = computed(() => pickField(patientData.value, ['numero', 'address_number']));
|
|
const bairro = computed(() => pickField(patientData.value, ['bairro', 'neighborhood']));
|
|
const complemento = computed(() => pickField(patientData.value, ['complemento', 'address_complement']));
|
|
|
|
// Dados Adicionais
|
|
const escolaridade = computed(() => pickField(patientData.value, ['escolaridade', 'education', 'education_level']));
|
|
const profissao = computed(() => pickField(patientData.value, ['profissao', 'profession']));
|
|
const nomeParente = computed(() => pickField(patientData.value, ['nome_parente', 'relative_name']));
|
|
const grauParentesco = computed(() => pickField(patientData.value, ['grau_parentesco', 'relative_relation']));
|
|
const telefoneParente = computed(() => pickField(patientData.value, ['telefone_parente', 'relative_phone']));
|
|
|
|
// Responsável (pra menores)
|
|
const nomeResponsavel = computed(() => pickField(patientData.value, ['nome_responsavel', 'guardian_name']));
|
|
const cpfResponsavel = computed(() => pickField(patientData.value, ['cpf_responsavel', 'guardian_cpf']));
|
|
const telefoneResponsavel = computed(() => pickField(patientData.value, ['telefone_responsavel', 'guardian_phone']));
|
|
const observacaoResponsavel = computed(() => pickField(patientData.value, ['observacao_responsavel', 'guardian_note']));
|
|
|
|
// Grupos como string label (Origem na Informacoes Pessoais)
|
|
const groupNames = computed(() => detail.groups.value.map((g) => g?.name).filter(Boolean));
|
|
const groupLabel = computed(() => groupNames.value.length ? groupNames.value.join(', ') : '—');
|
|
const groupCountLabel = computed(() => groupNames.value.length <= 1 ? 'Grupo' : 'Grupos');
|
|
|
|
// ── Tab Agenda: filtros + agrupamento por mes ──────────────
|
|
const agendaSessoesFiltradas = computed(() => {
|
|
const list = sessionsHook.sessions.value;
|
|
const now = Date.now();
|
|
switch (agendaFilter.value) {
|
|
case 'future': return list.filter((s) => s.inicio_em && new Date(s.inicio_em).getTime() > now);
|
|
case 'past': return list.filter((s) => s.inicio_em && new Date(s.inicio_em).getTime() <= now);
|
|
case 'realiz': return list.filter((s) => /realiz|present/i.test(String(s.status || '')));
|
|
case 'falt': return list.filter((s) => /falt/i.test(String(s.status || ''))) ;
|
|
case 'cancel': return list.filter((s) => /cancel|remarca/i.test(String(s.status || '')));
|
|
default: return list;
|
|
}
|
|
});
|
|
// Agrupa por "Mes de YYYY" mantendo ordem DESC (mais recente primeiro)
|
|
const agendaAgrupadas = computed(() => {
|
|
const groups = [];
|
|
let key = null;
|
|
for (const ev of agendaSessoesFiltradas.value) {
|
|
if (!ev.inicio_em) continue;
|
|
const d = new Date(ev.inicio_em);
|
|
const k = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
|
|
if (k !== key) {
|
|
key = k;
|
|
groups.push({
|
|
key: k,
|
|
label: d.toLocaleDateString('pt-BR', { month: 'long', year: 'numeric' })
|
|
.replace(/(^|\s)\S/g, (l) => l.toUpperCase()),
|
|
items: []
|
|
});
|
|
}
|
|
groups[groups.length - 1].items.push(ev);
|
|
}
|
|
return groups;
|
|
});
|
|
|
|
// Handler de mutacao financeira: marcar pago / reverter
|
|
async function markRecordPaid(record) {
|
|
const result = await financialHook.markPaid(record.id);
|
|
if (result.ok) {
|
|
toast.add({ severity: 'success', summary: 'Lançamento marcado como pago', life: 2200 });
|
|
} else {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Falha ao marcar pago',
|
|
detail: result.error || 'Erro inesperado',
|
|
life: 4000
|
|
});
|
|
}
|
|
}
|
|
async function revertRecordPaid(record) {
|
|
const result = await financialHook.markUnpaid(record.id);
|
|
if (result.ok) {
|
|
toast.add({ severity: 'success', summary: 'Pagamento revertido', life: 2200 });
|
|
} else {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Falha ao reverter',
|
|
detail: result.error || 'Erro inesperado',
|
|
life: 4000
|
|
});
|
|
}
|
|
}
|
|
|
|
// Handler de mutacao de status (Realizada / Falta / Cancelar)
|
|
async function updateSessionStatus(ev, novoStatus, msg) {
|
|
const result = await sessionsHook.updateStatus(ev.id, novoStatus);
|
|
if (result.ok) {
|
|
toast.add({ severity: 'success', summary: msg, life: 2200 });
|
|
} else {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Falha ao atualizar',
|
|
detail: result.error || 'Erro inesperado',
|
|
life: 4000
|
|
});
|
|
}
|
|
}
|
|
|
|
// ── Tab Prontuario: lista filtrada de sessoes ──────────────
|
|
// MVP enquanto anamnese/evolucao_clinica nao existem no schema:
|
|
// usa agenda_eventos.observacoes como nota evolutiva.
|
|
const pronSessions = computed(() => {
|
|
const all = sessionsHook.sessions.value;
|
|
if (pronFilter.value === 'todas') return all;
|
|
if (pronFilter.value === 'com-evolucao') {
|
|
return all.filter((s) => s.observacoes && String(s.observacoes).trim());
|
|
}
|
|
if (pronFilter.value === 'realiz') {
|
|
return all.filter((s) => /realiz|present/i.test(String(s.status || '')));
|
|
}
|
|
if (pronFilter.value === 'falt') {
|
|
return all.filter((s) => /falt/i.test(String(s.status || '')));
|
|
}
|
|
if (pronFilter.value === 'cancel') {
|
|
return all.filter((s) => /cancel|remarca/i.test(String(s.status || '')));
|
|
}
|
|
return all;
|
|
});
|
|
const pronSessionsCount = computed(() => pronSessions.value.length);
|
|
const sessoesComEvolucao = computed(() =>
|
|
sessionsHook.sessions.value.filter((s) => s.observacoes && String(s.observacoes).trim()).length
|
|
);
|
|
|
|
// ── KPIs Visao Geral (Fase 2) ──────────────────────────────
|
|
const kpiSessoes = computed(() => sessionsHook.totalSessoes.value);
|
|
const kpiRealizadas = computed(() => sessionsHook.totalRealizadas.value);
|
|
const kpiMensagens = computed(() => messagesHook.messages.value.length);
|
|
|
|
// ── Acoes ──────────────────────────────────────────────────
|
|
// X (close): volta de onde veio (Agenda OU Pacientes) via history.
|
|
// Fallback pra /melissa/pacientes se nao tem history (deep-link direto).
|
|
function close() {
|
|
emit('close');
|
|
if (window.history.length > 1) {
|
|
router.back();
|
|
} else {
|
|
router.push('/melissa/pacientes');
|
|
}
|
|
}
|
|
|
|
// Botao dedicado: navega EXPLICITAMENTE pra lista de pacientes.
|
|
function goToPacientes() {
|
|
router.push('/melissa/pacientes');
|
|
}
|
|
|
|
// Edit: navega pra /melissa/pacientes?edit=<id> e a propria
|
|
// MelissaPacientes detecta esse query param e abre o cadastroFullDialog.
|
|
function editPatient() {
|
|
emit('edit', props.patientId);
|
|
router.push({ path: '/melissa/pacientes', query: { edit: props.patientId } });
|
|
}
|
|
|
|
// Open WhatsApp: usa o conversationDrawerStore global (mesmo padrao
|
|
// que MelissaPacientes usa). O drawer desce sobre o Melissa sem fechar.
|
|
function openWhatsapp() {
|
|
emit('open-whatsapp', props.patientId);
|
|
if (!props.patientId) return;
|
|
const data = patientData.value || {};
|
|
try {
|
|
if (typeof conversationDrawerStore.openForPatient === 'function') {
|
|
conversationDrawerStore.openForPatient({
|
|
id: props.patientId,
|
|
name: data.nome_completo || data.nome || '',
|
|
phone: data.telefone || data.phone || '',
|
|
avatar_url: data.avatar_url || data.avatar || ''
|
|
});
|
|
}
|
|
} catch (e) {
|
|
toast.add({
|
|
severity: 'error',
|
|
summary: 'Falha ao abrir conversa',
|
|
detail: e?.message || 'Erro ao abrir o drawer.',
|
|
life: 3500
|
|
});
|
|
}
|
|
}
|
|
|
|
// Add financial: por enquanto so emite. Futuro: dialog inline de
|
|
// novo lancamento (Fase 9).
|
|
function addFinancial() {
|
|
emit('add-financial', props.patientId);
|
|
toast.add({
|
|
severity: 'info',
|
|
summary: 'Em breve',
|
|
detail: 'Dialog de novo lançamento será adicionado numa próxima sessão.',
|
|
life: 3000
|
|
});
|
|
}
|
|
|
|
// ── Load data quando patientId muda ────────────────────────
|
|
async function loadAll(id) {
|
|
if (!id) return;
|
|
await Promise.all([
|
|
detail.load(id),
|
|
sessionsHook.load(id),
|
|
financialHook.load(id),
|
|
messagesHook.load(id),
|
|
documentsHook.load(id)
|
|
]);
|
|
}
|
|
|
|
watch(() => props.patientId, async (id) => {
|
|
activeTab.value = 'overview';
|
|
activeProfileSection.value = 'pessoais';
|
|
await loadAll(id);
|
|
}, { immediate: true });
|
|
|
|
// ── Lifecycle (matchMedia) ─────────────────────────────────
|
|
onMounted(() => {
|
|
if (typeof window !== 'undefined' && window.matchMedia) {
|
|
_mqMobile = window.matchMedia('(max-width: 1023px)');
|
|
isMobile.value = _mqMobile.matches;
|
|
try { _mqMobile.addEventListener('change', _onMqMobileChange); }
|
|
catch { _mqMobile.addListener(_onMqMobileChange); }
|
|
}
|
|
});
|
|
|
|
onBeforeUnmount(() => {
|
|
if (_mqMobile) {
|
|
try { _mqMobile.removeEventListener('change', _onMqMobileChange); }
|
|
catch { _mqMobile.removeListener(_onMqMobileChange); }
|
|
}
|
|
});
|
|
|
|
</script>
|
|
|
|
<template>
|
|
<Transition name="mpa-drawer-fade">
|
|
<div
|
|
v-show="isMobile && drawerOpen"
|
|
class="mpa-mobile-drawer"
|
|
:class="{ 'is-open': drawerOpen }"
|
|
>
|
|
<div id="mpa-mobile-drawer-target" class="mpa-mobile-drawer__scroll" />
|
|
</div>
|
|
</Transition>
|
|
<Transition name="mpa-drawer-fade">
|
|
<div
|
|
v-show="isMobile && drawerOpen"
|
|
class="mpa-mobile-drawer__backdrop"
|
|
@click="fecharDrawer"
|
|
/>
|
|
</Transition>
|
|
|
|
<section class="mpa-page">
|
|
<!-- ── Header ── -->
|
|
<header class="mpa-page__head">
|
|
<button
|
|
class="mpa-menu-btn mpa-menu-btn--mobile-only"
|
|
v-tooltip.bottom="'Ações & Navegação'"
|
|
@click="toggleDrawer"
|
|
>
|
|
<i class="pi pi-bars" />
|
|
<span>Menu</span>
|
|
</button>
|
|
<div class="mpa-page__title">
|
|
<div v-if="avatarUrl" class="mpa-head__avatar">
|
|
<img :src="avatarUrl" alt="" />
|
|
</div>
|
|
<div v-else class="mpa-head__avatar mpa-head__avatar--initials">
|
|
<span>{{ avatarInitials }}</span>
|
|
</div>
|
|
<div class="mpa-head__id">
|
|
<div class="mpa-head__name">{{ dash(nomeCompleto) }}</div>
|
|
<div class="mpa-head__meta">
|
|
<span v-if="ageLabel">{{ ageLabel }}</span>
|
|
<span v-if="patientData.pronomes" class="mpa-head__sep">·</span>
|
|
<span v-if="patientData.pronomes">{{ patientData.pronomes }}</span>
|
|
<Tag v-if="statusPaciente" :value="statusPaciente" severity="success" class="mpa-head__tag" />
|
|
<Tag v-if="convenio" :value="convenio" severity="info" class="mpa-head__tag" />
|
|
<span v-if="riscoElevado" class="mpa-head__risk">
|
|
<i class="pi pi-exclamation-circle" />
|
|
risco
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-page__actions">
|
|
<button class="mpa-act-btn" v-tooltip.bottom="'Conversar via WhatsApp'" @click="openWhatsapp">
|
|
<i class="pi pi-whatsapp" />
|
|
</button>
|
|
<button class="mpa-act-btn" v-tooltip.bottom="'Editar paciente'" @click="editPatient">
|
|
<i class="pi pi-pencil" />
|
|
</button>
|
|
<button class="mpa-close" v-tooltip.bottom="'Fechar (Esc)'" @click="close">
|
|
<i class="pi pi-times" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<!-- Banner de risco elevado (subheader) -->
|
|
<div v-if="riscoElevado" class="mpa-subheader mpa-subheader--risk">
|
|
<i class="pi pi-exclamation-triangle mpa-subheader__icon" />
|
|
<span class="mpa-subheader__text">
|
|
<strong>Risco elevado sinalizado</strong> — mantenha atenção redobrada na próxima sessão.
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Body 2-col -->
|
|
<div class="mpa-body">
|
|
<!-- ═══ COL 1: Sidebar — esquerda ═══ -->
|
|
<Teleport to="#mpa-mobile-drawer-target" :disabled="!isMobile">
|
|
<aside class="mpa-side">
|
|
<!-- Voltar pra Pacientes — substitui o botao de Configuracoes
|
|
(prontuario pertence a Pacientes, nao a Configuracoes). -->
|
|
<button class="mpa-cfg-btn mpa-cfg-btn--back" @click="goToPacientes">
|
|
<i class="pi pi-arrow-left" />
|
|
<span>Voltar para Pacientes</span>
|
|
</button>
|
|
<div class="mpa-side__scroll">
|
|
<!-- Card: Acoes rapidas -->
|
|
<div class="mpa-w mpa-w--side">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon"><i class="pi pi-bolt" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">Ações rápidas</div>
|
|
<div class="mpa-w__sub">Atalhos pra sessão atual</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<button type="button" class="mpa-quick-btn" @click="openWhatsapp">
|
|
<i class="pi pi-whatsapp" :style="{ color: '#22c55e' }" />
|
|
<span>Conversar</span>
|
|
</button>
|
|
<button type="button" class="mpa-quick-btn" @click="editPatient">
|
|
<i class="pi pi-pencil" :style="{ color: '#3b82f6' }" />
|
|
<span>Editar dados</span>
|
|
</button>
|
|
<button type="button" class="mpa-quick-btn" @click="addFinancial">
|
|
<i class="pi pi-plus" :style="{ color: '#f59e0b' }" />
|
|
<span>Lançamento</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Card: Navegacao (tabs) -->
|
|
<div class="mpa-w mpa-w--side">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon"><i class="pi pi-list" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">Navegação</div>
|
|
<div class="mpa-w__sub">Click pra trocar de aba</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<button
|
|
v-for="t in TABS"
|
|
:key="t.key"
|
|
type="button"
|
|
class="mpa-tab-btn"
|
|
:class="{ 'is-active': activeTab === t.key }"
|
|
@click="selectTab(t.key)"
|
|
>
|
|
<div
|
|
class="mpa-tab-btn__icon"
|
|
:style="{
|
|
background: t.color + '22',
|
|
color: t.color
|
|
}"
|
|
>
|
|
<i :class="t.icon" />
|
|
</div>
|
|
<span class="mpa-tab-btn__label">{{ t.label }}</span>
|
|
<i class="pi pi-chevron-right mpa-tab-btn__chev" />
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Sub-nav Perfil (so quando aba Perfil ativa, desktop) -->
|
|
<div v-if="activeTab === 'perfil'" class="mpa-w mpa-w--side">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon"><i class="pi pi-user" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">Seções do perfil</div>
|
|
<div class="mpa-w__sub">Acordeão</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<button
|
|
v-for="s in PROFILE_SECTIONS"
|
|
:key="s.key"
|
|
type="button"
|
|
class="mpa-tab-btn mpa-tab-btn--sub"
|
|
:class="{ 'is-active': activeProfileSection === s.key }"
|
|
@click="scrollToProfileSection(s.key)"
|
|
>
|
|
<i :class="s.icon" class="mpa-tab-btn__sub-icon" />
|
|
<span class="mpa-tab-btn__label">{{ s.label }}</span>
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tags + grupos (preview compacto) -->
|
|
<div v-if="detail.groups.value.length || detail.tags.value.length" class="mpa-w mpa-w--side">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon"><i class="pi pi-tag" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">Vínculos</div>
|
|
<div class="mpa-w__sub">Grupos e tags</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body mpa-w__body--chips">
|
|
<span v-for="g in detail.groups.value" :key="`g-${g.id}`" class="mpa-chip mpa-chip--group">
|
|
<i class="pi pi-th-large" />
|
|
{{ g.name }}
|
|
</span>
|
|
<span v-for="t in detail.tags.value" :key="`t-${t.id}`" class="mpa-chip" :style="tagStyle(t)">
|
|
{{ t.name }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</aside>
|
|
</Teleport>
|
|
|
|
<!-- ═══ COL 2: Main — tab content ═══ -->
|
|
<div class="mpa-main">
|
|
<!-- Loading -->
|
|
<template v-if="detail.loading.value">
|
|
<div class="mpa-w" v-for="n in 3" :key="`sk-${n}`">
|
|
<div class="mpa-w__body">
|
|
<Skeleton width="40%" height="20px" class="mb-3" />
|
|
<Skeleton v-for="m in 3" :key="`sk-${n}-${m}`" width="100%" height="36px" class="mb-2" />
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Erro -->
|
|
<template v-else-if="detail.loadError.value">
|
|
<div class="mpa-error">
|
|
<i class="pi pi-exclamation-circle" />
|
|
<div>
|
|
<div class="mpa-error__title">Falha ao carregar</div>
|
|
<div class="mpa-error__detail">{{ detail.loadError.value }}</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<!-- Conteudo de cada aba -->
|
|
<template v-else>
|
|
<!-- ABA: Visao Geral (Fase 2 — KPIs + Timeline + Mensagens + Notas) -->
|
|
<div v-if="activeTab === 'overview'" class="mpa-tab">
|
|
<!-- ── 4 KPI cards ──────────────────────── -->
|
|
<div class="mpa-kpis">
|
|
<!-- 01 Sessoes -->
|
|
<article class="mpa-kpi" style="--c:#4ade80">
|
|
<span class="mpa-kpi__num">01</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-check-circle" /></div>
|
|
<span class="mpa-kpi__tag">Sessões</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ kpiRealizadas }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
de {{ kpiSessoes }} totais
|
|
<span v-if="sessionsHook.totalFaltas.value" class="mpa-kpi__cap-warn">
|
|
· {{ sessionsHook.totalFaltas.value }}
|
|
{{ sessionsHook.totalFaltas.value === 1 ? 'falta' : 'faltas' }}
|
|
</span>
|
|
<span v-if="sessionsHook.totalCanceladas.value" class="mpa-kpi__cap-dim">
|
|
· {{ sessionsHook.totalCanceladas.value }} cancel.
|
|
</span>
|
|
</div>
|
|
</article>
|
|
|
|
<!-- 02 Pagamento -->
|
|
<article
|
|
class="mpa-kpi"
|
|
:style="financialHook.statusFinanceiro.value.emDia === false
|
|
? '--c:#f87171'
|
|
: '--c:var(--p-primary-color)'"
|
|
>
|
|
<span class="mpa-kpi__num">02</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-wallet" /></div>
|
|
<span class="mpa-kpi__tag">Pagamento</span>
|
|
</header>
|
|
<template v-if="financialHook.loading.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">…</div>
|
|
<div class="mpa-kpi__cap">Carregando</div>
|
|
</template>
|
|
<template v-else-if="!financialHook.records.value.length">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">—</div>
|
|
<div class="mpa-kpi__cap">Sem lançamentos</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ financialHook.statusFinanceiro.value.emDia === false ? 'Em atraso' : 'Em dia' }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="financialHook.statusFinanceiro.value.proxVenc">
|
|
Próx. venc. {{ fmtDateBR(financialHook.statusFinanceiro.value.proxVenc.due_date) }}
|
|
<span class="mpa-kpi__cap-dim">
|
|
· {{ fmtRelative(financialHook.statusFinanceiro.value.proxVenc.due_date) }}
|
|
</span>
|
|
</template>
|
|
<template v-else-if="financialHook.statusFinanceiro.value.totalPendente > 0">
|
|
{{ fmtCurrency(financialHook.statusFinanceiro.value.totalPendente) }} pendente
|
|
</template>
|
|
<template v-else>Tudo quitado</template>
|
|
</div>
|
|
</template>
|
|
</article>
|
|
|
|
<!-- 03 Proxima sessao -->
|
|
<article class="mpa-kpi" style="--c:#60a5fa">
|
|
<span class="mpa-kpi__num">03</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-calendar-clock" /></div>
|
|
<span class="mpa-kpi__tag">Próxima</span>
|
|
</header>
|
|
<template v-if="sessionsHook.proximaSessao.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtRelative(sessionsHook.proximaSessao.value.inicio_em) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ fmtDateTimeBR(sessionsHook.proximaSessao.value.inicio_em) }}
|
|
<span v-if="sessionsHook.proximaSessao.value.modalidade" class="mpa-kpi__cap-dim">
|
|
· {{ sessionsHook.proximaSessao.value.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">—</div>
|
|
<div class="mpa-kpi__cap">Sem sessão futura agendada</div>
|
|
</template>
|
|
</article>
|
|
|
|
<!-- 04 Mensagens -->
|
|
<article class="mpa-kpi" style="--c:#34d399">
|
|
<span class="mpa-kpi__num">04</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-comments" /></div>
|
|
<span class="mpa-kpi__tag">Mensagens</span>
|
|
</header>
|
|
<template v-if="messagesHook.loading.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">…</div>
|
|
<div class="mpa-kpi__cap">Carregando</div>
|
|
</template>
|
|
<template v-else-if="messagesHook.ultimaMensagem.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtRelative(messagesHook.ultimaMensagem.value.created_at) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
Última {{ messagesHook.ultimaMensagem.value.direction === 'inbound' ? 'recebida' : 'enviada' }}
|
|
<span class="mpa-kpi__cap-dim">· {{ kpiMensagens }} no histórico</span>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">—</div>
|
|
<div class="mpa-kpi__cap">Nenhuma conversa registrada</div>
|
|
</template>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- ── Grid 2-col: Timeline + Mensagens/Notas ── -->
|
|
<div class="mpa-grid">
|
|
<!-- Timeline -->
|
|
<section class="mpa-panel">
|
|
<header class="mpa-panel__head">
|
|
<div class="mpa-panel__title"><i class="pi pi-history" /> Timeline de atendimentos</div>
|
|
<span class="mpa-panel__badge">{{ sessionsHook.ultimasAtendidas.value.length }}</span>
|
|
</header>
|
|
<div class="mpa-panel__body">
|
|
<div v-if="sessionsHook.loading.value" class="mpa-empty">Carregando…</div>
|
|
<div v-else-if="!sessionsHook.ultimasAtendidas.value.length" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-history" /></div>
|
|
<div class="mpa-empty__title">Sem atendimentos registrados</div>
|
|
<div class="mpa-empty__sub">As sessões realizadas aparecerão aqui em ordem cronológica.</div>
|
|
</div>
|
|
<ol v-else class="mpa-timeline">
|
|
<li
|
|
v-for="s in sessionsHook.ultimasAtendidas.value"
|
|
:key="s.id"
|
|
class="mpa-tl"
|
|
:data-status="String(s.status || 'agendado').toLowerCase()"
|
|
>
|
|
<span class="mpa-tl__dot" />
|
|
<div class="mpa-tl__main">
|
|
<div class="mpa-tl__top">
|
|
<span class="mpa-tl__when">{{ fmtDateTimeBR(s.inicio_em) }}</span>
|
|
<span class="mpa-tl__rel">{{ fmtRelative(s.inicio_em) }}</span>
|
|
</div>
|
|
<div class="mpa-tl__row">
|
|
<Tag
|
|
:value="STATUS_LABEL[s.status] || s.status || '—'"
|
|
:severity="STATUS_SEVERITY[s.status] || 'info'"
|
|
class="mpa-tl__tag"
|
|
/>
|
|
<span v-if="s.modalidade" class="mpa-tl__chip">
|
|
<i :class="s.modalidade === 'online' ? 'pi pi-video' : 'pi pi-map-marker'" />
|
|
{{ s.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
|
</span>
|
|
<span v-if="sessionDuration(s.inicio_em, s.fim_em)" class="mpa-tl__chip mpa-tl__chip--dim">
|
|
<i class="pi pi-clock" />
|
|
{{ sessionDuration(s.inicio_em, s.fim_em) }}
|
|
</span>
|
|
</div>
|
|
<p v-if="s.observacoes" class="mpa-tl__note">{{ s.observacoes }}</p>
|
|
</div>
|
|
</li>
|
|
</ol>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Coluna direita: Mensagens + Notas -->
|
|
<div class="mpa-grid__col">
|
|
<!-- Mensagens recentes -->
|
|
<section class="mpa-panel">
|
|
<header class="mpa-panel__head">
|
|
<div class="mpa-panel__title"><i class="pi pi-comments" /> Últimas mensagens</div>
|
|
<span class="mpa-panel__badge">{{ kpiMensagens }}</span>
|
|
</header>
|
|
<div class="mpa-panel__body">
|
|
<div v-if="messagesHook.loading.value" class="mpa-empty">Carregando…</div>
|
|
<div v-else-if="!messagesHook.recentes.value.length" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-comments" /></div>
|
|
<div class="mpa-empty__title">Sem conversa registrada</div>
|
|
<div class="mpa-empty__sub">As últimas mensagens aparecerão aqui.</div>
|
|
</div>
|
|
<ul v-else class="mpa-msgs">
|
|
<li
|
|
v-for="m in messagesHook.recentes.value"
|
|
:key="m.id"
|
|
class="mpa-msg"
|
|
:class="m.direction === 'inbound' ? 'mpa-msg--in' : 'mpa-msg--out'"
|
|
>
|
|
<div class="mpa-msg__meta">
|
|
<i :class="m.direction === 'inbound' ? 'pi pi-arrow-down-left' : 'pi pi-arrow-up-right'" />
|
|
<span>{{ m.direction === 'inbound' ? 'Recebida' : 'Enviada' }}</span>
|
|
<span class="mpa-msg__sep">·</span>
|
|
<span>{{ fmtRelative(m.created_at) }}</span>
|
|
</div>
|
|
<p class="mpa-msg__body">{{ m.body || '—' }}</p>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Notas e observacoes -->
|
|
<section v-if="notasInternas || observacoes" class="mpa-panel">
|
|
<header class="mpa-panel__head">
|
|
<div class="mpa-panel__title"><i class="pi pi-file-edit" /> Notas e observações</div>
|
|
</header>
|
|
<div class="mpa-panel__body mpa-notes">
|
|
<div v-if="observacoes" class="mpa-note">
|
|
<p class="mpa-note__label">Observações gerais</p>
|
|
<p class="mpa-note__text">{{ observacoes }}</p>
|
|
</div>
|
|
<div v-if="notasInternas" class="mpa-note">
|
|
<p class="mpa-note__label">
|
|
<i class="pi pi-lock" /> Internas
|
|
</p>
|
|
<p class="mpa-note__text">{{ notasInternas }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- ABA: Perfil (Fase 3 — 6 sections stacked com anchors) -->
|
|
<div v-else-if="activeTab === 'perfil'" class="mpa-tab">
|
|
<!-- ── 1. Informacoes Pessoais ── -->
|
|
<section id="mpa-perfil-pessoais" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-pencil" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">1. Informações Pessoais</div>
|
|
<div class="mpa-w__sub">Cadastro, contato e origem</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div class="mpa-fields-grid">
|
|
<!-- Dados de cadastro -->
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Dados de cadastro</p>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Nome completo</span>
|
|
<span class="mpa-field-row__v">{{ dash(nomeCompleto) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Data de nascimento</span>
|
|
<span class="mpa-field-row__v">
|
|
{{ fmtDateBR(birthValue) }}
|
|
<span v-if="calcAge(birthValue) != null" class="mpa-field-row__v-dim">
|
|
({{ calcAge(birthValue) }} a)
|
|
</span>
|
|
</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Gênero</span>
|
|
<span class="mpa-field-row__v">{{ fmtGender(genero) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Estado civil</span>
|
|
<span class="mpa-field-row__v">{{ fmtMarital(estadoCivil) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">CPF</span>
|
|
<span class="mpa-field-row__v mpa-field-row__v--mono">{{ fmtCPF(patientData.cpf) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">RG</span>
|
|
<span class="mpa-field-row__v mpa-field-row__v--mono">{{ fmtRG(patientData.rg) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--last">
|
|
<span class="mpa-field-row__k">Naturalidade</span>
|
|
<span class="mpa-field-row__v">{{ dash(naturalidade) }}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Contato + Origem -->
|
|
<div class="mpa-fields-stack">
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Contato</p>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Telefone / Celular</span>
|
|
<a v-if="telefone" :href="`tel:${telefone}`" class="mpa-field-row__v mpa-field-row__v--link">
|
|
{{ fmtPhoneMobile(telefone) }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Telefone alternativo</span>
|
|
<a v-if="telefoneAlternativo" :href="`tel:${telefoneAlternativo}`" class="mpa-field-row__v mpa-field-row__v--link">
|
|
{{ fmtPhoneMobile(telefoneAlternativo) }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">E-mail principal</span>
|
|
<a v-if="emailPrincipal" :href="`mailto:${emailPrincipal}`" class="mpa-field-row__v mpa-field-row__v--link mpa-field-row__v--truncate">
|
|
{{ emailPrincipal }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--last">
|
|
<span class="mpa-field-row__k">E-mail alternativo</span>
|
|
<a v-if="emailAlternativo" :href="`mailto:${emailAlternativo}`" class="mpa-field-row__v mpa-field-row__v--link mpa-field-row__v--truncate">
|
|
{{ emailAlternativo }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Origem</p>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">{{ groupCountLabel }}</span>
|
|
<span class="mpa-field-row__v">{{ groupLabel }}</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--align-start">
|
|
<span class="mpa-field-row__k">Tags</span>
|
|
<div class="mpa-field-row__chips">
|
|
<span v-for="t in detail.tags.value" :key="t.id" class="mpa-chip" :style="tagStyle(t)">
|
|
{{ t.name }}
|
|
</span>
|
|
<span v-if="!detail.tags.value.length" class="mpa-field-row__v">—</span>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Onde nos conheceu?</span>
|
|
<span class="mpa-field-row__v">{{ dash(ondeNosConheceu) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--last">
|
|
<span class="mpa-field-row__k">Encaminhado por</span>
|
|
<span class="mpa-field-row__v">{{ dash(encaminhadoPor) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Observacoes (full-width quando preenchido) -->
|
|
<div v-if="observacoes" class="mpa-fields mpa-fields--full">
|
|
<p class="mpa-fields__label">Observações</p>
|
|
<p class="mpa-fields__textblock">{{ observacoes }}</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── 2. Endereco ── -->
|
|
<section id="mpa-perfil-endereco" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-map-marker" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">2. Endereço</div>
|
|
<div class="mpa-w__sub">Localização do paciente</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Localização</p>
|
|
<div class="mpa-field-grid-2">
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">CEP</span>
|
|
<span class="mpa-field-row__v mpa-field-row__v--mono">{{ dash(cep) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">País</span>
|
|
<span class="mpa-field-row__v">{{ dash(pais) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Cidade</span>
|
|
<span class="mpa-field-row__v">{{ dash(cidade) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Estado</span>
|
|
<span class="mpa-field-row__v">{{ dash(estado) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Endereço</span>
|
|
<span class="mpa-field-row__v">{{ dash(endereco) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Número</span>
|
|
<span class="mpa-field-row__v">{{ dash(numero) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Bairro</span>
|
|
<span class="mpa-field-row__v">{{ dash(bairro) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--last">
|
|
<span class="mpa-field-row__k">Complemento</span>
|
|
<span class="mpa-field-row__v">{{ dash(complemento) }}</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── 3. Dados Adicionais ── -->
|
|
<section id="mpa-perfil-adicional" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-tags" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">3. Dados Adicionais</div>
|
|
<div class="mpa-w__sub">Formação e contato familiar</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Formação & família</p>
|
|
<div class="mpa-field-grid-2">
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Escolaridade</span>
|
|
<span class="mpa-field-row__v">{{ dash(escolaridade) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Profissão</span>
|
|
<span class="mpa-field-row__v">{{ dash(profissao) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Nome de um parente</span>
|
|
<span class="mpa-field-row__v">{{ dash(nomeParente) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Grau de parentesco</span>
|
|
<span class="mpa-field-row__v">{{ dash(grauParentesco) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row mpa-field-row--last">
|
|
<span class="mpa-field-row__k">Telefone do parente</span>
|
|
<a v-if="telefoneParente" :href="`tel:${telefoneParente}`" class="mpa-field-row__v mpa-field-row__v--link">
|
|
{{ fmtPhoneMobile(telefoneParente) }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── 4. Responsavel ── -->
|
|
<section id="mpa-perfil-resp" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-users" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">4. Responsável</div>
|
|
<div class="mpa-w__sub">Para pacientes menores de idade</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div class="mpa-fields">
|
|
<p class="mpa-fields__label">Dados do responsável</p>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">Nome</span>
|
|
<span class="mpa-field-row__v">{{ dash(nomeResponsavel) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row">
|
|
<span class="mpa-field-row__k">CPF</span>
|
|
<span class="mpa-field-row__v mpa-field-row__v--mono">{{ fmtCPF(cpfResponsavel) }}</span>
|
|
</div>
|
|
<div class="mpa-field-row" :class="{ 'mpa-field-row--last': !observacaoResponsavel }">
|
|
<span class="mpa-field-row__k">Telefone</span>
|
|
<a v-if="telefoneResponsavel" :href="`tel:${telefoneResponsavel}`" class="mpa-field-row__v mpa-field-row__v--link">
|
|
{{ fmtPhoneMobile(telefoneResponsavel) }}
|
|
</a>
|
|
<span v-else class="mpa-field-row__v">—</span>
|
|
</div>
|
|
<div v-if="observacaoResponsavel" class="mpa-field-block mpa-field-block--last">
|
|
<p class="mpa-field-block__label">Observação</p>
|
|
<p class="mpa-field-block__text">{{ observacaoResponsavel }}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── 5. Anotacoes Internas ── -->
|
|
<section id="mpa-perfil-anot" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-file-edit" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">5. Anotações Internas</div>
|
|
<div class="mpa-w__sub">Visível só pra você</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div class="mpa-fields">
|
|
<div class="mpa-fields__lock-hint">
|
|
<i class="pi pi-lock" />
|
|
Campo interno — não aparece no cadastro externo.
|
|
</div>
|
|
<p class="mpa-fields__textblock mpa-fields__textblock--min">
|
|
{{ notasInternas ? notasInternas : '—' }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- ── 6. Sessoes (mini lista — completa fica na aba Agenda) ── -->
|
|
<section id="mpa-perfil-sess" class="mpa-w">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--blue"><i class="pi pi-calendar" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">6. Sessões</div>
|
|
<div class="mpa-w__sub">{{ kpiSessoes }} no histórico — abertura completa na aba Agenda</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<div v-if="sessionsHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando sessões…
|
|
</div>
|
|
<div v-else-if="!sessionsHook.sessions.value.length" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-calendar" /></div>
|
|
<div class="mpa-empty__title">Nenhuma sessão</div>
|
|
<div class="mpa-empty__sub">As sessões agendadas com este paciente aparecerão aqui.</div>
|
|
</div>
|
|
<div v-else class="mpa-sess-list">
|
|
<div
|
|
v-for="s in sessionsHook.sessions.value"
|
|
:key="s.id"
|
|
class="mpa-sess"
|
|
>
|
|
<div class="mpa-sess__main">
|
|
<p class="mpa-sess__title">
|
|
{{ s.titulo_custom || s.titulo || (s.tipo ? s.tipo : 'Sessão') }}
|
|
</p>
|
|
<div class="mpa-sess__meta">
|
|
<span><i class="pi pi-calendar" />{{ fmtDateTimeBR(s.inicio_em) }}</span>
|
|
<span v-if="sessionDuration(s.inicio_em, s.fim_em)">
|
|
<i class="pi pi-clock" />{{ sessionDuration(s.inicio_em, s.fim_em) }}
|
|
</span>
|
|
<span v-if="s.modalidade">
|
|
<i :class="s.modalidade === 'online' ? 'pi pi-video' : 'pi pi-map-marker'" />
|
|
{{ s.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
|
</span>
|
|
</div>
|
|
<p v-if="s.observacoes" class="mpa-sess__obs">{{ s.observacoes }}</p>
|
|
</div>
|
|
<Tag
|
|
:value="STATUS_LABEL[s.status] || s.status || 'Agendado'"
|
|
:severity="STATUS_SEVERITY[s.status] || 'info'"
|
|
class="mpa-sess__tag"
|
|
/>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- ABA: Prontuario (Fase 4 MVP — evolucao via session.observacoes) -->
|
|
<div v-else-if="activeTab === 'pron'" class="mpa-tab">
|
|
<!-- Header explicativo -->
|
|
<div class="mpa-pron-hint">
|
|
<i class="pi pi-info-circle" />
|
|
<div>
|
|
<strong>Prontuário em construção.</strong>
|
|
Por enquanto mostra as <strong>observações que você anota nas sessões</strong>
|
|
como histórico evolutivo. Anamnese estruturada, plano terapêutico e
|
|
evolução por temas chegam quando o módulo clínico for liberado.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mini-stats -->
|
|
<div class="mpa-pron-stats">
|
|
<article class="mpa-pron-stat" style="--c:#06b6d4">
|
|
<i class="pi pi-file-edit" />
|
|
<div>
|
|
<div class="mpa-pron-stat__value">{{ sessoesComEvolucao }}</div>
|
|
<div class="mpa-pron-stat__label">com evolução</div>
|
|
</div>
|
|
</article>
|
|
<article class="mpa-pron-stat" style="--c:#10b981">
|
|
<i class="pi pi-check-circle" />
|
|
<div>
|
|
<div class="mpa-pron-stat__value">{{ sessionsHook.totalRealizadas.value }}</div>
|
|
<div class="mpa-pron-stat__label">realizadas</div>
|
|
</div>
|
|
</article>
|
|
<article class="mpa-pron-stat" style="--c:#f87171">
|
|
<i class="pi pi-user-minus" />
|
|
<div>
|
|
<div class="mpa-pron-stat__value">{{ sessionsHook.totalFaltas.value }}</div>
|
|
<div class="mpa-pron-stat__label">faltas</div>
|
|
</div>
|
|
</article>
|
|
<article class="mpa-pron-stat" style="--c:#94a3b8">
|
|
<i class="pi pi-calendar" />
|
|
<div>
|
|
<div class="mpa-pron-stat__value">{{ sessionsHook.totalSessoes.value }}</div>
|
|
<div class="mpa-pron-stat__label">total</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Filtros -->
|
|
<div class="mpa-pron-filters" role="tablist">
|
|
<button
|
|
v-for="f in PRON_FILTERS"
|
|
:key="f.value"
|
|
type="button"
|
|
role="tab"
|
|
:aria-selected="pronFilter === f.value"
|
|
class="mpa-pron-filter"
|
|
:class="{ 'is-active': pronFilter === f.value }"
|
|
@click="pronFilter = f.value"
|
|
>
|
|
<i :class="f.icon" />
|
|
<span>{{ f.label }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Lista de evolução -->
|
|
<div v-if="sessionsHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando…
|
|
</div>
|
|
<div v-else-if="!pronSessionsCount" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-file-edit" /></div>
|
|
<div class="mpa-empty__title">
|
|
<template v-if="!sessionsHook.sessions.value.length">
|
|
Sem sessões registradas
|
|
</template>
|
|
<template v-else-if="pronFilter === 'com-evolucao'">
|
|
Nenhuma sessão tem evolução escrita ainda
|
|
</template>
|
|
<template v-else>
|
|
Nenhuma sessão neste filtro
|
|
</template>
|
|
</div>
|
|
<div class="mpa-empty__sub">
|
|
<template v-if="pronFilter === 'com-evolucao' && sessionsHook.sessions.value.length">
|
|
Use o campo <strong>Observações</strong> ao editar uma sessão pra registrar
|
|
como ela transcorreu — vai aparecer aqui como nota evolutiva.
|
|
</template>
|
|
<template v-else-if="!sessionsHook.sessions.value.length">
|
|
Quando você atender este paciente, as sessões e evoluções aparecerão aqui.
|
|
</template>
|
|
<template v-else>
|
|
Tente outro filtro acima ou veja "Todas" pra listar o histórico completo.
|
|
</template>
|
|
</div>
|
|
</div>
|
|
<div v-else class="mpa-pron-list">
|
|
<article
|
|
v-for="s in pronSessions"
|
|
:key="s.id"
|
|
class="mpa-pron-item"
|
|
:data-status="String(s.status || 'agendado').toLowerCase()"
|
|
>
|
|
<div class="mpa-pron-item__head">
|
|
<div class="mpa-pron-item__when">
|
|
<span class="mpa-pron-item__date">{{ fmtDateTimeBR(s.inicio_em) }}</span>
|
|
<span class="mpa-pron-item__rel">{{ fmtRelative(s.inicio_em) }}</span>
|
|
</div>
|
|
<div class="mpa-pron-item__chips">
|
|
<Tag
|
|
:value="STATUS_LABEL[s.status] || s.status || 'Agendado'"
|
|
:severity="STATUS_SEVERITY[s.status] || 'info'"
|
|
class="mpa-pron-item__tag"
|
|
/>
|
|
<span v-if="s.modalidade" class="mpa-tl__chip">
|
|
<i :class="s.modalidade === 'online' ? 'pi pi-video' : 'pi pi-map-marker'" />
|
|
{{ s.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
|
</span>
|
|
<span v-if="sessionDuration(s.inicio_em, s.fim_em)" class="mpa-tl__chip mpa-tl__chip--dim">
|
|
<i class="pi pi-clock" /> {{ sessionDuration(s.inicio_em, s.fim_em) }}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div v-if="s.titulo_custom || s.titulo" class="mpa-pron-item__title">
|
|
{{ s.titulo_custom || s.titulo }}
|
|
</div>
|
|
<div v-if="s.observacoes" class="mpa-pron-item__evol">
|
|
<div class="mpa-pron-item__evol-label">
|
|
<i class="pi pi-file-edit" />
|
|
Evolução
|
|
</div>
|
|
<p class="mpa-pron-item__evol-text">{{ s.observacoes }}</p>
|
|
</div>
|
|
<div v-else class="mpa-pron-item__noevol">
|
|
<i class="pi pi-circle" />
|
|
Sem evolução registrada
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Roadmap card "em breve" -->
|
|
<section class="mpa-w mpa-pron-roadmap">
|
|
<div class="mpa-w__head">
|
|
<div class="mpa-w__icon mpa-w__icon--cyan"><i class="pi pi-sparkles" /></div>
|
|
<div class="mpa-w__title">
|
|
<div class="mpa-w__title-text">Em breve no prontuário</div>
|
|
<div class="mpa-w__sub">Roadmap clínico previsto</div>
|
|
</div>
|
|
</div>
|
|
<div class="mpa-w__body">
|
|
<ul class="mpa-pron-roadmap__list">
|
|
<li>
|
|
<i class="pi pi-clipboard" />
|
|
<div>
|
|
<strong>Anamnese estruturada</strong>
|
|
<span>Modelo configurável por terapeuta com seções (queixa, história, hipótese diagnóstica, objetivos).</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<i class="pi pi-bullseye" />
|
|
<div>
|
|
<strong>Plano terapêutico</strong>
|
|
<span>Objetivos com prazo + acompanhamento de progresso ao longo das sessões.</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<i class="pi pi-tag" />
|
|
<div>
|
|
<strong>Evolução por temas</strong>
|
|
<span>Tagging das notas pra cruzar evolução com objetivos e gerar relatórios.</span>
|
|
</div>
|
|
</li>
|
|
<li>
|
|
<i class="pi pi-shield" />
|
|
<div>
|
|
<strong>Assinatura digital + LGPD Art. 18</strong>
|
|
<span>Notas imutáveis com hash de auditoria, exportação compatível CFP.</span>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
|
|
<!-- ABA: Agenda (Fase 5 — KPIs + filtros + grupos por mes + acoes) -->
|
|
<div v-else-if="activeTab === 'agenda'" class="mpa-tab">
|
|
<!-- Loading -->
|
|
<div v-if="sessionsHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando…
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- KPIs (4) -->
|
|
<div class="mpa-kpis">
|
|
<article class="mpa-kpi" style="--c:var(--p-primary-color)">
|
|
<span class="mpa-kpi__num">01</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-calendar" /></div>
|
|
<span class="mpa-kpi__tag">Total</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ sessionsHook.totalSessoes.value }}</div>
|
|
<div class="mpa-kpi__cap">sessões registradas</div>
|
|
</article>
|
|
<article class="mpa-kpi" style="--c:#4ade80">
|
|
<span class="mpa-kpi__num">02</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-check-circle" /></div>
|
|
<span class="mpa-kpi__tag">Realizadas</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ sessionsHook.totalRealizadas.value }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="sessionsHook.totalSessoes.value">
|
|
{{ Math.round((sessionsHook.totalRealizadas.value / sessionsHook.totalSessoes.value) * 100) }}% do total
|
|
</template>
|
|
<template v-else>—</template>
|
|
</div>
|
|
</article>
|
|
<article
|
|
class="mpa-kpi"
|
|
:style="sessionsHook.totalFaltas.value > 0 ? '--c:#f87171' : '--c:#94a3b8'"
|
|
>
|
|
<span class="mpa-kpi__num">03</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-user-minus" /></div>
|
|
<span class="mpa-kpi__tag">Faltas</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ sessionsHook.totalFaltas.value }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="sessionsHook.totalCanceladas.value">
|
|
+ {{ sessionsHook.totalCanceladas.value }} cancel.
|
|
</template>
|
|
<template v-else>nenhuma falta</template>
|
|
</div>
|
|
</article>
|
|
<article class="mpa-kpi" style="--c:#60a5fa">
|
|
<span class="mpa-kpi__num">04</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-calendar-clock" /></div>
|
|
<span class="mpa-kpi__tag">Próxima</span>
|
|
</header>
|
|
<template v-if="sessionsHook.proximaSessao.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtRelative(sessionsHook.proximaSessao.value.inicio_em) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ fmtDateBR(sessionsHook.proximaSessao.value.inicio_em) }}
|
|
· {{ fmtHourShort(sessionsHook.proximaSessao.value.inicio_em) }}
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">—</div>
|
|
<div class="mpa-kpi__cap">Sem futura</div>
|
|
</template>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Filter chips -->
|
|
<div class="mpa-pron-filters" role="tablist">
|
|
<button
|
|
v-for="f in AGENDA_FILTERS"
|
|
:key="f.value"
|
|
type="button"
|
|
role="tab"
|
|
:aria-selected="agendaFilter === f.value"
|
|
class="mpa-pron-filter"
|
|
:class="{ 'is-active': agendaFilter === f.value }"
|
|
@click="agendaFilter = f.value"
|
|
>
|
|
<i :class="f.icon" />
|
|
<span>{{ f.label }}</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Empty state contextual -->
|
|
<div v-if="!agendaAgrupadas.length" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-calendar-times" /></div>
|
|
<div class="mpa-empty__title">
|
|
{{ sessionsHook.sessions.value.length ? 'Nenhuma sessão neste filtro' : 'Sem sessões registradas' }}
|
|
</div>
|
|
<div class="mpa-empty__sub">
|
|
<template v-if="sessionsHook.sessions.value.length">
|
|
Tente outro filtro acima ou veja "Todas" pra listar o histórico completo.
|
|
</template>
|
|
<template v-else>
|
|
As sessões agendadas e atendidas com este paciente aparecerão aqui.
|
|
</template>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Grupos por mes -->
|
|
<section
|
|
v-for="g in agendaAgrupadas"
|
|
:key="g.key"
|
|
class="mpa-panel mpa-ag__group"
|
|
>
|
|
<header class="mpa-panel__head">
|
|
<div class="mpa-panel__title"><i class="pi pi-calendar" /> {{ g.label }}</div>
|
|
<span class="mpa-panel__badge">{{ g.items.length }}</span>
|
|
</header>
|
|
<ul class="mpa-ag__list">
|
|
<li
|
|
v-for="s in g.items"
|
|
:key="s.id"
|
|
class="mpa-ag__item"
|
|
:data-status="String(s.status || 'agendado').toLowerCase()"
|
|
>
|
|
<!-- Coluna data -->
|
|
<div class="mpa-ag__date">
|
|
<span class="mpa-ag__date-dow">{{ fmtDayShort(s.inicio_em) }}</span>
|
|
<span class="mpa-ag__date-day">{{ new Date(s.inicio_em).getDate() }}</span>
|
|
<span class="mpa-ag__date-time">{{ fmtHourShort(s.inicio_em) }}</span>
|
|
</div>
|
|
<!-- Coluna main -->
|
|
<div class="mpa-ag__main">
|
|
<div class="mpa-ag__top">
|
|
<Tag
|
|
:value="STATUS_LABEL[s.status] || s.status || 'Agendado'"
|
|
:severity="STATUS_SEVERITY[s.status] || 'info'"
|
|
class="mpa-ag__tag"
|
|
/>
|
|
<span v-if="s.modalidade" class="mpa-tl__chip">
|
|
<i :class="s.modalidade === 'online' ? 'pi pi-video' : 'pi pi-map-marker'" />
|
|
{{ s.modalidade === 'online' ? 'Online' : 'Presencial' }}
|
|
</span>
|
|
<span v-if="sessionDuration(s.inicio_em, s.fim_em)" class="mpa-tl__chip mpa-tl__chip--dim">
|
|
<i class="pi pi-clock" />
|
|
{{ sessionDuration(s.inicio_em, s.fim_em) }}
|
|
</span>
|
|
<span class="mpa-ag__rel">{{ fmtRelative(s.inicio_em) }}</span>
|
|
</div>
|
|
<div v-if="s.titulo_custom || s.titulo" class="mpa-ag__title">
|
|
{{ s.titulo_custom || s.titulo }}
|
|
</div>
|
|
<p v-if="s.observacoes" class="mpa-ag__note">{{ s.observacoes }}</p>
|
|
</div>
|
|
<!-- Coluna actions -->
|
|
<div class="mpa-ag__actions">
|
|
<button
|
|
type="button"
|
|
v-tooltip.left="'Marcar como realizada'"
|
|
class="mpa-ag__act mpa-ag__act--ok"
|
|
:disabled="sessionsHook.busy.value"
|
|
@click="updateSessionStatus(s, 'realizado', 'Sessão marcada como realizada')"
|
|
>
|
|
<i class="pi pi-check-circle" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
v-tooltip.left="'Marcar como falta'"
|
|
class="mpa-ag__act mpa-ag__act--warn"
|
|
:disabled="sessionsHook.busy.value"
|
|
@click="updateSessionStatus(s, 'faltou', 'Marcada como falta')"
|
|
>
|
|
<i class="pi pi-user-minus" />
|
|
</button>
|
|
<button
|
|
type="button"
|
|
v-tooltip.left="'Cancelar'"
|
|
class="mpa-ag__act mpa-ag__act--danger"
|
|
:disabled="sessionsHook.busy.value"
|
|
@click="updateSessionStatus(s, 'cancelado', 'Sessão cancelada')"
|
|
>
|
|
<i class="pi pi-ban" />
|
|
</button>
|
|
</div>
|
|
</li>
|
|
</ul>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- ABA: Financeiro (Fase 6 — KPIs + tabela + mark paid) -->
|
|
<div v-else-if="activeTab === 'financ'" class="mpa-tab">
|
|
<!-- Loading -->
|
|
<div v-if="financialHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando…
|
|
</div>
|
|
|
|
<!-- Empty state com CTA -->
|
|
<div v-else-if="!financialHook.records.value.length" class="mpa-empty mpa-empty--rich">
|
|
<div class="mpa-empty__icon"><i class="pi pi-wallet" /></div>
|
|
<div class="mpa-empty__title">Sem lançamentos financeiros</div>
|
|
<div class="mpa-empty__sub">
|
|
Adicione o primeiro lançamento de cobrança ou recebimento deste paciente.
|
|
</div>
|
|
<button type="button" class="mpa-quick-btn mpa-quick-btn--cta" @click="addFinancial">
|
|
<i class="pi pi-plus" :style="{ color: '#f59e0b' }" />
|
|
<span>Novo lançamento</span>
|
|
</button>
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- 3 KPIs financeiros -->
|
|
<div class="mpa-kpis">
|
|
<article class="mpa-kpi" style="--c:#4ade80">
|
|
<span class="mpa-kpi__num">01</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-check-circle" /></div>
|
|
<span class="mpa-kpi__tag">Pago</span>
|
|
</header>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtCurrency(financialHook.statusFinanceiro.value.totalPago) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ financialHook.records.value.filter((r) => !!r.paid_at).length }}
|
|
{{ financialHook.records.value.filter((r) => !!r.paid_at).length === 1 ? 'lançamento' : 'lançamentos' }}
|
|
</div>
|
|
</article>
|
|
|
|
<article class="mpa-kpi" style="--c:var(--p-primary-color)">
|
|
<span class="mpa-kpi__num">02</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-clock" /></div>
|
|
<span class="mpa-kpi__tag">Pendente</span>
|
|
</header>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtCurrency(financialHook.statusFinanceiro.value.totalPendente) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="financialHook.statusFinanceiro.value.proxVenc">
|
|
Próx. venc. {{ fmtDateBR(financialHook.statusFinanceiro.value.proxVenc.due_date) }}
|
|
</template>
|
|
<template v-else>A receber</template>
|
|
</div>
|
|
</article>
|
|
|
|
<article
|
|
class="mpa-kpi"
|
|
:style="financialHook.statusFinanceiro.value.vencidos > 0 ? '--c:#f87171' : '--c:#94a3b8'"
|
|
>
|
|
<span class="mpa-kpi__num">03</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-exclamation-triangle" /></div>
|
|
<span class="mpa-kpi__tag">Em atraso</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ financialHook.statusFinanceiro.value.vencidos }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ financialHook.statusFinanceiro.value.vencidos === 0
|
|
? 'Tudo em dia'
|
|
: (financialHook.statusFinanceiro.value.vencidos === 1 ? 'lançamento vencido' : 'lançamentos vencidos') }}
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- Tabela de lançamentos -->
|
|
<section class="mpa-panel">
|
|
<header class="mpa-panel__head">
|
|
<div class="mpa-panel__title"><i class="pi pi-list" /> Lançamentos</div>
|
|
<div class="mpa-fin__head-actions">
|
|
<span class="mpa-panel__badge">{{ financialHook.recordsOrdenados.value.length }}</span>
|
|
<button type="button" class="mpa-icon-btn-sm" v-tooltip.left="'Novo lançamento'" @click="addFinancial">
|
|
<i class="pi pi-plus" />
|
|
</button>
|
|
</div>
|
|
</header>
|
|
<div class="mpa-fin__table" role="table">
|
|
<div class="mpa-fin__row mpa-fin__row--head" role="row">
|
|
<span role="columnheader">Vencimento</span>
|
|
<span role="columnheader">Descrição</span>
|
|
<span role="columnheader" class="mpa-fin__col-method">Forma</span>
|
|
<span role="columnheader" class="mpa-fin__col-amount">Valor</span>
|
|
<span role="columnheader" class="mpa-fin__col-status">Status</span>
|
|
<span role="columnheader" class="mpa-fin__col-action"></span>
|
|
</div>
|
|
<div
|
|
v-for="r in financialHook.recordsOrdenados.value"
|
|
:key="r.id"
|
|
class="mpa-fin__row"
|
|
:data-status="recordStatus(r)"
|
|
role="row"
|
|
>
|
|
<span class="mpa-fin__date" role="cell">
|
|
<span class="mpa-fin__date-main">
|
|
{{ r.due_date ? fmtDateBR(r.due_date) : fmtDateBR(r.created_at) }}
|
|
</span>
|
|
<span v-if="r.due_date" class="mpa-fin__date-rel">
|
|
{{ fmtRelative(r.due_date) }}
|
|
</span>
|
|
</span>
|
|
<span class="mpa-fin__desc" role="cell">
|
|
{{ r.description || (r.category ? r.category : 'Lançamento') }}
|
|
</span>
|
|
<span class="mpa-fin__method" role="cell">
|
|
{{ fmtPaymentMethod(r.payment_method) || '—' }}
|
|
</span>
|
|
<span class="mpa-fin__amount" role="cell">
|
|
{{ fmtCurrency(Number(r.amount) || 0) }}
|
|
</span>
|
|
<span class="mpa-fin__status-cell" role="cell">
|
|
<span class="mpa-fin__status" :data-status="recordStatus(r)">
|
|
<span class="mpa-fin__status-dot" />
|
|
{{ RECORD_STATUS_LABEL[recordStatus(r)] }}
|
|
</span>
|
|
</span>
|
|
<span class="mpa-fin__action" role="cell">
|
|
<!-- Marca como pago se ainda nao foi -->
|
|
<button
|
|
v-if="!r.paid_at"
|
|
type="button"
|
|
v-tooltip.left="'Marcar como pago'"
|
|
class="mpa-ag__act mpa-ag__act--ok"
|
|
:disabled="financialHook.busy.value"
|
|
@click="markRecordPaid(r)"
|
|
>
|
|
<i class="pi pi-check" />
|
|
</button>
|
|
<!-- Reverte pagamento -->
|
|
<button
|
|
v-else
|
|
type="button"
|
|
v-tooltip.left="'Reverter pagamento'"
|
|
class="mpa-ag__act mpa-ag__act--warn"
|
|
:disabled="financialHook.busy.value"
|
|
@click="revertRecordPaid(r)"
|
|
>
|
|
<i class="pi pi-undo" />
|
|
</button>
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- ABA: Documentos (Fase 7 — KPIs + DocumentsListPage embedded) -->
|
|
<div v-else-if="activeTab === 'doc'" class="mpa-tab">
|
|
<!-- Loading -->
|
|
<div v-if="documentsHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando…
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- KPIs (so quando ha algo) -->
|
|
<div v-if="documentsHook.total.value" class="mpa-kpis">
|
|
<article class="mpa-kpi" style="--c:var(--p-primary-color)">
|
|
<span class="mpa-kpi__num">01</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-folder" /></div>
|
|
<span class="mpa-kpi__tag">Total</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ documentsHook.total.value }}</div>
|
|
<div class="mpa-kpi__cap">{{ documentsHook.sizeTotalFormatted.value }} no total</div>
|
|
</article>
|
|
|
|
<article v-if="documentsHook.topType.value" class="mpa-kpi" style="--c:#0ea5e9">
|
|
<span class="mpa-kpi__num">02</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-tag" /></div>
|
|
<span class="mpa-kpi__tag">Mais comum</span>
|
|
</header>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ documentsHook.topType.value.label }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ documentsHook.topType.value.count }}
|
|
{{ documentsHook.topType.value.count === 1 ? 'documento' : 'documentos' }}
|
|
</div>
|
|
</article>
|
|
|
|
<article v-if="documentsHook.ultimo.value" class="mpa-kpi" style="--c:#4ade80">
|
|
<span class="mpa-kpi__num">03</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-clock" /></div>
|
|
<span class="mpa-kpi__tag">Último</span>
|
|
</header>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtRelative(documentsHook.ultimo.value.created_at) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">{{ fmtDateBR(documentsHook.ultimo.value.created_at) }}</div>
|
|
</article>
|
|
|
|
<article v-if="documentsHook.pendentes.value > 0" class="mpa-kpi" style="--c:#f59e0b">
|
|
<span class="mpa-kpi__num">04</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-exclamation-circle" /></div>
|
|
<span class="mpa-kpi__tag">Revisão</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ documentsHook.pendentes.value }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ documentsHook.pendentes.value === 1 ? 'pendente' : 'pendentes' }}
|
|
</div>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- DocumentsListPage embedded (ja vem com upload/preview/lista) -->
|
|
<section class="mpa-w mpa-embed">
|
|
<div class="mpa-w__body mpa-embed__body">
|
|
<DocumentsListPage
|
|
:patient-id="patientId"
|
|
:patient-name="nomeCompleto || ''"
|
|
embedded
|
|
/>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
|
|
<!-- ABA: Conversas (Fase 7 — KPIs + CTA + PatientConversationsTab embedded) -->
|
|
<div v-else-if="activeTab === 'conv'" class="mpa-tab">
|
|
<!-- Loading -->
|
|
<div v-if="messagesHook.loading.value" class="mpa-empty">
|
|
<i class="pi pi-spin pi-spinner mr-2" /> Carregando…
|
|
</div>
|
|
|
|
<template v-else>
|
|
<!-- KPIs (so quando ha mensagens) -->
|
|
<div v-if="kpiMensagens" class="mpa-kpis">
|
|
<article class="mpa-kpi" style="--c:#25d366">
|
|
<span class="mpa-kpi__num">01</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-comments" /></div>
|
|
<span class="mpa-kpi__tag">Mensagens</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ kpiMensagens }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="messagesHook.canais.value.length">
|
|
via {{ messagesHook.canais.value.map(chConvLabel).join(', ') }}
|
|
</template>
|
|
<template v-else>no histórico</template>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="mpa-kpi" style="--c:#4ade80">
|
|
<span class="mpa-kpi__num">02</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-arrow-down-left" /></div>
|
|
<span class="mpa-kpi__tag">Recebidas</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ messagesHook.totalIn.value }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="kpiMensagens">
|
|
{{ Math.round((messagesHook.totalIn.value / kpiMensagens) * 100) }}% do total
|
|
</template>
|
|
<template v-else>—</template>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="mpa-kpi" style="--c:#60a5fa">
|
|
<span class="mpa-kpi__num">03</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-arrow-up-right" /></div>
|
|
<span class="mpa-kpi__tag">Enviadas</span>
|
|
</header>
|
|
<div class="mpa-kpi__big">{{ messagesHook.totalOut.value }}</div>
|
|
<div class="mpa-kpi__cap">
|
|
<template v-if="kpiMensagens">
|
|
{{ Math.round((messagesHook.totalOut.value / kpiMensagens) * 100) }}% do total
|
|
</template>
|
|
<template v-else>—</template>
|
|
</div>
|
|
</article>
|
|
|
|
<article class="mpa-kpi" style="--c:var(--p-primary-color)">
|
|
<span class="mpa-kpi__num">04</span>
|
|
<header class="mpa-kpi__head">
|
|
<div class="mpa-kpi__icon"><i class="pi pi-clock" /></div>
|
|
<span class="mpa-kpi__tag">Última</span>
|
|
</header>
|
|
<template v-if="messagesHook.ultimaMensagem.value">
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">
|
|
{{ fmtRelative(messagesHook.ultimaMensagem.value.created_at) }}
|
|
</div>
|
|
<div class="mpa-kpi__cap">
|
|
{{ messagesHook.ultimaMensagem.value.direction === 'inbound' ? 'Recebida' : 'Enviada' }}
|
|
<span v-if="messagesHook.primeiraMensagem.value" class="mpa-kpi__cap-dim">
|
|
· 1ª {{ fmtRelative(messagesHook.primeiraMensagem.value.created_at) }}
|
|
</span>
|
|
</div>
|
|
</template>
|
|
<template v-else>
|
|
<div class="mpa-kpi__big mpa-kpi__big--small">—</div>
|
|
<div class="mpa-kpi__cap">Sem mensagens</div>
|
|
</template>
|
|
</article>
|
|
</div>
|
|
|
|
<!-- CTA: abrir conversa no drawer global (continuar real-time) -->
|
|
<div class="mpa-conv-cta">
|
|
<button type="button" class="mpa-conv-cta__btn" @click="openWhatsapp">
|
|
<i class="pi pi-whatsapp" />
|
|
<span>Abrir conversa no drawer</span>
|
|
</button>
|
|
<span class="mpa-conv-cta__hint">
|
|
Continue a conversa em tempo real sem fechar o prontuário.
|
|
</span>
|
|
</div>
|
|
|
|
<!-- PatientConversationsTab embedded (thread completa) -->
|
|
<section class="mpa-w mpa-embed">
|
|
<div class="mpa-w__body mpa-embed__body">
|
|
<PatientConversationsTab
|
|
:patient-id="patientId"
|
|
:patient-name="nomeCompleto || ''"
|
|
/>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* ═══════ Page chrome (espelha MelissaAgendador/Negocio) ═══════ */
|
|
.mpa-page {
|
|
position: absolute;
|
|
inset: 6px 6px calc(var(--m-dock-h, 76px) + 6px) var(--m-config-aside-left, 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: mpa-page-enter 240ms cubic-bezier(0.2, 0.7, 0.3, 1);
|
|
}
|
|
@keyframes mpa-page-enter {
|
|
from { opacity: 0; transform: scale(0.985); }
|
|
to { opacity: 1; transform: scale(1); }
|
|
}
|
|
|
|
/* Page chrome desktop: largura TOTAL (sem right:max). O prontuario tem
|
|
muito conteudo (KPIs grid + tabelas + timeline), full-width facilita
|
|
leitura. Diferente das outras Melissa Pages que tem painel flutuante
|
|
ou conteudo mais compacto. */
|
|
|
|
.mpa-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;
|
|
}
|
|
.mpa-page__title {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
}
|
|
.mpa-head__avatar {
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
overflow: hidden;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
flex-shrink: 0;
|
|
display: grid;
|
|
place-items: center;
|
|
}
|
|
.mpa-head__avatar img {
|
|
width: 100%;
|
|
height: 100%;
|
|
object-fit: cover;
|
|
}
|
|
.mpa-head__avatar--initials {
|
|
background: color-mix(in srgb, var(--p-primary-color) 18%, transparent);
|
|
color: var(--p-primary-color);
|
|
font-weight: 800;
|
|
font-size: 0.92rem;
|
|
}
|
|
.mpa-head__id {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.mpa-head__name {
|
|
font-size: 0.98rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.mpa-head__meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-head__sep { opacity: 0.4; }
|
|
.mpa-head__tag {
|
|
font-size: 0.66rem !important;
|
|
margin-left: 2px;
|
|
}
|
|
.mpa-head__risk {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
background: rgba(220, 38, 38, 0.12);
|
|
color: rgb(220, 38, 38);
|
|
border: 1px solid rgba(220, 38, 38, 0.35);
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
}
|
|
.mpa-head__risk > i { font-size: 0.66rem; }
|
|
|
|
.mpa-page__actions { display: flex; align-items: center; gap: 6px; }
|
|
.mpa-act-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: 8px;
|
|
cursor: pointer;
|
|
transition: background-color 120ms ease;
|
|
}
|
|
.mpa-act-btn:hover { background: var(--m-bg-soft-hover); }
|
|
.mpa-act-btn > i { font-size: 0.85rem; }
|
|
|
|
.mpa-close {
|
|
width: 32px;
|
|
height: 32px;
|
|
display: grid;
|
|
place-items: center;
|
|
background: transparent;
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text-muted);
|
|
border-radius: 8px;
|
|
cursor: pointer;
|
|
transition: background-color 120ms ease, color 120ms ease;
|
|
}
|
|
.mpa-close:hover { background: var(--m-bg-soft-hover); color: var(--m-text); }
|
|
|
|
|
|
.mpa-menu-btn {
|
|
display: none;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 6px 10px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 8px;
|
|
color: var(--m-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
transition: background-color 140ms ease;
|
|
}
|
|
.mpa-menu-btn:hover { background: var(--m-bg-soft-hover); }
|
|
.mpa-menu-btn > i { font-size: 0.78rem; color: var(--p-primary-color); }
|
|
|
|
.mpa-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;
|
|
}
|
|
.mpa-subheader--risk {
|
|
background: rgba(220, 38, 38, 0.10);
|
|
border-bottom-color: rgba(220, 38, 38, 0.30);
|
|
color: rgb(180, 33, 33);
|
|
}
|
|
.mpa-subheader__icon { font-size: 0.92rem; flex-shrink: 0; margin-top: 1px; }
|
|
.mpa-subheader__text { flex: 1; min-width: 0; }
|
|
.mpa-subheader__text strong { font-weight: 700; color: var(--m-text); }
|
|
.mpa-subheader--risk .mpa-subheader__text strong { color: rgb(180, 33, 33); }
|
|
|
|
/* ═══════ Body 2-col ═══════ */
|
|
.mpa-body {
|
|
flex: 1;
|
|
min-height: 0;
|
|
display: flex;
|
|
overflow: hidden;
|
|
}
|
|
.mpa-side {
|
|
width: 320px;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
background: var(--m-bg-soft);
|
|
border-right: 1px solid var(--m-border);
|
|
overflow: hidden;
|
|
}
|
|
.mpa-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;
|
|
}
|
|
.mpa-side__scroll::-webkit-scrollbar { width: 5px; }
|
|
.mpa-side__scroll::-webkit-scrollbar-thumb {
|
|
background: var(--m-border-strong);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
/* Cards da sidebar (.mpa-w) NUNCA podem encolher — caso contrario o flex
|
|
shrink default (1) corta conteudo quando ha muitos cards stacked
|
|
(Acoes + Nav 7 tabs + Sub-nav Perfil 6 + Vinculos). Combinado com o
|
|
overflow:hidden do .mpa-w (necessario pro radius), cards encolhidos
|
|
perdem itens da lista interna. flex-shrink: 0 + altura auto garante
|
|
que cada card cresce ate o tamanho real do conteudo, e o
|
|
.mpa-side__scroll lida com overflow via scroll vertical. */
|
|
.mpa-side__scroll > .mpa-w {
|
|
flex-shrink: 0;
|
|
height: auto;
|
|
}
|
|
|
|
/* Wrapper de cada aba do main: precisa de display:flex + gap senao os
|
|
filhos (KPIs grid + cards + panels) ficam colados. .mpa-main ja eh
|
|
flex-col com gap, mas como cada aba envolve seus elementos num div
|
|
.mpa-tab, ESSE div precisa replicar o spacing. */
|
|
.mpa-tab {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
|
|
.mpa-cfg-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
width: calc(100% - 24px);
|
|
margin: 12px 12px 0;
|
|
padding: 10px 12px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 9px;
|
|
color: var(--m-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.84rem;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
transition: background-color 120ms ease, border-color 120ms ease, color 120ms ease;
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-cfg-btn:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mpa-cfg-btn--back:hover {
|
|
background: color-mix(in srgb, var(--p-primary-color) 8%, var(--m-bg-medium));
|
|
border-color: color-mix(in srgb, var(--p-primary-color) 30%, transparent);
|
|
}
|
|
.mpa-cfg-btn > i:first-child {
|
|
color: var(--p-primary-color);
|
|
font-size: 0.92rem;
|
|
}
|
|
.mpa-cfg-btn > span { flex: 1; }
|
|
.mpa-main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
min-height: 0;
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: 16px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--m-border-strong) transparent;
|
|
}
|
|
.mpa-main::-webkit-scrollbar { width: 5px; }
|
|
.mpa-main::-webkit-scrollbar-thumb {
|
|
background: var(--m-border-strong);
|
|
border-radius: 3px;
|
|
}
|
|
|
|
@media (min-width: 1024px) {
|
|
.mpa-main {
|
|
max-width: 1100px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
.mpa-main > .mpa-w {
|
|
height: auto;
|
|
flex-shrink: 0;
|
|
}
|
|
}
|
|
|
|
/* ═══════ Card-base ═══════ */
|
|
.mpa-w {
|
|
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);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mpa-w--side {
|
|
background: var(--m-bg-medium);
|
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.16);
|
|
}
|
|
.mpa-w__head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 12px 14px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
.mpa-w__icon {
|
|
width: 36px;
|
|
height: 36px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 9px;
|
|
flex-shrink: 0;
|
|
background: color-mix(in srgb, var(--p-primary-color) 15%, transparent);
|
|
color: var(--p-primary-color);
|
|
}
|
|
.mpa-w__icon > i { font-size: 0.95rem; }
|
|
.mpa-w__icon--blue { background: color-mix(in srgb, #3b82f6 15%, transparent); color: #3b82f6; }
|
|
.mpa-w__icon--cyan { background: color-mix(in srgb, #06b6d4 15%, transparent); color: #06b6d4; }
|
|
.mpa-w__icon--green { background: color-mix(in srgb, #10b981 15%, transparent); color: #10b981; }
|
|
.mpa-w__icon--orange { background: color-mix(in srgb, #f97316 15%, transparent); color: #f97316; }
|
|
.mpa-w__title { flex: 1; min-width: 0; }
|
|
.mpa-w__title-text {
|
|
font-size: 0.92rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
line-height: 1.2;
|
|
}
|
|
.mpa-w__sub {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
margin-top: 2px;
|
|
line-height: 1.3;
|
|
}
|
|
.mpa-w__body {
|
|
padding: 14px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.mpa-w__body--chips {
|
|
flex-direction: row;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
|
|
/* ═══════ Quick-action btn (sidebar) ═══════ */
|
|
.mpa-quick-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 9px 12px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.82rem;
|
|
font-weight: 600;
|
|
text-align: left;
|
|
transition: background-color 120ms ease, border-color 120ms ease;
|
|
}
|
|
.mpa-quick-btn:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mpa-quick-btn > i { font-size: 0.92rem; flex-shrink: 0; }
|
|
|
|
/* ═══════ Tab btn (sidebar nav) ═══════ */
|
|
.mpa-tab-btn {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 8px 10px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
text-align: left;
|
|
transition: background-color 120ms ease, border-color 120ms ease;
|
|
}
|
|
.mpa-tab-btn:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mpa-tab-btn.is-active {
|
|
background: color-mix(in srgb, var(--p-primary-color) 14%, transparent);
|
|
border-color: color-mix(in srgb, var(--p-primary-color) 38%, transparent);
|
|
color: var(--p-primary-color);
|
|
font-weight: 700;
|
|
}
|
|
.mpa-tab-btn__icon {
|
|
width: 28px;
|
|
height: 28px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 7px;
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-tab-btn__icon > i { font-size: 0.78rem; }
|
|
.mpa-tab-btn__label {
|
|
flex: 1;
|
|
min-width: 0;
|
|
font-size: 0.82rem;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
}
|
|
.mpa-tab-btn__chev {
|
|
color: var(--m-text-muted);
|
|
font-size: 0.7rem;
|
|
opacity: 0.5;
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-tab-btn--sub {
|
|
padding: 6px 10px;
|
|
background: transparent;
|
|
border: 1px solid transparent;
|
|
}
|
|
.mpa-tab-btn--sub:hover {
|
|
background: var(--m-bg-soft);
|
|
border-color: var(--m-border);
|
|
}
|
|
.mpa-tab-btn--sub.is-active {
|
|
background: color-mix(in srgb, var(--p-primary-color) 10%, transparent);
|
|
}
|
|
.mpa-tab-btn__sub-icon {
|
|
color: var(--m-text-muted);
|
|
font-size: 0.78rem;
|
|
flex-shrink: 0;
|
|
width: 18px;
|
|
text-align: center;
|
|
}
|
|
|
|
/* ═══════ Chips (grupos / tags) ═══════ */
|
|
.mpa-chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
padding: 3px 10px;
|
|
border-radius: 999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-chip > i { font-size: 0.66rem; }
|
|
.mpa-chip--group {
|
|
background: color-mix(in srgb, var(--p-primary-color) 12%, transparent);
|
|
color: var(--p-primary-color);
|
|
border-color: color-mix(in srgb, var(--p-primary-color) 28%, transparent);
|
|
}
|
|
|
|
/* ═══════ KPIs (Visao Geral Fase 2 — 4 cards ricos) ═══════ */
|
|
.mpa-kpis {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
|
|
gap: 12px;
|
|
}
|
|
.mpa-kpi {
|
|
position: relative;
|
|
padding: 14px 14px 12px;
|
|
border-radius: 12px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid color-mix(in srgb, var(--c, #6366f1) 28%, var(--m-border));
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
min-height: 110px;
|
|
}
|
|
.mpa-kpi::before {
|
|
content: '';
|
|
position: absolute;
|
|
inset: 0;
|
|
background: linear-gradient(135deg, var(--c, #6366f1) 0%, transparent 65%);
|
|
opacity: 0.10;
|
|
pointer-events: none;
|
|
}
|
|
.mpa-kpi__num {
|
|
position: absolute;
|
|
top: 8px;
|
|
right: 14px;
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-size: 0.66rem;
|
|
color: var(--c, #6366f1);
|
|
opacity: 0.45;
|
|
font-weight: 800;
|
|
pointer-events: none;
|
|
}
|
|
.mpa-kpi__head {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 6px;
|
|
}
|
|
.mpa-kpi__icon {
|
|
width: 32px;
|
|
height: 32px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 8px;
|
|
background: color-mix(in srgb, var(--c, #6366f1) 16%, transparent);
|
|
color: var(--c, #6366f1);
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-kpi__icon > i { font-size: 0.85rem; }
|
|
.mpa-kpi__tag {
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-kpi__big {
|
|
font-size: 1.6rem;
|
|
font-weight: 800;
|
|
color: var(--m-text);
|
|
line-height: 1.05;
|
|
letter-spacing: -0.02em;
|
|
margin-top: auto;
|
|
}
|
|
.mpa-kpi__big--small { font-size: 1.1rem; }
|
|
.mpa-kpi__cap {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
margin-top: 4px;
|
|
line-height: 1.4;
|
|
}
|
|
.mpa-kpi__cap-warn {
|
|
color: rgb(245, 158, 11);
|
|
font-weight: 600;
|
|
}
|
|
.mpa-kpi__cap-dim {
|
|
opacity: 0.7;
|
|
}
|
|
|
|
/* ═══════ Grid 2-col (Timeline + Mensagens/Notas) ═══════ */
|
|
.mpa-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
}
|
|
@media (min-width: 900px) {
|
|
.mpa-grid {
|
|
grid-template-columns: 1.4fr 1fr;
|
|
}
|
|
}
|
|
.mpa-grid__col {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ═══════ Painel base ═══════ */
|
|
.mpa-panel {
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
border-radius: 12px;
|
|
overflow: hidden;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mpa-panel__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
padding: 12px 14px;
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
.mpa-panel__title {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
font-size: 0.88rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-panel__title > i {
|
|
color: var(--p-primary-color);
|
|
font-size: 0.85rem;
|
|
}
|
|
.mpa-panel__badge {
|
|
background: color-mix(in srgb, var(--p-primary-color) 16%, transparent);
|
|
color: var(--p-primary-color);
|
|
border-radius: 999px;
|
|
padding: 2px 10px;
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
}
|
|
.mpa-panel__body {
|
|
padding: 12px 14px 14px;
|
|
flex: 1;
|
|
min-height: 0;
|
|
}
|
|
|
|
/* ═══════ Empty state ═══════ */
|
|
.mpa-empty {
|
|
text-align: center;
|
|
color: var(--m-text-muted);
|
|
padding: 16px 0;
|
|
font-size: 0.82rem;
|
|
}
|
|
.mpa-empty--rich {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
gap: 6px;
|
|
padding: 24px 12px;
|
|
}
|
|
.mpa-empty__icon {
|
|
width: 44px;
|
|
height: 44px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 50%;
|
|
background: var(--m-bg-medium);
|
|
color: var(--m-text-muted);
|
|
margin-bottom: 4px;
|
|
}
|
|
.mpa-empty__icon > i { font-size: 1rem; }
|
|
.mpa-empty__title {
|
|
font-size: 0.88rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-empty__sub {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.4;
|
|
max-width: 280px;
|
|
}
|
|
|
|
/* ═══════ Timeline (sessoes atendidas) ═══════ */
|
|
.mpa-timeline {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
position: relative;
|
|
}
|
|
.mpa-timeline::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 6px;
|
|
bottom: 6px;
|
|
left: 6px;
|
|
width: 1.5px;
|
|
background: var(--m-border);
|
|
}
|
|
.mpa-tl {
|
|
position: relative;
|
|
padding-left: 22px;
|
|
padding-bottom: 14px;
|
|
}
|
|
.mpa-tl:last-child { padding-bottom: 0; }
|
|
.mpa-tl__dot {
|
|
position: absolute;
|
|
top: 6px;
|
|
left: 1px;
|
|
width: 11px;
|
|
height: 11px;
|
|
border-radius: 50%;
|
|
background: var(--m-bg-medium);
|
|
border: 2px solid var(--m-text-muted);
|
|
}
|
|
.mpa-tl[data-status*="realiz"] .mpa-tl__dot,
|
|
.mpa-tl[data-status*="present"] .mpa-tl__dot { border-color: rgb(34, 197, 94); }
|
|
.mpa-tl[data-status*="falt"] .mpa-tl__dot { border-color: rgb(239, 68, 68); }
|
|
.mpa-tl[data-status*="cancel"] .mpa-tl__dot,
|
|
.mpa-tl[data-status*="remarc"] .mpa-tl__dot { border-color: rgb(245, 158, 11); }
|
|
.mpa-tl__main {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.mpa-tl__top {
|
|
display: flex;
|
|
align-items: baseline;
|
|
gap: 8px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.mpa-tl__when {
|
|
font-size: 0.82rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-tl__rel {
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-tl__row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.mpa-tl__tag {
|
|
font-size: 0.66rem !important;
|
|
}
|
|
.mpa-tl__chip {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 3px;
|
|
padding: 2px 8px;
|
|
border-radius: 999px;
|
|
background: var(--m-bg-medium);
|
|
color: var(--m-text);
|
|
font-size: 0.66rem;
|
|
font-weight: 600;
|
|
}
|
|
.mpa-tl__chip > i { font-size: 0.62rem; opacity: 0.7; }
|
|
.mpa-tl__chip--dim {
|
|
background: transparent;
|
|
color: var(--m-text-muted);
|
|
border: 1px solid var(--m-border);
|
|
}
|
|
.mpa-tl__note {
|
|
font-size: 0.78rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.4;
|
|
padding: 6px 8px;
|
|
border-left: 2px solid var(--m-border);
|
|
background: var(--m-bg-medium);
|
|
border-radius: 4px;
|
|
margin-top: 4px;
|
|
}
|
|
|
|
/* ═══════ Mensagens recentes ═══════ */
|
|
.mpa-msgs {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
}
|
|
.mpa-msg {
|
|
padding: 8px 10px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
border-left-width: 3px;
|
|
}
|
|
.mpa-msg--in {
|
|
border-left-color: rgb(34, 197, 94);
|
|
}
|
|
.mpa-msg--out {
|
|
border-left-color: rgb(96, 165, 250);
|
|
}
|
|
.mpa-msg__meta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 0.66rem;
|
|
color: var(--m-text-muted);
|
|
font-weight: 600;
|
|
margin-bottom: 4px;
|
|
}
|
|
.mpa-msg__meta > i { font-size: 0.62rem; opacity: 0.7; }
|
|
.mpa-msg__sep { opacity: 0.4; }
|
|
.mpa-msg__body {
|
|
font-size: 0.82rem;
|
|
color: var(--m-text);
|
|
line-height: 1.45;
|
|
word-break: break-word;
|
|
overflow: hidden;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 3;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
|
|
/* ═══════ Tab Perfil — fields stacked grid ═══════ */
|
|
.mpa-fields-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
gap: 12px;
|
|
}
|
|
@media (min-width: 720px) {
|
|
.mpa-fields-grid {
|
|
grid-template-columns: 1fr 1fr;
|
|
}
|
|
}
|
|
.mpa-fields-stack {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
min-width: 0;
|
|
}
|
|
|
|
.mpa-fields {
|
|
border-radius: 10px;
|
|
border: 1px solid var(--m-border);
|
|
background: var(--m-bg-medium);
|
|
padding: 12px 14px;
|
|
}
|
|
.mpa-fields--full {
|
|
margin-top: 12px;
|
|
grid-column: 1 / -1;
|
|
}
|
|
.mpa-fields__label {
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--m-text-muted);
|
|
margin-bottom: 8px;
|
|
opacity: 0.85;
|
|
}
|
|
.mpa-fields__textblock {
|
|
font-size: 0.85rem;
|
|
color: var(--m-text);
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
.mpa-fields__textblock--min { min-height: 3.5rem; }
|
|
.mpa-fields__lock-hint {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
opacity: 0.75;
|
|
margin-bottom: 10px;
|
|
}
|
|
.mpa-fields__lock-hint > i { font-size: 0.66rem; }
|
|
|
|
/* Field row generico (k = label esq, v = valor dir) */
|
|
.mpa-field-row {
|
|
display: flex;
|
|
align-items: baseline;
|
|
justify-content: space-between;
|
|
gap: 12px;
|
|
padding: 6px 0;
|
|
border-bottom: 1px solid var(--m-border);
|
|
font-size: 0.82rem;
|
|
}
|
|
.mpa-field-row--last { border-bottom: none; }
|
|
.mpa-field-row--align-start { align-items: flex-start; }
|
|
.mpa-field-row__k {
|
|
color: var(--m-text-muted);
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-field-row__v {
|
|
color: var(--m-text);
|
|
font-weight: 600;
|
|
text-align: right;
|
|
word-break: break-word;
|
|
}
|
|
.mpa-field-row__v--mono {
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-size: 0.78rem;
|
|
}
|
|
.mpa-field-row__v--link {
|
|
color: var(--p-primary-color);
|
|
text-decoration: none;
|
|
}
|
|
.mpa-field-row__v--link:hover { text-decoration: underline; }
|
|
.mpa-field-row__v--truncate {
|
|
max-width: 200px;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
display: inline-block;
|
|
}
|
|
.mpa-field-row__v-dim {
|
|
color: var(--m-text-muted);
|
|
font-weight: 400;
|
|
margin-left: 4px;
|
|
}
|
|
.mpa-field-row__chips {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px;
|
|
justify-content: flex-end;
|
|
}
|
|
|
|
/* Grid 2-col pra blocos de endereco/dados (8 fields em 2 colunas) */
|
|
.mpa-field-grid-2 {
|
|
display: grid;
|
|
grid-template-columns: 1fr;
|
|
}
|
|
@media (min-width: 600px) {
|
|
.mpa-field-grid-2 {
|
|
grid-template-columns: 1fr 1fr;
|
|
column-gap: 18px;
|
|
}
|
|
.mpa-field-grid-2 > .mpa-field-row {
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
/* Ultima da esq tira borda quando ultima do grid; --last e atribuida no template */
|
|
.mpa-field-grid-2 > .mpa-field-row--last { border-bottom: none; }
|
|
}
|
|
|
|
/* Bloco textual full-width dentro de .mpa-fields (ex: Observacao Responsavel) */
|
|
.mpa-field-block {
|
|
padding: 10px 0;
|
|
border-top: 1px solid var(--m-border);
|
|
}
|
|
.mpa-field-block--last { border-bottom: none; }
|
|
.mpa-field-block__label {
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
color: var(--m-text-muted);
|
|
margin-bottom: 4px;
|
|
}
|
|
.mpa-field-block__text {
|
|
font-size: 0.82rem;
|
|
color: var(--m-text);
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
}
|
|
|
|
/* Mini lista de sessoes (panel 6 da aba Perfil) */
|
|
.mpa-sess-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 8px;
|
|
max-height: 360px;
|
|
overflow-y: auto;
|
|
padding-right: 2px;
|
|
}
|
|
.mpa-sess {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 10px 12px;
|
|
border-radius: 10px;
|
|
border: 1px solid var(--m-border);
|
|
background: var(--m-bg-medium);
|
|
}
|
|
.mpa-sess__main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.mpa-sess__title {
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
line-height: 1.2;
|
|
margin-bottom: 4px;
|
|
}
|
|
.mpa-sess__meta {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 4px 10px;
|
|
font-size: 0.72rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-sess__meta > span {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
.mpa-sess__meta > span > i { font-size: 0.66rem; opacity: 0.6; }
|
|
.mpa-sess__obs {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.4;
|
|
margin-top: 4px;
|
|
overflow: hidden;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
.mpa-sess__tag {
|
|
flex-shrink: 0;
|
|
font-size: 0.66rem !important;
|
|
}
|
|
|
|
/* ═══════ Tab Conversas (Fase 7) — CTA pra drawer ═══════ */
|
|
.mpa-conv-cta {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
padding: 12px 14px;
|
|
border-radius: 12px;
|
|
background: color-mix(in srgb, #25d366 8%, transparent);
|
|
border: 1px solid color-mix(in srgb, #25d366 28%, transparent);
|
|
flex-wrap: wrap;
|
|
}
|
|
.mpa-conv-cta__btn {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 8px 16px;
|
|
border-radius: 999px;
|
|
background: #25d366;
|
|
color: #fff;
|
|
border: none;
|
|
font-family: inherit;
|
|
font-size: 0.82rem;
|
|
font-weight: 700;
|
|
cursor: pointer;
|
|
transition: background-color 120ms ease, transform 120ms ease;
|
|
}
|
|
.mpa-conv-cta__btn:hover {
|
|
background: color-mix(in srgb, #25d366 88%, black);
|
|
transform: translateY(-1px);
|
|
}
|
|
.mpa-conv-cta__btn > i { font-size: 0.92rem; }
|
|
.mpa-conv-cta__hint {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
|
|
/* ═══════ Embed wrapper (DocumentsListPage / PatientConversationsTab) ═══════
|
|
O componente embedded ja vem com seu proprio padding/scroll. So damos
|
|
o card-base do Melissa (border-radius, fundo) e zeramos o body padding
|
|
pra ele preencher tudo. */
|
|
.mpa-embed {
|
|
background: var(--m-bg-soft);
|
|
overflow: hidden;
|
|
}
|
|
.mpa-embed__body {
|
|
padding: 0;
|
|
gap: 0;
|
|
}
|
|
|
|
/* ═══════ Tab Financeiro (Fase 6) ═══════ */
|
|
.mpa-fin__head-actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
}
|
|
.mpa-icon-btn-sm {
|
|
width: 26px;
|
|
height: 26px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 6px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--p-primary-color);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: background-color 120ms ease, border-color 120ms ease;
|
|
}
|
|
.mpa-icon-btn-sm:hover {
|
|
background: color-mix(in srgb, var(--p-primary-color) 14%, transparent);
|
|
border-color: color-mix(in srgb, var(--p-primary-color) 38%, transparent);
|
|
}
|
|
.mpa-icon-btn-sm > i { font-size: 0.78rem; }
|
|
|
|
/* Quick btn variant CTA pra empty state */
|
|
.mpa-quick-btn--cta {
|
|
margin: 12px auto 0;
|
|
padding: 10px 16px;
|
|
background: var(--m-bg-medium);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
|
|
/* Tabela financeira */
|
|
.mpa-fin__table {
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mpa-fin__row {
|
|
display: grid;
|
|
grid-template-columns: 110px 1fr 90px 100px 100px 36px;
|
|
gap: 12px;
|
|
align-items: center;
|
|
padding: 10px 14px;
|
|
border-top: 1px solid var(--m-border);
|
|
font-size: 0.82rem;
|
|
border-left: 3px solid transparent;
|
|
transition: background-color 120ms ease;
|
|
}
|
|
.mpa-fin__row:first-child { border-top: none; }
|
|
.mpa-fin__row:hover:not(.mpa-fin__row--head) { background: var(--m-bg-medium); }
|
|
.mpa-fin__row[data-status="pago"] { border-left-color: rgb(34, 197, 94); }
|
|
.mpa-fin__row[data-status="vencido"] { border-left-color: rgb(239, 68, 68); }
|
|
.mpa-fin__row[data-status="pendente"] { border-left-color: rgb(96, 165, 250); }
|
|
|
|
.mpa-fin__row--head {
|
|
background: var(--m-bg-medium);
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
color: var(--m-text-muted);
|
|
border-left-color: transparent !important;
|
|
}
|
|
|
|
.mpa-fin__date {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.mpa-fin__date-main {
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-size: 0.78rem;
|
|
}
|
|
.mpa-fin__date-rel {
|
|
font-size: 0.66rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
|
|
.mpa-fin__desc {
|
|
color: var(--m-text);
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
white-space: nowrap;
|
|
min-width: 0;
|
|
}
|
|
.mpa-fin__method {
|
|
color: var(--m-text-muted);
|
|
font-size: 0.74rem;
|
|
}
|
|
.mpa-fin__amount {
|
|
font-weight: 800;
|
|
color: var(--m-text);
|
|
text-align: right;
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-size: 0.82rem;
|
|
}
|
|
|
|
.mpa-fin__col-amount { text-align: right; }
|
|
.mpa-fin__col-method,
|
|
.mpa-fin__col-status,
|
|
.mpa-fin__col-action { text-align: center; }
|
|
|
|
.mpa-fin__status {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 3px 10px;
|
|
border-radius: 999px;
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
}
|
|
.mpa-fin__status-dot {
|
|
width: 6px;
|
|
height: 6px;
|
|
border-radius: 50%;
|
|
background: currentColor;
|
|
}
|
|
.mpa-fin__status[data-status="pago"] {
|
|
background: color-mix(in srgb, rgb(34, 197, 94) 12%, transparent);
|
|
color: rgb(34, 197, 94);
|
|
border-color: color-mix(in srgb, rgb(34, 197, 94) 35%, transparent);
|
|
}
|
|
.mpa-fin__status[data-status="vencido"] {
|
|
background: color-mix(in srgb, rgb(239, 68, 68) 12%, transparent);
|
|
color: rgb(239, 68, 68);
|
|
border-color: color-mix(in srgb, rgb(239, 68, 68) 35%, transparent);
|
|
}
|
|
.mpa-fin__status[data-status="pendente"] {
|
|
background: color-mix(in srgb, rgb(96, 165, 250) 12%, transparent);
|
|
color: rgb(96, 165, 250);
|
|
border-color: color-mix(in srgb, rgb(96, 165, 250) 35%, transparent);
|
|
}
|
|
|
|
.mpa-fin__action {
|
|
display: flex;
|
|
justify-content: center;
|
|
}
|
|
|
|
/* Mobile: stack tabela em cards 2-col */
|
|
@media (max-width: 720px) {
|
|
.mpa-fin__row--head { display: none; }
|
|
.mpa-fin__row {
|
|
grid-template-columns: 1fr auto;
|
|
grid-template-rows: auto auto auto;
|
|
gap: 4px 10px;
|
|
padding: 12px 14px;
|
|
}
|
|
.mpa-fin__date {
|
|
grid-row: 1;
|
|
grid-column: 1;
|
|
}
|
|
.mpa-fin__amount {
|
|
grid-row: 1;
|
|
grid-column: 2;
|
|
text-align: right;
|
|
}
|
|
.mpa-fin__desc {
|
|
grid-row: 2;
|
|
grid-column: 1 / -1;
|
|
font-size: 0.78rem;
|
|
}
|
|
.mpa-fin__method {
|
|
grid-row: 3;
|
|
grid-column: 1;
|
|
}
|
|
.mpa-fin__status-cell {
|
|
grid-row: 3;
|
|
grid-column: 2;
|
|
text-align: right;
|
|
}
|
|
.mpa-fin__action {
|
|
grid-row: 4;
|
|
grid-column: 1 / -1;
|
|
justify-content: flex-end;
|
|
}
|
|
}
|
|
|
|
/* ═══════ Tab Agenda (Fase 5) ═══════ */
|
|
.mpa-ag__group + .mpa-ag__group { margin-top: 10px; }
|
|
.mpa-ag__list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
.mpa-ag__item {
|
|
display: grid;
|
|
grid-template-columns: 64px 1fr auto;
|
|
gap: 12px;
|
|
align-items: center;
|
|
padding: 10px 14px;
|
|
border-top: 1px solid var(--m-border);
|
|
border-left: 3px solid transparent;
|
|
transition: background-color 120ms ease;
|
|
}
|
|
.mpa-ag__item:first-child { border-top: none; }
|
|
.mpa-ag__item:hover { background: var(--m-bg-medium); }
|
|
.mpa-ag__item[data-status*="realiz"],
|
|
.mpa-ag__item[data-status*="present"] { border-left-color: rgb(34, 197, 94); }
|
|
.mpa-ag__item[data-status*="falt"] { border-left-color: rgb(239, 68, 68); }
|
|
.mpa-ag__item[data-status*="cancel"],
|
|
.mpa-ag__item[data-status*="remarc"] { border-left-color: rgb(245, 158, 11); }
|
|
.mpa-ag__item[data-status*="agendado"] { border-left-color: rgb(96, 165, 250); }
|
|
|
|
/* Coluna data (esquerda) */
|
|
.mpa-ag__date {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 6px 8px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
min-width: 56px;
|
|
}
|
|
.mpa-ag__date-dow {
|
|
font-size: 0.62rem;
|
|
color: var(--m-text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
line-height: 1;
|
|
}
|
|
.mpa-ag__date-day {
|
|
font-size: 1.1rem;
|
|
font-weight: 800;
|
|
color: var(--m-text);
|
|
line-height: 1.05;
|
|
margin: 2px 0;
|
|
letter-spacing: -0.02em;
|
|
}
|
|
.mpa-ag__date-time {
|
|
font-size: 0.66rem;
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
color: var(--m-text-muted);
|
|
font-weight: 600;
|
|
line-height: 1;
|
|
}
|
|
|
|
/* Coluna main (centro) */
|
|
.mpa-ag__main {
|
|
flex: 1;
|
|
min-width: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
}
|
|
.mpa-ag__top {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.mpa-ag__tag { font-size: 0.66rem !important; }
|
|
.mpa-ag__rel {
|
|
margin-left: auto;
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-ag__title {
|
|
font-size: 0.85rem;
|
|
font-weight: 600;
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-ag__note {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.4;
|
|
overflow: hidden;
|
|
display: -webkit-box;
|
|
-webkit-line-clamp: 2;
|
|
-webkit-box-orient: vertical;
|
|
}
|
|
|
|
/* Coluna actions (direita) */
|
|
.mpa-ag__actions {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-ag__act {
|
|
width: 30px;
|
|
height: 30px;
|
|
display: grid;
|
|
place-items: center;
|
|
border-radius: 7px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text-muted);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
transition: background-color 120ms ease, color 120ms ease, border-color 120ms ease;
|
|
}
|
|
.mpa-ag__act > i { font-size: 0.85rem; }
|
|
.mpa-ag__act:hover:not(:disabled) {
|
|
background: var(--m-bg-soft-hover);
|
|
color: var(--m-text);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mpa-ag__act:disabled {
|
|
opacity: 0.4;
|
|
cursor: not-allowed;
|
|
}
|
|
.mpa-ag__act--ok:hover:not(:disabled) {
|
|
background: rgba(34, 197, 94, 0.12);
|
|
color: rgb(34, 197, 94);
|
|
border-color: rgba(34, 197, 94, 0.40);
|
|
}
|
|
.mpa-ag__act--warn:hover:not(:disabled) {
|
|
background: rgba(245, 158, 11, 0.12);
|
|
color: rgb(245, 158, 11);
|
|
border-color: rgba(245, 158, 11, 0.40);
|
|
}
|
|
.mpa-ag__act--danger:hover:not(:disabled) {
|
|
background: rgba(239, 68, 68, 0.12);
|
|
color: rgb(239, 68, 68);
|
|
border-color: rgba(239, 68, 68, 0.40);
|
|
}
|
|
|
|
/* Mobile: stack date + main em coluna; actions vai pra baixo */
|
|
@media (max-width: 600px) {
|
|
.mpa-ag__item {
|
|
grid-template-columns: 56px 1fr;
|
|
grid-template-rows: auto auto;
|
|
}
|
|
.mpa-ag__actions {
|
|
grid-column: 1 / -1;
|
|
justify-content: flex-end;
|
|
}
|
|
.mpa-ag__rel {
|
|
flex-basis: 100%;
|
|
margin-left: 0;
|
|
text-align: left;
|
|
}
|
|
}
|
|
|
|
/* ═══════ Tab Prontuario (Fase 4 MVP) ═══════ */
|
|
.mpa-pron-hint {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 12px 14px;
|
|
border-radius: 10px;
|
|
background: color-mix(in srgb, #06b6d4 9%, transparent);
|
|
border: 1px solid color-mix(in srgb, #06b6d4 28%, transparent);
|
|
color: var(--m-text);
|
|
font-size: 0.78rem;
|
|
line-height: 1.5;
|
|
}
|
|
.mpa-pron-hint > i {
|
|
color: #06b6d4;
|
|
font-size: 1rem;
|
|
margin-top: 2px;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.mpa-pron-stats {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
gap: 8px;
|
|
}
|
|
.mpa-pron-stat {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
padding: 10px 12px;
|
|
border-radius: 10px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
border-left: 3px solid var(--c, var(--p-primary-color));
|
|
}
|
|
.mpa-pron-stat > i {
|
|
color: var(--c, var(--p-primary-color));
|
|
font-size: 1.05rem;
|
|
flex-shrink: 0;
|
|
}
|
|
.mpa-pron-stat__value {
|
|
font-size: 1.05rem;
|
|
font-weight: 800;
|
|
color: var(--m-text);
|
|
line-height: 1.1;
|
|
}
|
|
.mpa-pron-stat__label {
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
margin-top: 2px;
|
|
}
|
|
|
|
.mpa-pron-filters {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 6px;
|
|
}
|
|
.mpa-pron-filter {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
padding: 6px 12px;
|
|
border-radius: 999px;
|
|
background: var(--m-bg-soft);
|
|
border: 1px solid var(--m-border);
|
|
color: var(--m-text);
|
|
cursor: pointer;
|
|
font-family: inherit;
|
|
font-size: 0.78rem;
|
|
font-weight: 600;
|
|
transition: all 120ms ease;
|
|
}
|
|
.mpa-pron-filter > i { font-size: 0.7rem; }
|
|
.mpa-pron-filter:hover {
|
|
background: var(--m-bg-soft-hover);
|
|
border-color: var(--m-border-strong);
|
|
}
|
|
.mpa-pron-filter.is-active {
|
|
background: var(--p-primary-color);
|
|
border-color: var(--p-primary-color);
|
|
color: var(--p-primary-contrast-color, #fff);
|
|
}
|
|
|
|
.mpa-pron-list {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.mpa-pron-item {
|
|
border-radius: 12px;
|
|
border: 1px solid var(--m-border);
|
|
background: var(--m-bg-soft);
|
|
padding: 12px 14px;
|
|
border-left: 3px solid var(--m-text-muted);
|
|
transition: border-color 120ms ease;
|
|
}
|
|
.mpa-pron-item[data-status*="realiz"],
|
|
.mpa-pron-item[data-status*="present"] { border-left-color: rgb(34, 197, 94); }
|
|
.mpa-pron-item[data-status*="falt"] { border-left-color: rgb(239, 68, 68); }
|
|
.mpa-pron-item[data-status*="cancel"],
|
|
.mpa-pron-item[data-status*="remarc"] { border-left-color: rgb(245, 158, 11); }
|
|
.mpa-pron-item__head {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
justify-content: space-between;
|
|
gap: 10px;
|
|
flex-wrap: wrap;
|
|
margin-bottom: 6px;
|
|
}
|
|
.mpa-pron-item__when {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
min-width: 0;
|
|
}
|
|
.mpa-pron-item__date {
|
|
font-size: 0.85rem;
|
|
font-weight: 700;
|
|
color: var(--m-text);
|
|
}
|
|
.mpa-pron-item__rel {
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
}
|
|
.mpa-pron-item__chips {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
flex-wrap: wrap;
|
|
}
|
|
.mpa-pron-item__tag { font-size: 0.66rem !important; }
|
|
.mpa-pron-item__title {
|
|
font-size: 0.82rem;
|
|
font-weight: 600;
|
|
color: var(--m-text);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.mpa-pron-item__evol {
|
|
margin-top: 8px;
|
|
padding: 10px 12px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-medium);
|
|
border-left: 2px solid var(--p-primary-color);
|
|
}
|
|
.mpa-pron-item__evol-label {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 5px;
|
|
font-size: 0.66rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--p-primary-color);
|
|
margin-bottom: 4px;
|
|
}
|
|
.mpa-pron-item__evol-label > i { font-size: 0.62rem; }
|
|
.mpa-pron-item__evol-text {
|
|
font-size: 0.85rem;
|
|
color: var(--m-text);
|
|
line-height: 1.55;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
.mpa-pron-item__noevol {
|
|
margin-top: 6px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
font-size: 0.7rem;
|
|
color: var(--m-text-muted);
|
|
opacity: 0.65;
|
|
font-style: italic;
|
|
}
|
|
.mpa-pron-item__noevol > i { font-size: 0.5rem; }
|
|
|
|
.mpa-pron-roadmap {
|
|
margin-top: 6px;
|
|
border-style: dashed;
|
|
background: transparent;
|
|
}
|
|
.mpa-pron-roadmap__list {
|
|
list-style: none;
|
|
margin: 0;
|
|
padding: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
.mpa-pron-roadmap__list > li {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 10px;
|
|
padding: 8px 0;
|
|
border-bottom: 1px solid var(--m-border);
|
|
}
|
|
.mpa-pron-roadmap__list > li:last-child {
|
|
border-bottom: none;
|
|
padding-bottom: 0;
|
|
}
|
|
.mpa-pron-roadmap__list > li > i {
|
|
color: var(--p-primary-color);
|
|
font-size: 0.95rem;
|
|
margin-top: 2px;
|
|
flex-shrink: 0;
|
|
width: 24px;
|
|
text-align: center;
|
|
}
|
|
.mpa-pron-roadmap__list > li > div {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.mpa-pron-roadmap__list strong {
|
|
display: block;
|
|
font-size: 0.85rem;
|
|
color: var(--m-text);
|
|
margin-bottom: 2px;
|
|
}
|
|
.mpa-pron-roadmap__list span {
|
|
font-size: 0.74rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.4;
|
|
}
|
|
|
|
/* ═══════ Notas e observacoes ═══════ */
|
|
.mpa-notes {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 12px;
|
|
}
|
|
.mpa-note {
|
|
padding: 10px 12px;
|
|
border-radius: 8px;
|
|
background: var(--m-bg-medium);
|
|
border: 1px solid var(--m-border);
|
|
}
|
|
.mpa-note__label {
|
|
font-size: 0.7rem;
|
|
font-weight: 700;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
color: var(--m-text-muted);
|
|
margin-bottom: 4px;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
.mpa-note__label > i { font-size: 0.66rem; opacity: 0.7; }
|
|
.mpa-note__text {
|
|
font-size: 0.82rem;
|
|
color: var(--m-text);
|
|
line-height: 1.5;
|
|
white-space: pre-wrap;
|
|
word-break: break-word;
|
|
}
|
|
|
|
/* ═══════ Placeholder (em desenvolvimento) ═══════ */
|
|
.mpa-placeholder {
|
|
font-size: 0.85rem;
|
|
color: var(--m-text-muted);
|
|
line-height: 1.55;
|
|
padding: 6px 0;
|
|
}
|
|
.mpa-placeholder code {
|
|
font-family: 'JetBrains Mono', ui-monospace, monospace;
|
|
font-size: 0.78rem;
|
|
background: var(--m-bg-medium);
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
color: var(--p-primary-color);
|
|
}
|
|
|
|
/* ═══════ Erro state ═══════ */
|
|
.mpa-error {
|
|
display: flex;
|
|
align-items: flex-start;
|
|
gap: 12px;
|
|
padding: 14px 16px;
|
|
border-radius: 10px;
|
|
background: rgba(220, 38, 38, 0.08);
|
|
border: 1px solid rgba(220, 38, 38, 0.30);
|
|
color: rgb(180, 33, 33);
|
|
}
|
|
.mpa-error > i { font-size: 1.05rem; margin-top: 2px; flex-shrink: 0; }
|
|
.mpa-error__title {
|
|
font-size: 0.88rem;
|
|
font-weight: 700;
|
|
}
|
|
.mpa-error__detail {
|
|
font-size: 0.78rem;
|
|
margin-top: 2px;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
/* ═══════ Mobile drawer ═══════ */
|
|
.mpa-mobile-drawer {
|
|
position: fixed;
|
|
top: 0; left: 0;
|
|
height: 100vh;
|
|
height: 100dvh;
|
|
width: min(380px, 90vw);
|
|
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;
|
|
}
|
|
.mpa-mobile-drawer.is-open { transform: translateX(0); }
|
|
.mpa-mobile-drawer__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;
|
|
}
|
|
.mpa-mobile-drawer__scroll::-webkit-scrollbar { width: 5px; }
|
|
.mpa-mobile-drawer__scroll::-webkit-scrollbar-thumb {
|
|
background: var(--m-border-strong);
|
|
border-radius: 3px;
|
|
}
|
|
.mpa-mobile-drawer__scroll .mpa-side {
|
|
width: 100%;
|
|
border-right: none;
|
|
}
|
|
|
|
.mpa-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;
|
|
}
|
|
.mpa-drawer-fade-enter-active,
|
|
.mpa-drawer-fade-leave-active { transition: opacity 200ms ease; }
|
|
.mpa-drawer-fade-enter-from,
|
|
.mpa-drawer-fade-leave-to { opacity: 0; }
|
|
|
|
/* ═══════ Mobile (<1024px) ═══════ */
|
|
@media (max-width: 1023px) {
|
|
.mpa-body { flex-direction: column; padding: 0; }
|
|
.mpa-body > .mpa-side { display: none; }
|
|
.mpa-main { width: 100%; padding: 8px; }
|
|
.mpa-main .mpa-w {
|
|
height: auto;
|
|
flex: 0 0 auto;
|
|
align-self: stretch;
|
|
}
|
|
.mpa-page__title { gap: 8px; }
|
|
.mpa-head__avatar { width: 36px; height: 36px; }
|
|
.mpa-head__name { font-size: 0.88rem; }
|
|
.mpa-head__meta { font-size: 0.7rem; }
|
|
.mpa-menu-btn { display: inline-flex; }
|
|
.mpa-act-btn:nth-child(2) { display: none; }
|
|
.mpa-kpis { grid-template-columns: 1fr 1fr; }
|
|
}
|
|
</style>
|