busca melissa: shape RPC + cores legíveis + cor de sessão

3 fixes pedidos no teste manual:

1. Shape errado da RPC: search_global retorna { id, label, sublabel,
   deeplink } pra TODOS os tipos, mas o codigo lia campos diretos
   (nome_completo, paciente_nome, inicio_em, nome_original etc) que
   nao existem -> resultados saiam "(sem nome)", sem datas.
   Fix: filteredPacientes + rpcAppointments + rpcDocuments + rpcIntakes
   agora usam label/sublabel direto. selectEntry extrai patient_id da
   deeplink quando precisa.

2. Cores ilegiveis: fundo do panel transparente demais (var(--m-bg-medium)
   nao tinha contraste em alguns temas). Fix: fundo solido rgba(20,22,32,
   0.92), border 14% white, text 96% white pra label, 65% pra sub
   (sobe pra 78% no hover/active). Group title 50% + bold pra hierarquia
   clara.

3. Cor das sessoes: grupo "Sessoes" tinha icone cinza generico. Fix:
   classes .mb-item__icon--{patient,sessao,doc,intake} com paleta
   espelhando a agenda — sessao = indigo-500 (#a5b4fc texto +
   rgba(99,102,241,0.20) bg, mesma cor do pickColor() padrao);
   patient = pink-400; doc = sky-500; intake = orange-400.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-21 12:26:24 -03:00
