MelissaPaciente Fase 3: Tab Perfil completa (6 sections stacked + anchors)

EXTENSAO: src/features/patients/utils/patientFormatters.js
- +5 formatters: pickField (compartilhado), onlyDigits, fmtCPF (000.000.000-00),
  fmtRG (passthrough), fmtPhoneMobile ((XX) 9XXXX-XXXX), fmtGender
  (Masculino/Feminino/Nao-binario/Outro), fmtMarital (Solteiro/Casado/
  Divorciado/Viuvo/Uniao estavel).

MELISSAPACIENTE.VUE — script
- 30+ field computeds usando pickField (cobre snake_case + camelCase):
  birthValue, telefone/Alternativo, email/Alternativo, genero, estadoCivil,
  naturalidade, ondeNosConheceu, encaminhadoPor, observacoes, notasInternas
  + 8 campos de endereco + 5 dados adicionais + 4 responsavel.
- groupNames/groupLabel/groupCountLabel pra bloco Origem.
- scrollToProfileSection(key): liga sidebar sub-nav -> scrollIntoView do
  anchor #mpa-perfil-XXX. Em mobile fecha o drawer.

MELISSAPACIENTE.VUE — Tab Perfil reescrita
Diferente do PatientProntuario legacy que usa PrimeVue Accordion (1 painel
aberto por vez), o Melissa nativo mostra os 6 cards stacked com scroll
suave do sidebar sub-nav. Mais legivel em desktop, mais rapido de escanear.

- 1. Informacoes Pessoais: 2-col com Dados de cadastro (nome/data nasc
  com idade inline/genero/estado civil/CPF/RG/naturalidade) + Contato +
  Origem (grupos/tags chips/onde nos conheceu/encaminhado por). tel: e
  mailto: links onde ha valor. Observacoes full-width quando preenchido.
- 2. Endereco: grid 2-col com 8 fields.
- 3. Dados Adicionais: grid 2-col com escolaridade/profissao/parente/grau/
  tel parente.
- 4. Responsavel: 1-col com nome/CPF/tel + observacao block textual.
- 5. Anotacoes Internas: card com hint lock + textblock min-height.
- 6. Sessoes: lista compacta scrollable (max-height 360px) com titulo/
  data/duracao/modalidade chips + tag status.

CSS: ~250L novos pros componentes (mpa-fields/field-row/field-grid-2/
field-block/sess/sess-list). Pattern visual Melissa: cards com label
uppercase, separadores horizontais sutis, links primary, monospace pra
CPF/RG/CEP.

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:43:03 -03:00
parent ab7526b8d7
commit 4fc0e3a02b
3 changed files with 691 additions and 22 deletions
@@ -40,6 +40,82 @@ export function dash(v) {
return s || '—';
}
/**
* Pega a primeira chave nao vazia de um objeto (snake_case ou camelCase).
* Usado pra resolver discrepancias de schema (ex: 'data_nascimento' vs 'birth_date').
*/
export 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;
}
export function onlyDigits(v) {
return String(v ?? '').replace(/\D/g, '');
}
/**
* Formata CPF: 00000000000 -> 000.000.000-00
*/
export function fmtCPF(v) {
const d = onlyDigits(v);
if (!d) return '—';
if (d.length !== 11) return d;
return `${d.slice(0, 3)}.${d.slice(3, 6)}.${d.slice(6, 9)}-${d.slice(9)}`;
}
/**
* Formata RG (genericamente — varia por estado):
* 00.000.000-0 / 0000000000 → mantem digitos com pontos a cada 3 a partir da direita.
*/
export function fmtRG(v) {
const s = String(v ?? '').trim();
if (!s) return '—';
return s;
}
/**
* Formata telefone celular pt-br: (XX) 9XXXX-XXXX ou (XX) XXXX-XXXX.
*/
export function fmtPhoneMobile(v) {
const d = onlyDigits(v);
if (!d) return '—';
if (d.length === 11) return `(${d.slice(0, 2)}) ${d.slice(2, 7)}-${d.slice(7, 11)}`;
if (d.length === 10) return `(${d.slice(0, 2)}) ${d.slice(2, 6)}-${d.slice(6, 10)}`;
return d;
}
/**
* Mapeia variantes de genero pra label legivel.
*/
export function fmtGender(v) {
const s = String(v ?? '').trim();
if (!s) return '—';
const x = s.toLowerCase();
if (['m', 'masc', 'masculino', 'male', 'man', 'homem'].includes(x)) return 'Masculino';
if (['f', 'fem', 'feminino', 'female', 'woman', 'mulher'].includes(x)) return 'Feminino';
if (['nb', 'nao-binario', 'não-binário', 'nonbinary', 'non-binary'].includes(x)) return 'Não-binário';
if (['outro', 'other'].includes(x)) return 'Outro';
return s;
}
/**
* Mapeia variantes de estado civil pra label pt-br.
*/
export function fmtMarital(v) {
const s = String(v ?? '').trim();
if (!s) return '—';
const x = s.toLowerCase();
if (['solteiro', 'solteira', 'single'].includes(x)) return 'Solteiro(a)';
if (['casado', 'casada', 'married'].includes(x)) return 'Casado(a)';
if (['divorciado', 'divorciada', 'divorced'].includes(x)) return 'Divorciado(a)';
if (['viuvo', 'viúva', 'viuvo(a)', 'widowed'].includes(x)) return 'Viúvo(a)';
if (['uniao estavel', 'união estável', 'civil union'].includes(x)) return 'União estável';
return s;
}
export function fmtDateBR(v) {
const d = parseDateLoose(v);
if (!d) return v ? dash(v) : '—';