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:
@@ -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>
|
||||
|
||||
@@ -333,6 +333,33 @@ describe('selectPaciente / clearPaciente', () => {
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
});
|
||||
|
||||
it('selectPaciente copia status pro form quando Ativo', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectPaciente } = setup({ composer });
|
||||
selectPaciente({ id: 'p-1', nome: 'Ana', status: 'Ativo' });
|
||||
expect(composer.form.value.paciente_id).toBe('p-1');
|
||||
expect(composer.form.value.paciente_status).toBe('Ativo');
|
||||
});
|
||||
|
||||
it('selectPaciente bloqueia paciente Arquivado (defesa em camadas)', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectPaciente, pacientePickerOpen } = setup({ composer });
|
||||
pacientePickerOpen.value = true;
|
||||
selectPaciente({ id: 'p-arq', nome: 'Ana', status: 'Arquivado' });
|
||||
// Form nao deve ter sido tocado
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
expect(composer.form.value.paciente_status).toBeFalsy();
|
||||
// Picker permanece aberto pro user escolher outro
|
||||
expect(pacientePickerOpen.value).toBe(true);
|
||||
});
|
||||
|
||||
it('selectPaciente bloqueia paciente Inativo', () => {
|
||||
const composer = makeComposer();
|
||||
const { selectPaciente } = setup({ composer });
|
||||
selectPaciente({ id: 'p-ina', nome: 'Bruno', status: 'Inativo' });
|
||||
expect(composer.form.value.paciente_id).toBe(null);
|
||||
});
|
||||
|
||||
it('clearPaciente limpa form + samePatientConflict', () => {
|
||||
const composer = makeComposer({
|
||||
formExtra: { paciente_id: 'p-1', paciente_nome: 'Ana', paciente_avatar: 'url' }
|
||||
|
||||
@@ -286,9 +286,18 @@ export function useAgendaEventPickerBilling({
|
||||
|
||||
function selectPaciente(p) {
|
||||
if (!p?.id) return;
|
||||
// Bloqueia clique em paciente arquivado/inativo — defesa em camadas:
|
||||
// o template do picker ja marca esses items como disabled, mas se
|
||||
// alguem chamar selectPaciente programaticamente (cache stale, etc),
|
||||
// a regra precisa valer.
|
||||
if (p.status && p.status !== 'Ativo') return;
|
||||
composer.form.value.paciente_id = p.id;
|
||||
composer.form.value.paciente_nome = p.nome || '';
|
||||
composer.form.value.paciente_avatar = p.avatar_url || '';
|
||||
// Sem isso, form.paciente_status fica '' e canSave nao consegue
|
||||
// aplicar getPatientAgendaPermissions — qualquer falha do filtro
|
||||
// acima vira sessao criavel com paciente fora do escopo.
|
||||
composer.form.value.paciente_status = p.status || '';
|
||||
pacientePickerOpen.value = false;
|
||||
}
|
||||
|
||||
@@ -296,6 +305,7 @@ export function useAgendaEventPickerBilling({
|
||||
composer.form.value.paciente_id = null;
|
||||
composer.form.value.paciente_nome = '';
|
||||
composer.form.value.paciente_avatar = '';
|
||||
composer.form.value.paciente_status = '';
|
||||
actions.samePatientConflict.value = null;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user