agenda: pacientes arquivados/inativos visiveis e bloqueados no picker

AgendaEventDialogV2.filteredPatients agora mostra TODOS os pacientes
(antes filtrava status='Ativo' silenciosamente), ordenados Ativo > Inativo
> Arquivado. Items nao-Ativo vem com Tag colorida + disabled + tooltip
explicativo — UX clara: o paciente aparece (user nao "perde" no search)
mas nao da pra agendar.

selectPaciente bloqueia non-Ativo (defesa em camadas: template ja marca
disabled, mas se alguem chamar a funcao programaticamente por cache stale
etc, a regra continua valendo). Copia status pro form pra canSave aplicar
getPatientAgendaPermissions corretamente.

3 specs novas em useAgendaEventPickerBilling.spec cobrem o bloqueio +
copia do status.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Leonardo
2026-05-11 10:45:57 -03:00
parent 8e3c09d1b1
commit 8f4e6679eb
3 changed files with 62 additions and 8 deletions
@@ -270,16 +270,22 @@ function onPatientCreatedRapido(p) {
cadRapidoOpen.value = false;
}
// Mostra TODOS os pacientes (inclusive Inativo/Arquivado) — UX intencional:
// nao-Ativos vem com badge + disabled no template, ordenados pro final.
// selectPaciente bloqueia o clique se status !== 'Ativo'.
const _aev2StatusRank = { Ativo: 0, Inativo: 1, Arquivado: 2 };
const filteredPatients = computed(() => {
const q = String(pacienteSearch.value || '').trim().toLowerCase();
const list = (patients.value || []).filter((p) => p.status === 'Ativo');
if (!q) return list;
return list.filter((p) => {
const nome = String(p.nome || '').toLowerCase();
const email = String(p.email || '').toLowerCase();
const tel = String(p.telefone || '').toLowerCase();
return nome.includes(q) || email.includes(q) || tel.includes(q);
});
const list = patients.value || [];
const matched = !q
? [...list]
: list.filter((p) => {
const nome = String(p.nome || '').toLowerCase();
const email = String(p.email || '').toLowerCase();
const tel = String(p.telefone || '').toLowerCase();
return nome.includes(q) || email.includes(q) || tel.includes(q);
});
return matched.sort((a, b) => (_aev2StatusRank[a.status] ?? 3) - (_aev2StatusRank[b.status] ?? 3));
});
function goToAgendamentosRecebidos() {
@@ -924,6 +930,9 @@ const heroDateText = computed(() => {
:key="p.id"
type="button"
class="aev2-picker-item"
:class="{ 'aev2-picker-item--blocked': p.status && p.status !== 'Ativo' }"
:disabled="p.status && p.status !== 'Ativo'"
:title="p.status === 'Arquivado' ? 'Paciente arquivado — não é possível agendar' : p.status === 'Inativo' ? 'Paciente inativo — não é possível agendar' : ''"
@click="selectPaciente(p)"
>
<Avatar v-if="p.avatar_url" :image="p.avatar_url" shape="circle" size="normal" />
@@ -932,6 +941,8 @@ const heroDateText = computed(() => {
<div class="font-semibold truncate">{{ p.nome }}</div>
<div class="text-xs opacity-60 truncate">{{ p.email || p.telefone }}</div>
</div>
<Tag v-if="p.status === 'Arquivado'" value="Arquivado" severity="danger" class="shrink-0" />
<Tag v-else-if="p.status === 'Inativo'" value="Inativo" severity="warn" class="shrink-0" />
</button>
</div>
</div>
@@ -946,6 +957,7 @@ const heroDateText = computed(() => {
email-field="email_principal"
phone-field="telefone"
:extra-payload="{ status: 'Ativo' }"
hide-view-list-button
@created="onPatientCreatedRapido"
/>
@@ -1477,4 +1489,9 @@ const heroDateText = computed(() => {
transition: background .15s;
}
.aev2-picker-item:hover { background: var(--aev2-pill-bg); }
.aev2-picker-item--blocked {
opacity: .6;
cursor: not-allowed;
}
.aev2-picker-item--blocked:hover { background: transparent; }
</style>