MelissaPaciente Fase 2: Tab Visao Geral completa (4 KPIs + timeline + msgs + notas)

Reescreveu o placeholder da aba Visao Geral por uma versao 1:1 do
PatientProntuario.vue legado, com estilo Melissa nativo e dados
alimentados pelos composables criados na Fase 1.

NOVO: src/features/patients/utils/patientFormatters.js (~165L)
- Helpers compartilhaveis extraidos do PatientProntuario:
  parseDateLoose, fmtDateBR, fmtDateTimeBR, fmtCurrency, fmtRelative
  (pt-br: "agora"/"ha 5 min"/"em 2 dias"/"ha 3 sem"), sessionDuration,
  calcAge.
- STATUS_LABEL e STATUS_SEVERITY pra mapear status de sessao (cobre
  variantes: realizado/realizada, falta/faltou, cancelado/cancelada).
- tagStyle com contraste auto (luminance WCAG-ish: bg colorido +
  texto preto/branco baseado em luminance < 0.45).
- Sera reutilizado pelas Fases 3-7 e na Fase 8 substitui as funcoes
  duplicadas do PatientProntuario.

EXTENSAO de composables (Fase 1):
- usePatientSessions: novo computed `ultimasAtendidas` (top 6 sessoes
  com status realiz/falt/cancel/remarc pra Timeline). totalRealizadas/
  Faltas/Canceladas refinados pra usar regex (cobre variantes pt-br).
- usePatientFinancial: novo computed `statusFinanceiro` que retorna
  { emDia: bool, proxVenc: record, totalPendente, totalPago, vencidos }
  pra alimentar KPI 02 com info detalhada de status financeiro.

MELISSAPACIENTE.VUE — Visao Geral reescrita:
- 4 KPI cards ricos (substituem os simples da Fase 1):
  - 01 Sessoes: realizadas / total + faltas + canceladas
  - 02 Pagamento: status (Em dia/atraso) + prox venc + cor adaptativa
    (vermelho atrasado / primary ok)
  - 03 Proxima sessao: relative + datetime + modalidade
  - 04 Mensagens: ultima relative + direction + count
- Grid 2-col abaixo (1.4fr / 1fr em >=900px):
  - Timeline coluna esquerda: dots coloridos por status, tags severity,
    chips modalidade + duracao, nota observacoes inline.
  - Coluna direita: Mensagens recentes (4) com border-left in/out +
    meta direction/relative + body 3-line clamp; Notas e observacoes
    em card papel com label uppercase e icone lock.
- Removeu kpiEmAberto/Atrasado nao usados (statusFinanceiro encapsula).

CSS: ~280L novos pros componentes (KPIs ricos, panel base, empty rich,
timeline, mensagens, notas). Mantem o pattern visual Melissa.

