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
@@ -69,6 +69,38 @@ export function usePatientFinancial() {
return [...pagos].sort((a, b) => new Date(b.paid_at) - new Date(a.paid_at))[0];
});
/**
* Status financeiro detalhado pra KPI da Visao Geral.
* - emDia: nenhum pendente vencido (paid_at NULL && due_date < hoje)
* - proxVenc: proximo pendente com due_date no futuro
* - totalPendente / totalPago: somatorio
* - vencidos: count de pendentes vencidos
*/
const statusFinanceiro = computed(() => {
const recs = records.value;
if (!recs?.length) {
return { emDia: null, proxVenc: null, totalPendente: 0, totalPago: 0, vencidos: 0 };
}
const now = Date.now();
const pendentes = recs.filter((r) => !r.paid_at);
const pagos = recs.filter((r) => !!r.paid_at);
const vencidos = pendentes.filter(
(r) => r.due_date && new Date(r.due_date + 'T23:59:59').getTime() < now
);
const proxVenc = pendentes
.filter((r) => r.due_date && new Date(r.due_date + 'T00:00:00').getTime() >= now)
.sort((a, b) => new Date(a.due_date) - new Date(b.due_date))[0] || null;
const totalPendente = pendentes.reduce((acc, r) => acc + (Number(r.amount) || 0), 0);
const totalPago = pagos.reduce((acc, r) => acc + (Number(r.amount) || 0), 0);
return {
emDia: vencidos.length === 0,
proxVenc,
totalPendente,
totalPago,
vencidos: vencidos.length
};
});
return {
records,
loading,
@@ -77,6 +109,7 @@ export function usePatientFinancial() {
totalRecebido,
totalEmAberto,
totalAtrasado,
ultimoPago
ultimoPago,
statusFinanceiro
};
}
@@ -58,14 +58,27 @@ export function usePatientSessions() {
});
const totalSessoes = computed(() => sessions.value.length);
// Conta status com regex pra cobrir variantes pt-br
// (realizada/realizado/presente; falta/faltou; cancelada/cancelado/remarcada).
const totalRealizadas = computed(() =>
sessions.value.filter((s) => s.status === 'realizada' || s.status === 'presente').length
sessions.value.filter((s) => /realiz|present/i.test(String(s.status || ''))).length
);
const totalFaltas = computed(() =>
sessions.value.filter((s) => s.status === 'falta').length
sessions.value.filter((s) => /falt/i.test(String(s.status || ''))).length
);
const totalCanceladas = computed(() =>
sessions.value.filter((s) => s.status === 'cancelada' || s.status === 'cancelado').length
sessions.value.filter((s) => /cancel|remarca/i.test(String(s.status || ''))).length
);
/**
* Top 6 sessoes "atendidas" (qualquer status que indica encontro: realizado,
* faltou, cancelado, remarcado) — alimenta a Timeline da Visao Geral.
*/
const ultimasAtendidas = computed(() =>
sessions.value
.filter((s) => /realiz|present|falt|cancel|remarca/i.test(String(s.status || '')))
.slice(0, 6)
);
return {
@@ -78,6 +91,7 @@ export function usePatientSessions() {
totalSessoes,
totalRealizadas,
totalFaltas,
totalCanceladas
totalCanceladas,
ultimasAtendidas
};
}