parent dee89ccd84
commit ffd8eab72d
+83 -45
View File
@@ -79,6 +79,9 @@ const filteredAtalhos = computed(() => {
// Pacientes — combina RPC (autoritativo, todos os pacientes) com props (preview de hoje). // Pacientes — combina RPC (autoritativo, todos os pacientes) com props (preview de hoje).
// RPC tem prioridade; props complementa quando RPC ainda não trouxe nada. // RPC tem prioridade; props complementa quando RPC ainda não trouxe nada.
//
// Shape do RPC search_global (patients): { id, label, sublabel, avatar_url, deeplink, score }
// label = nome_completo; sublabel = email_principal ou telefone.
const filteredPacientes = computed(() => { const filteredPacientes = computed(() => {
const q = normalize(query.value); const q = normalize(query.value);
if (q.length < 2) return []; if (q.length < 2) return [];
@@ -87,15 +90,16 @@ const filteredPacientes = computed(() => {
if (rpc.length) { if (rpc.length) {
return rpc.slice(0, 5).map(p => ({ return rpc.slice(0, 5).map(p => ({
id: p.id, id: p.id,
nome: p.nome_completo || p.nome_social || p.nome || '(sem nome)', nome: p.label || '(sem nome)',
email: p.email, sub: p.sublabel || '',
telefone: p.telefone avatar_url: p.avatar_url || null
})); }));
} }
// Fallback client-side // Fallback client-side (props.pacientes vem do MelissaLayout — shape diferente)
return props.pacientes return props.pacientes
.filter((p) => normalize(p.nome).includes(q)) .filter((p) => normalize(p.nome).includes(q))
.slice(0, 5); .slice(0, 5)
.map(p => ({ id: p.id, nome: p.nome, sub: '', avatar_url: null }));
}); });
const filteredEventos = computed(() => { const filteredEventos = computed(() => {
@@ -139,9 +143,17 @@ function selectEntry(entry) {
else if (entry.group === 'pacientes') emit('paciente', entry.item); else if (entry.group === 'pacientes') emit('paciente', entry.item);
else if (entry.group === 'recent') emit('paciente', { id: entry.item.id, nome: entry.item.nome, ...entry.item.extras }); else if (entry.group === 'recent') emit('paciente', { id: entry.item.id, nome: entry.item.nome, ...entry.item.extras });
else if (entry.group === 'eventos') emit('evento', entry.item); else if (entry.group === 'eventos') emit('evento', entry.item);
else if (entry.group === 'rpc-appointments') emit('evento', entry.item); else if (entry.group === 'rpc-appointments') {
else if (entry.group === 'rpc-documents') emit('documento', entry.item); // Sessão da RPC: deeplink pra agenda com evento focado
else if (entry.group === 'rpc-intakes') emit('intake', entry.item); emit('evento', { id: entry.item.id, deeplink: entry.item.deeplink });
} else if (entry.group === 'rpc-documents') {
// Documento da RPC: extrai patient_id da deeplink se possível
const dl = entry.item.deeplink || '';
const m = dl.match(/patients\/([0-9a-f-]+)/i);
emit('documento', { id: entry.item.id, patient_id: m?.[1] || null, label: entry.item.label });
} else if (entry.group === 'rpc-intakes') {
emit('intake', entry.item);
}
closePanel(); closePanel();
} }
@@ -320,10 +332,10 @@ onBeforeUnmount(() => {
@click="selectEntry({ group: 'pacientes', item: p })" @click="selectEntry({ group: 'pacientes', item: p })"
@mouseenter="activeIndex = findFlatIndex('pacientes', i)" @mouseenter="activeIndex = findFlatIndex('pacientes', i)"
> >
<span class="mb-item__icon"><i class="pi pi-user" /></span> <span class="mb-item__icon mb-item__icon--patient"><i class="pi pi-user" /></span>
<span class="mb-item__main"> <span class="mb-item__main">
<span class="mb-item__label">{{ p.nome }}</span> <span class="mb-item__label">{{ p.nome }}</span>
<span class="mb-item__sub">Abrir prontuário</span> <span class="mb-item__sub">{{ p.sub || 'Abrir prontuário' }}</span>
</span> </span>
<i class="mb-item__go pi pi-arrow-right" /> <i class="mb-item__go pi pi-arrow-right" />
</button> </button>
@@ -354,7 +366,10 @@ onBeforeUnmount(() => {
</button> </button>
</div> </div>
<!-- RPC: Sessões/agendamentos (qualquer data) --> <!-- RPC: Sessões/agendamentos (qualquer data)
RPC retorna { id, label, sublabel, deeplink }. Sublabel ja vem
com "Paciente · dd/mm/yyyy HH:MM". Cor do icone = cor de sessao
(indigo-500, igual ao pickColor() padrao). -->
<div v-if="rpcAppointments.length" class="mb-group"> <div v-if="rpcAppointments.length" class="mb-group">
<div class="mb-group__title">Sessões</div> <div class="mb-group__title">Sessões</div>
<button <button
@@ -365,10 +380,10 @@ onBeforeUnmount(() => {
@click="selectEntry({ group: 'rpc-appointments', item: e })" @click="selectEntry({ group: 'rpc-appointments', item: e })"
@mouseenter="activeIndex = findFlatIndex('rpc-appointments', i)" @mouseenter="activeIndex = findFlatIndex('rpc-appointments', i)"
> >
<span class="mb-item__icon"><i class="pi pi-calendar" /></span> <span class="mb-item__icon mb-item__icon--sessao"><i class="pi pi-calendar" /></span>
<span class="mb-item__main"> <span class="mb-item__main">
<span class="mb-item__label">{{ e.paciente_nome || e.title || 'Sessão' }}</span> <span class="mb-item__label">{{ e.label || 'Sessão' }}</span>
<span class="mb-item__sub">{{ e.inicio_em ? new Date(e.inicio_em).toLocaleDateString('pt-BR') + ' ' + new Date(e.inicio_em).toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit' }) : 'Sem data' }}</span> <span class="mb-item__sub">{{ e.sublabel || 'Sem detalhes' }}</span>
</span> </span>
<i class="mb-item__go pi pi-arrow-right" /> <i class="mb-item__go pi pi-arrow-right" />
</button> </button>
@@ -385,10 +400,10 @@ onBeforeUnmount(() => {
@click="selectEntry({ group: 'rpc-documents', item: d })" @click="selectEntry({ group: 'rpc-documents', item: d })"
@mouseenter="activeIndex = findFlatIndex('rpc-documents', i)" @mouseenter="activeIndex = findFlatIndex('rpc-documents', i)"
> >
<span class="mb-item__icon"><i class="pi pi-file" /></span> <span class="mb-item__icon mb-item__icon--doc"><i class="pi pi-file" /></span>
<span class="mb-item__main"> <span class="mb-item__main">
<span class="mb-item__label">{{ d.nome_original || 'Documento' }}</span> <span class="mb-item__label">{{ d.label || 'Documento' }}</span>
<span class="mb-item__sub">{{ d.paciente_nome ? `${d.paciente_nome} · ` : '' }}{{ d.tipo_documento || 'outro' }}</span> <span class="mb-item__sub">{{ d.sublabel || '' }}</span>
</span> </span>
<i class="mb-item__go pi pi-arrow-right" /> <i class="mb-item__go pi pi-arrow-right" />
</button> </button>
@@ -405,10 +420,10 @@ onBeforeUnmount(() => {
@click="selectEntry({ group: 'rpc-intakes', item: r })" @click="selectEntry({ group: 'rpc-intakes', item: r })"
@mouseenter="activeIndex = findFlatIndex('rpc-intakes', i)" @mouseenter="activeIndex = findFlatIndex('rpc-intakes', i)"
> >
<span class="mb-item__icon"><i class="pi pi-inbox" /></span> <span class="mb-item__icon mb-item__icon--intake"><i class="pi pi-inbox" /></span>
<span class="mb-item__main"> <span class="mb-item__main">
<span class="mb-item__label">{{ r.nome_completo || 'Cadastro' }}</span> <span class="mb-item__label">{{ r.label || 'Cadastro' }}</span>
<span class="mb-item__sub">{{ r.created_at ? new Date(r.created_at).toLocaleDateString('pt-BR') : '' }}</span> <span class="mb-item__sub">{{ r.sublabel || '' }}</span>
</span> </span>
<i class="mb-item__go pi pi-arrow-right" /> <i class="mb-item__go pi pi-arrow-right" />
</button> </button>
@@ -486,40 +501,41 @@ onBeforeUnmount(() => {
z-index: 30; z-index: 30;
max-height: 60vh; max-height: 60vh;
overflow-y: auto; overflow-y: auto;
background: var(--m-bg-medium); /* Fundo mais sólido pra melhor contraste com o lockscreen */
background: rgba(20, 22, 32, 0.92);
backdrop-filter: blur(28px) saturate(160%); backdrop-filter: blur(28px) saturate(160%);
-webkit-backdrop-filter: blur(28px) saturate(160%); -webkit-backdrop-filter: blur(28px) saturate(160%);
border: 1px solid var(--m-border-strong); border: 1px solid rgba(255, 255, 255, 0.14);
border-radius: 12px; border-radius: 12px;
padding: 6px; padding: 6px;
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.45); box-shadow: 0 16px 40px rgba(0, 0, 0, 0.55);
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: var(--m-border-strong) transparent; scrollbar-color: rgba(255, 255, 255, 0.18) transparent;
} }
.mb-panel::-webkit-scrollbar { width: 6px; } .mb-panel::-webkit-scrollbar { width: 6px; }
.mb-panel::-webkit-scrollbar-thumb { .mb-panel::-webkit-scrollbar-thumb {
background: var(--m-border-strong); background: rgba(255, 255, 255, 0.18);
border-radius: 3px; border-radius: 3px;
} }
.mb-empty { .mb-empty {
padding: 18px 14px; padding: 18px 14px;
text-align: center; text-align: center;
color: var(--m-text-muted); color: rgba(255, 255, 255, 0.65);
font-size: 0.85rem; font-size: 0.85rem;
} }
.mb-group + .mb-group { .mb-group + .mb-group {
margin-top: 4px; margin-top: 4px;
padding-top: 4px; padding-top: 4px;
border-top: 1px solid var(--m-border); border-top: 1px solid rgba(255, 255, 255, 0.08);
} }
.mb-group__title { .mb-group__title {
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.18em; letter-spacing: 0.18em;
color: var(--m-text-faint); color: rgba(255, 255, 255, 0.5);
font-size: 0.6rem; font-size: 0.62rem;
font-weight: 600; font-weight: 700;
padding: 8px 10px 4px; padding: 8px 10px 4px;
} }
@@ -528,11 +544,11 @@ onBeforeUnmount(() => {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 8px 10px; padding: 9px 10px;
background: transparent; background: transparent;
border: none; border: none;
border-radius: 8px; border-radius: 8px;
color: white; color: rgba(255, 255, 255, 0.95);
text-align: left; text-align: left;
cursor: pointer; cursor: pointer;
font-family: inherit; font-family: inherit;
@@ -540,43 +556,65 @@ onBeforeUnmount(() => {
} }
.mb-item:hover, .mb-item:hover,
.mb-item.is-active { .mb-item.is-active {
background: var(--m-bg-soft); background: rgba(255, 255, 255, 0.08);
} }
.mb-item__icon { .mb-item__icon {
width: 30px; width: 32px;
height: 30px; height: 32px;
display: grid; display: grid;
place-items: center; place-items: center;
background: var(--m-bg-soft); background: rgba(255, 255, 255, 0.08);
border-radius: 7px; border-radius: 7px;
color: var(--m-text-muted); color: rgba(255, 255, 255, 0.7);
flex-shrink: 0; flex-shrink: 0;
font-size: 0.85rem; font-size: 0.9rem;
}
/* Cores por tipo — espelha a paleta da agenda */
.mb-item__icon--patient {
background: rgba(244, 114, 182, 0.18); /* pink-400 — paciente */
color: #f9a8d4;
}
.mb-item__icon--sessao {
background: rgba(99, 102, 241, 0.20); /* indigo-500 — sessão (compromisso determinístico) */
color: #a5b4fc;
}
.mb-item__icon--doc {
background: rgba(14, 165, 233, 0.18); /* sky-500 — documentos */
color: #7dd3fc;
}
.mb-item__icon--intake {
background: rgba(251, 146, 60, 0.18); /* orange-400 — cadastros recebidos */
color: #fdba74;
} }
.mb-item__main { .mb-item__main {
flex: 1; flex: 1;
min-width: 0; min-width: 0;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 1px; gap: 2px;
} }
.mb-item__label { .mb-item__label {
font-size: 0.85rem; font-size: 0.88rem;
color: white; font-weight: 500;
color: rgba(255, 255, 255, 0.96);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.mb-item__sub { .mb-item__sub {
font-size: 0.7rem; font-size: 0.74rem;
color: var(--m-text-muted); color: rgba(255, 255, 255, 0.65);
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
} }
.mb-item:hover .mb-item__sub,
.mb-item.is-active .mb-item__sub {
color: rgba(255, 255, 255, 0.78);
}
.mb-item__go { .mb-item__go {
color: var(--m-text-faint); color: rgba(255, 255, 255, 0.4);
font-size: 0.7rem; font-size: 0.75rem;
flex-shrink: 0; flex-shrink: 0;
} }