AgendaEventDialog: props lockType + lockPatient + slot #headerLeft (additivos)
User escolheu caminho A: modificar AgendaEventDialog em vez de copiar.
Mudancas SAO ADITIVAS — comportamento atual dos 5 callsites legacy
(TherapistDashboard, PatientsListPage, MelissaAgenda,
MelissaAgendamentosRecebidos, MelissaLayout) preservado.
VALIDACAO: rodei os 7 spec files do agenda — 301 testes passaram.
Zero regressao.
ADICIONADO em src/features/agenda/components/AgendaEventDialog.vue
- Prop lockType (Boolean, default false): pula step 1 (escolha de tipo)
e vai direto pro form. Watch immediate em [lockType, modelValue]
forca step.value=2 quando lockType=true e dialog abre.
- Prop lockPatient (Boolean, default false): esconde botoes "trocar"/
"limpar" do paciente. Mostra icon de lock com tooltip "Paciente do
prontuario". Cobre o cenario "criar sessao pra paciente fixo" sem
precisar do isEdit que o patientLocked computed exige.
- Slot #headerLeft: substitui o conteudo esquerdo do header (default
era header-dot + headerTitle + previewRange). Permite callsites
customizar com icon+title+subtitle proprios sem mexer no resto do
header (X / actions).
- v-if no Step 1: "step === 1 && !lockType"
- v-if nos buttons trocar/limpar: "!patientLocked && !lockPatient"
- Lock icon: "patientLocked || lockPatient" + tooltip dinamico
MELISSAPACIENTE.VUE
- Reverte o inject-only do commit 88dff50.
- Mantem o inject(MELISSA_AGENDA_KEY) APENAS pra LER dados pesados
(commitmentOptions, workRules, allEvents, agendaSettings, feriados,
ownerId, tenantId) — evita re-fetch.
- State LOCAL pro dialog: sessaoDialogOpen, sessaoDialogEventRow,
sessaoDialogStartISO, sessaoDialogEndISO. Nao colide com o dialog
global do MelissaLayout que continua na Agenda.
- goAgendar(): inicializa eventRow com paciente_id fixo + tipo='sessao'
+ defaults razoaveis (proximo slot 15min + duracao da agenda),
abre o dialog local.
- Handlers onSessaoDialogSave / onSessaoDialogDelete delegam pros
handlers globais (M.onDialogSave/Delete) e ao final refetcham
sessions+recorrencias do paciente in-place.
- Render <AgendaEventDialog> com lock-type=true + lock-patient=true
+ slot #headerLeft custom (icon pi-calendar-plus em quadrado
primary 40x40 + "Nova sessão" + nome do paciente como subtitulo).
Resultado: prontuario tem o MESMO componente da Agenda (form completo
de sessao, frequencia com preview de ocorrencias + conflitos,
vinculacao de servicos/billing, edicao de serie, etc) mas pre-fixado
no contexto do paciente, com header proprio e single source of truth.
ESLint: 31 errors pre-existentes em ambos arquivos (variaveis declaradas
nao usadas — confirmado via git stash baseline). 0 errors da minha
mudanca.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -23,6 +23,7 @@ import { useRouter } from 'vue-router';
|
||||
import { useToast } from 'primevue/usetoast';
|
||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
||||
import { MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda';
|
||||
import AgendaEventDialog from '@/features/agenda/components/AgendaEventDialog.vue';
|
||||
import DocumentsListPage from '@/features/documents/DocumentsListPage.vue';
|
||||
import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue';
|
||||
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue';
|
||||
@@ -458,8 +459,16 @@ function addFinancial() {
|
||||
novoLancOpen.value = true;
|
||||
}
|
||||
|
||||
// Abre o AgendaEventDialog GLOBAL (mesmo que MelissaAgenda usa) com
|
||||
// paciente pre-selecionado. O dialog vive no MelissaLayout via provide.
|
||||
// Abre AgendaEventDialog LOCAL (instancia propria) com paciente fixo
|
||||
// e tipo='sessao' travado. Dados pesados (commitmentOptions, workRules,
|
||||
// allEvents, agendaSettings, feriados, ownerId, tenantId) vem via inject
|
||||
// do MelissaLayout. State do dialog (open/eventRow/start/end) e LOCAL
|
||||
// pra nao colidir com o dialog global da Agenda.
|
||||
const sessaoDialogOpen = ref(false);
|
||||
const sessaoDialogEventRow = ref(null);
|
||||
const sessaoDialogStartISO = ref(null);
|
||||
const sessaoDialogEndISO = ref(null);
|
||||
|
||||
function goAgendar() {
|
||||
activeTab.value = 'agenda';
|
||||
if (isMobile.value) drawerOpen.value = false;
|
||||
@@ -467,7 +476,7 @@ function goAgendar() {
|
||||
toast.add({ severity: 'warn', summary: 'Paciente sem ID', life: 2500 });
|
||||
return;
|
||||
}
|
||||
if (!melissaAgenda || typeof melissaAgenda.onCreateEventoForPatient !== 'function') {
|
||||
if (!melissaAgenda?.ownerId?.value) {
|
||||
toast.add({
|
||||
severity: 'warn',
|
||||
summary: 'Agenda indisponível',
|
||||
@@ -476,21 +485,58 @@ function goAgendar() {
|
||||
});
|
||||
return;
|
||||
}
|
||||
melissaAgenda.onCreateEventoForPatient(props.patientId);
|
||||
// Defaults razoaveis: proximo slot 15min + duracao padrao da agenda.
|
||||
const durMin =
|
||||
melissaAgenda.settings?.value?.session_duration_min ??
|
||||
melissaAgenda.settings?.value?.duracao_padrao_minutos ??
|
||||
50;
|
||||
const base = new Date();
|
||||
base.setSeconds(0, 0);
|
||||
const remainder = base.getMinutes() % 15;
|
||||
if (remainder !== 0) base.setMinutes(base.getMinutes() + (15 - remainder));
|
||||
|
||||
sessaoDialogEventRow.value = {
|
||||
owner_id: melissaAgenda.ownerId.value,
|
||||
terapeuta_id: null,
|
||||
paciente_id: String(props.patientId),
|
||||
tipo: 'sessao',
|
||||
status: 'agendado',
|
||||
titulo: null,
|
||||
observacoes: null,
|
||||
visibility_scope: 'public',
|
||||
determined_commitment_id: null
|
||||
};
|
||||
sessaoDialogStartISO.value = base.toISOString();
|
||||
sessaoDialogEndISO.value = new Date(base.getTime() + durMin * 60000).toISOString();
|
||||
sessaoDialogOpen.value = true;
|
||||
}
|
||||
|
||||
// Watch o dialogOpen do AgendaEventDialog: quando ele fecha (true -> false)
|
||||
// refetcha sessoes + recorrencias do paciente. Cobre tanto save quanto
|
||||
// close-sem-salvar (idempotente; load() ja eh barato).
|
||||
if (melissaAgenda?.dialogOpen) {
|
||||
watch(melissaAgenda.dialogOpen, async (now, prev) => {
|
||||
if (prev && !now && props.patientId) {
|
||||
await Promise.all([
|
||||
sessionsHook.load(props.patientId),
|
||||
recorrenciasHook.load(props.patientId)
|
||||
]);
|
||||
}
|
||||
});
|
||||
// Handlers do dialog local — delegam pros handlers globais do useMelissaAgenda
|
||||
// (M.onDialogSave/Delete) que ja sabem mexer com agenda_eventos +
|
||||
// recurrence_rules + exceptions. Apos fechar, refetcha sessions+recorrencias.
|
||||
async function onSessaoDialogSave(payload) {
|
||||
if (typeof melissaAgenda?.onDialogSave === 'function') {
|
||||
await melissaAgenda.onDialogSave(payload);
|
||||
}
|
||||
sessaoDialogOpen.value = false;
|
||||
if (props.patientId) {
|
||||
await Promise.all([
|
||||
sessionsHook.load(props.patientId),
|
||||
recorrenciasHook.load(props.patientId)
|
||||
]);
|
||||
}
|
||||
}
|
||||
async function onSessaoDialogDelete(payload) {
|
||||
if (typeof melissaAgenda?.onDialogDelete === 'function') {
|
||||
await melissaAgenda.onDialogDelete(payload);
|
||||
}
|
||||
sessaoDialogOpen.value = false;
|
||||
if (props.patientId) {
|
||||
await Promise.all([
|
||||
sessionsHook.load(props.patientId),
|
||||
recorrenciasHook.load(props.patientId)
|
||||
]);
|
||||
}
|
||||
}
|
||||
async function salvarLancamento() {
|
||||
const f = novoLancForm.value;
|
||||
@@ -2204,11 +2250,53 @@ onBeforeUnmount(() => {
|
||||
</template>
|
||||
</Dialog>
|
||||
|
||||
<!-- Dialog Nova Sessao removido — agora usa o AgendaEventDialog
|
||||
GLOBAL do MelissaLayout (mesmo que a Agenda usa). MelissaPaciente
|
||||
dispara via melissaAgenda.onCreateEventoForPatient(patientId).
|
||||
Watch em melissaAgenda.dialogOpen refetcha sessions+recorrencias
|
||||
quando o dialog fecha (cobre save e cancel). -->
|
||||
<!-- AgendaEventDialog LOCAL do prontuario.
|
||||
- Reusa o componente da Agenda (sem duplicar codigo) com 2 props
|
||||
novas: lockType (pula step 1) + lockPatient (esconde trocar/limpar).
|
||||
- Slot #headerLeft sobrescreve o header padrao com icon + "Nova
|
||||
sessao" + nome do paciente.
|
||||
- State LOCAL (sessaoDialog*) — nao colide com o dialog global do
|
||||
MelissaLayout que continua na Agenda.
|
||||
- Dados pesados (commitmentOptions, workRules, allEvents,
|
||||
agendaSettings, feriados, ownerId, tenantId) vem via inject do
|
||||
MelissaLayout — evita re-fetch.
|
||||
- Save/Delete delegam pros handlers globais (M.onDialogSave/Delete)
|
||||
que ja sabem orquestrar agenda_eventos + recurrence_rules. -->
|
||||
<AgendaEventDialog
|
||||
v-if="melissaAgenda"
|
||||
v-model="sessaoDialogOpen"
|
||||
:event-row="sessaoDialogEventRow"
|
||||
:initial-start-i-s-o="sessaoDialogStartISO"
|
||||
:initial-end-i-s-o="sessaoDialogEndISO"
|
||||
:owner-id="melissaAgenda.ownerId?.value || ''"
|
||||
:tenant-id="melissaAgenda.clinicTenantId?.value || ''"
|
||||
:commitment-options="melissaAgenda.commitmentOptions?.value || []"
|
||||
:work-rules="melissaAgenda.workRules?.value || []"
|
||||
:blocked-dates="[]"
|
||||
:agenda-settings="melissaAgenda.settings?.value || null"
|
||||
:all-events="melissaAgenda.allEventsForDialog?.value || []"
|
||||
:pausas-semanais="melissaAgenda.settings?.value?.pausas_semanais || []"
|
||||
:feriados="melissaAgenda.feriados?.value || []"
|
||||
new-patient-route="/therapist/patients/cadastro"
|
||||
:lock-type="true"
|
||||
:lock-patient="true"
|
||||
@save="onSessaoDialogSave"
|
||||
@delete="onSessaoDialogDelete"
|
||||
@updateSeriesEvent="onSessaoDialogSave"
|
||||
@editSeriesOccurrence="onSessaoDialogSave"
|
||||
>
|
||||
<template #headerLeft>
|
||||
<div class="mpa-dlg-head">
|
||||
<div class="mpa-dlg-head__icon">
|
||||
<i class="pi pi-calendar-plus" />
|
||||
</div>
|
||||
<div class="mpa-dlg-head__text">
|
||||
<div class="mpa-dlg-head__title">Nova sessão</div>
|
||||
<div class="mpa-dlg-head__sub">{{ dash(nomeCompleto) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</AgendaEventDialog>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
|
||||
Reference in New Issue
Block a user