MelissaPaciente Fase 6: Tab Financeiro completa + mark paid (mutation que legacy nao tem)

EXTENSAO src/features/patients/utils/patientFormatters.js
- recordStatus(r): pago / vencido (paid_at NULL && due_date < hoje) / pendente
- RECORD_STATUS_LABEL map
- fmtPaymentMethod(v): PIX/Cartao/Dinheiro/Boleto/Transferencia/Convenio
  cobrindo variantes pt-br + camelCase

EXTENSAO src/features/patients/composables/usePatientFinancial.js
- ref `busy` + `_lastPatientId` interno
- recordsOrdenados computed: DESC por due_date com fallback created_at
- markPaid(recordId): UPDATE financial_records SET paid_at=NOW() +
  auto-reload via _lastPatientId. Retorna {ok, error?}
- markUnpaid(recordId): reverte (paid_at=NULL) + auto-reload

MELISSAPACIENTE.VUE — script
- Imports: recordStatus, RECORD_STATUS_LABEL, fmtPaymentMethod
- markRecordPaid(r): chama financialHook.markPaid + toast success/error
- revertRecordPaid(r): chama markUnpaid + toast

MELISSAPACIENTE.VUE — Tab Financeiro reescrita (substitui placeholder Fase 1)
- Loading state
- Empty state com CTA "Novo lancamento" (mpa-quick-btn--cta)
- 3 KPIs: Pago / Pendente com proxVenc / Em atraso (cor adaptativa
  vermelho quando > 0, cinza quando 0)
- Header "Lancamentos" com badge count + botao "+ Novo" no canto
- Tabela 6-col responsiva:
  - Vencimento (date mono + relative)
  - Descricao
  - Forma (PIX/Cartao/etc)
  - Valor (mono right-aligned)
  - Status pill colorida (verde pago / vermelho vencido / azul pendente)
  - Action button (pi-check verde marca pago / pi-undo amarelo reverte)
- border-left adaptativa por status
- Mobile: tabela colapsa em cards 2-col 4-row

DIFERENCA DO LEGACY: o PatientProntuario.vue exibe a tabela mas NAO
permite marcar pago/reverter direto dela. MelissaPaciente adiciona essa
acao inline (mutation auto-reload).

CSS: ~190L novos. Padrao Melissa: status pills com color-mix, JetBrains
Mono pra valores, header cell uppercase letter-spacing.

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:57:42 -03:00
parent 8a8d2e05bd
commit e7c0f6c4f5
4 changed files with 506 additions and 17 deletions
@@ -17,8 +17,11 @@ export function usePatientFinancial() {
const records = ref([]);
const loading = ref(false);
const error = ref('');
const busy = ref(false);
let _lastPatientId = null;
async function load(patientId) {
_lastPatientId = patientId || null;
if (!patientId) {
records.value = [];
return;
@@ -101,15 +104,74 @@ export function usePatientFinancial() {
};
});
/**
* Lancamentos ordenados DESC por due_date (fallback created_at).
* Mais recente primeiro pra alimentar a tabela da Tab Financeiro.
*/
const recordsOrdenados = computed(() =>
[...records.value].sort((a, b) => {
const da = a.due_date || a.created_at;
const db = b.due_date || b.created_at;
return new Date(db) - new Date(da);
})
);
/**
* Marca um lancamento como pago (paid_at = now). Auto-reload.
* Retorna {ok, error?}.
*/
async function markPaid(recordId) {
if (!recordId || busy.value) return { ok: false, error: 'busy' };
busy.value = true;
try {
const { error: err } = await supabase
.from('financial_records')
.update({ paid_at: new Date().toISOString() })
.eq('id', recordId);
if (err) throw err;
if (_lastPatientId) await load(_lastPatientId);
return { ok: true };
} catch (e) {
return { ok: false, error: e?.message || 'Erro ao marcar como pago' };
} finally {
busy.value = false;
}
}
/**
* Reverte: remove paid_at (volta pra pendente). Auto-reload.
*/
async function markUnpaid(recordId) {
if (!recordId || busy.value) return { ok: false, error: 'busy' };
busy.value = true;
try {
const { error: err } = await supabase
.from('financial_records')
.update({ paid_at: null })
.eq('id', recordId);
if (err) throw err;
if (_lastPatientId) await load(_lastPatientId);
return { ok: true };
} catch (e) {
return { ok: false, error: e?.message || 'Erro ao reverter pagamento' };
} finally {
busy.value = false;
}
}
return {
records,
loading,
error,
busy,
load,
totalRecebido,
totalEmAberto,
totalAtrasado,
ultimoPago,
statusFinanceiro
statusFinanceiro,
recordsOrdenados,
markPaid,
markUnpaid
};
}