ESLint: 0 errors da minha mudanca.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-08 09:31:36 -03:00
parent df61cc4d99
commit ab7526b8d7
5 changed files with 819 additions and 73 deletions
+548 -68
View File
@@ -26,6 +26,17 @@ import { usePatientSessions } from '@/features/patients/composables/usePatientSe
import { usePatientFinancial } from '@/features/patients/composables/usePatientFinancial';
import { usePatientMessages } from '@/features/patients/composables/usePatientMessages';
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
import {
calcAge,
fmtRelative,
fmtDateBR,
fmtDateTimeBR,
fmtCurrency,
sessionDuration,
STATUS_LABEL,
STATUS_SEVERITY,
tagStyle as tagStyleHelper
} from '@/features/patients/utils/patientFormatters';
// Tag/Skeleton: auto via PrimeVueResolver
const props = defineProps({
@@ -104,15 +115,8 @@ const avatarInitials = computed(() => {
});
const ageLabel = computed(() => {
const dob = patientData.value?.data_nascimento;
if (!dob) return '';
const birth = new Date(dob);
if (Number.isNaN(birth.getTime())) return '';
const now = new Date();
let age = now.getFullYear() - birth.getFullYear();
const m = now.getMonth() - birth.getMonth();
if (m < 0 || (m === 0 && now.getDate() < birth.getDate())) age--;
return `${age} anos`;
const age = calcAge(patientData.value?.data_nascimento);
return age == null ? '' : `${age} anos`;
});
const convenio = computed(() => patientData.value?.convenio || patientData.value?.plano_saude || '');
@@ -120,19 +124,23 @@ const statusPaciente = computed(() => patientData.value?.status || '');
const riscoElevado = computed(() => !!patientData.value?.risco_elevado);
function tagStyle(t) {
if (!t?.color) return {};
return {
background: `${t.color}22`,
color: t.color,
border: `1px solid ${t.color}44`
};
return tagStyleHelper(t);
}
// ── KPIs basicos pro Visao Geral (placeholder Fase 1) ─────
// ── Notas + observacoes (campos opcionais do paciente) ─────
function pickField(obj, keys = []) {
for (const k of keys) {
const v = obj?.[k];
if (v !== null && v !== undefined && String(v).trim()) return v;
}
return null;
}
const observacoes = computed(() => pickField(patientData.value, ['observacoes', 'notes_short']));
const notasInternas = computed(() => pickField(patientData.value, ['notas_internas', 'notes']));
// ── KPIs Visao Geral (Fase 2) ──────────────────────────────
const kpiSessoes = computed(() => sessionsHook.totalSessoes.value);
const kpiRealizadas = computed(() => sessionsHook.totalRealizadas.value);
const kpiEmAberto = computed(() => financialHook.totalEmAberto.value);
const kpiAtrasado = computed(() => financialHook.totalAtrasado.value);
const kpiMensagens = computed(() => messagesHook.messages.value.length);
const kpiDocumentos = computed(() => documentsHook.total.value);
@@ -397,48 +405,222 @@ void toast;
<!-- Conteudo de cada aba -->
<template v-else>
<!-- ABA: Visao Geral (KPIs simples ja funcionam Fase 1) -->
<!-- ABA: Visao Geral (Fase 2 KPIs + Timeline + Mensagens + Notas) -->
<div v-if="activeTab === 'overview'" class="mpa-tab">
<!-- 4 KPI cards -->
<div class="mpa-kpis">
<article class="mpa-kpi" style="--c:#10b981">
<div class="mpa-kpi__icon"><i class="pi pi-calendar" /></div>
<div class="mpa-kpi__value">{{ kpiSessoes }}</div>
<div class="mpa-kpi__label">Sessões totais</div>
<div class="mpa-kpi__sub">{{ kpiRealizadas }} realizadas</div>
</article>
<article class="mpa-kpi" style="--c:#f59e0b">
<div class="mpa-kpi__icon"><i class="pi pi-wallet" /></div>
<div class="mpa-kpi__value">R$ {{ kpiEmAberto.toFixed(2) }}</div>
<div class="mpa-kpi__label">Em aberto</div>
<div v-if="kpiAtrasado > 0" class="mpa-kpi__sub mpa-kpi__sub--alert">
R$ {{ kpiAtrasado.toFixed(2) }} atrasado
<!-- 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>
<article class="mpa-kpi" style="--c:#22c55e">
<div class="mpa-kpi__icon"><i class="pi pi-whatsapp" /></div>
<div class="mpa-kpi__value">{{ kpiMensagens }}</div>
<div class="mpa-kpi__label">Mensagens trocadas</div>
<!-- 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>
<article class="mpa-kpi" style="--c:#f97316">
<div class="mpa-kpi__icon"><i class="pi pi-folder" /></div>
<div class="mpa-kpi__value">{{ kpiDocumentos }}</div>
<div class="mpa-kpi__label">Documentos</div>
<!-- 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>
<div class="mpa-w">
<div class="mpa-w__head">
<div class="mpa-w__icon"><i class="pi pi-info-circle" /></div>
<div class="mpa-w__title">
<div class="mpa-w__title-text">Visão Geral Fase 1</div>
<div class="mpa-w__sub">KPIs básicos funcionam. Timeline + cards ricos: Fase 2.</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>
</div>
<div class="mpa-w__body">
<p class="mpa-placeholder">
Esta aba será expandida na <strong>Fase 2</strong> com timeline de últimas
interações, próxima sessão, alertas e cards de últimas mensagens/lançamentos.
</p>
</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>
@@ -1019,62 +1201,360 @@ void toast;
border-color: color-mix(in srgb, var(--p-primary-color) 28%, transparent);
}
/* ═══════ KPIs (Visao Geral Fase 1) ═══════ */
/* ═══════ KPIs (Visao Geral Fase 2 — 4 cards ricos) ═══════ */
.mpa-kpis {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 12px;
}
.mpa-kpi {
position: relative;
padding: 14px;
padding: 14px 14px 12px;
border-radius: 12px;
background: var(--m-bg-soft);
border: 1px solid var(--m-border);
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 60%);
opacity: 0.08;
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) 15%, transparent);
background: color-mix(in srgb, var(--c, #6366f1) 16%, transparent);
color: var(--c, #6366f1);
margin-bottom: 4px;
flex-shrink: 0;
}
.mpa-kpi__icon > i { font-size: 0.85rem; }
.mpa-kpi__value {
font-size: 1.4rem;
.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.1;
line-height: 1.05;
letter-spacing: -0.02em;
margin-top: auto;
}
.mpa-kpi__label {
font-size: 0.78rem;
.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__sub {
.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);
opacity: 0.8;
margin-top: 2px;
}
.mpa-kpi__sub--alert { color: rgb(220, 38, 38); font-weight: 600; }
.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;
}
/* ═══════ 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 {