diff --git a/src/layout/melissa/MelissaPaciente.vue b/src/layout/melissa/MelissaPaciente.vue index 5772a4b..ba9add7 100644 --- a/src/layout/melissa/MelissaPaciente.vue +++ b/src/layout/melissa/MelissaPaciente.vue @@ -18,11 +18,11 @@ * * Prefixo CSS: .mpa-* (Melissa PAciente). */ -import { ref, computed, watch, nextTick, onMounted, onBeforeUnmount } from 'vue'; +import { ref, computed, watch, nextTick, inject, onMounted, onBeforeUnmount } from 'vue'; import { useRouter } from 'vue-router'; import { useToast } from 'primevue/usetoast'; import { useConversationDrawerStore } from '@/stores/conversationDrawerStore'; -import { supabase } from '@/lib/supabase/client'; +import { MELISSA_AGENDA_KEY } from './composables/useMelissaAgenda'; import DocumentsListPage from '@/features/documents/DocumentsListPage.vue'; import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue'; import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue'; @@ -32,7 +32,6 @@ import { usePatientFinancial } from '@/features/patients/composables/usePatientF import { usePatientMessages } from '@/features/patients/composables/usePatientMessages'; import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments'; import { usePatientRecurrences } from '@/features/patients/composables/usePatientRecurrences'; -import { useRecurrence } from '@/features/agenda/composables/useRecurrence'; import { pickField, calcAge, @@ -44,7 +43,6 @@ import { fmtDayShort, fmtRecurrenceLabel, fmtRecurrenceFim, - WEEKDAY_LABEL as WEEKDAY_LABEL_BLOCK, fmtCPF, fmtRG, fmtGender, @@ -70,6 +68,11 @@ const router = useRouter(); const toast = useToast(); const conversationDrawerStore = useConversationDrawerStore(); +// Inject do MelissaLayout — da acesso ao AgendaEventDialog global, +// suas mutations (onCreateEventoForPatient) e o flag dialogOpen pra +// detectar fechamento e refetchar sessions/recorrencias. +const melissaAgenda = inject(MELISSA_AGENDA_KEY, null); + // ── Composables de dados ─────────────────────────────────── const detail = usePatientDetail(); const sessionsHook = usePatientSessions(); @@ -77,7 +80,6 @@ const financialHook = usePatientFinancial(); const messagesHook = usePatientMessages(); const documentsHook = usePatientDocuments(); const recorrenciasHook = usePatientRecurrences(); -const recurrenceHook = useRecurrence(); // ── Breakpoints + drawer ─────────────────────────────────── const drawerOpen = ref(false); @@ -456,85 +458,8 @@ function addFinancial() { novoLancOpen.value = true; } -// Atalho: navega pra aba Agenda + abre dialog de nova sessao. -// Frequencia espelha AgendaEventDialog: avulsa | semanal | quinzenal | -// diasEspecificos. Avulsa cria 1 row em agenda_eventos; demais criam -// regra em recurrence_rules via useRecurrence. -const novaSessaoOpen = ref(false); -const novaSessaoForm = ref({ - tipo: 'sessao', - data: '', - hora: '', - duracao_min: 50, - modalidade: 'presencial', - titulo_custom: '', - observacoes: '', - freq: 'avulsa', - diasSelecionados: [], - qtdMode: '12', - qtdCustom: 12 -}); -const FREQ_OPCOES = [ - { value: 'avulsa', label: 'Avulsa' }, - { value: 'semanal', label: 'Semanal' }, - { value: 'quinzenal', label: 'Quinzenal' }, - { value: 'diasEspecificos', label: 'Dias específicos' } -]; -const DIAS_SEMANA_OPCOES = [ - { value: 1, short: 'Seg' }, - { value: 2, short: 'Ter' }, - { value: 3, short: 'Qua' }, - { value: 4, short: 'Qui' }, - { value: 5, short: 'Sex' }, - { value: 6, short: 'Sáb' }, - { value: 0, short: 'Dom' } -]; -const QTD_SESSOES_OPCOES = [ - { value: '4', label: '4 sessões' }, - { value: '8', label: '8 sessões' }, - { value: '12', label: '12 sessões' }, - { value: 'personalizar', label: 'Personalizar' } -]; - -function toggleDiaSelecionado(d) { - const arr = [...(novaSessaoForm.value.diasSelecionados || [])]; - const idx = arr.indexOf(d); - if (idx === -1) arr.push(d); - else arr.splice(idx, 1); - novaSessaoForm.value.diasSelecionados = arr; -} - -const qtdSessoesEfetiva = computed(() => { - const f = novaSessaoForm.value; - if (f.qtdMode === 'personalizar') return Number(f.qtdCustom) || null; - return Number(f.qtdMode) || null; -}); - -// Label dinamico do botao "Salvar" -const novaSessaoCtaLabel = computed(() => { - return novaSessaoForm.value.freq === 'avulsa' ? 'Agendar sessão' : 'Criar recorrência'; -}); -const SESSAO_TIPOS = [ - { label: 'Sessão', value: 'sessao' }, - { label: 'Primeira consulta', value: 'primeira' }, - { label: 'Retorno', value: 'retorno' }, - { label: 'Avaliação', value: 'avaliacao' }, - { label: 'Devolutiva', value: 'devolutiva' } -]; -const SESSAO_DURACOES = [ - { label: '30 min', value: 30 }, - { label: '40 min', value: 40 }, - { label: '45 min', value: 45 }, - { label: '50 min', value: 50 }, - { label: '55 min', value: 55 }, - { label: '1 hora', value: 60 }, - { label: '1h 30', value: 90 }, - { label: '2 horas', value: 120 } -]; -const SESSAO_MODALIDADES = [ - { label: 'Presencial', value: 'presencial' }, - { label: 'Online (vídeo)', value: 'online' } -]; +// Abre o AgendaEventDialog GLOBAL (mesmo que MelissaAgenda usa) com +// paciente pre-selecionado. O dialog vive no MelissaLayout via provide. function goAgendar() { activeTab.value = 'agenda'; if (isMobile.value) drawerOpen.value = false; @@ -542,132 +467,30 @@ function goAgendar() { toast.add({ severity: 'warn', summary: 'Paciente sem ID', life: 2500 }); return; } - // Default: amanha as 09:00, sessao 50min presencial. - const tomorrow = new Date(); - tomorrow.setDate(tomorrow.getDate() + 1); - novaSessaoForm.value = { - tipo: 'sessao', - data: tomorrow.toISOString().slice(0, 10), - hora: '09:00', - duracao_min: 50, - modalidade: 'presencial', - titulo_custom: '', - observacoes: '', - freq: 'avulsa', - diasSelecionados: [], - qtdMode: '12', - qtdCustom: 12 - }; - novaSessaoOpen.value = true; + if (!melissaAgenda || typeof melissaAgenda.onCreateEventoForPatient !== 'function') { + toast.add({ + severity: 'warn', + summary: 'Agenda indisponível', + detail: 'Aguarde a agenda carregar e tente novamente.', + life: 3000 + }); + return; + } + melissaAgenda.onCreateEventoForPatient(props.patientId); } -async function salvarSessao() { - const f = novaSessaoForm.value; - if (!f.data) { - toast.add({ severity: 'warn', summary: 'Data obrigatória', life: 2500 }); - return; - } - if (!f.hora) { - toast.add({ severity: 'warn', summary: 'Hora obrigatória', life: 2500 }); - return; - } - const [y, m, d] = f.data.split('-').map(Number); - const [hh, mm] = f.hora.split(':').map(Number); - const inicio = new Date(y, (m || 1) - 1, d || 1, hh || 0, mm || 0, 0); - if (Number.isNaN(inicio.getTime())) { - toast.add({ severity: 'warn', summary: 'Data/hora inválida', life: 2500 }); - return; - } - // Caminho RECORRENTE: cria regra em recurrence_rules via useRecurrence. - // Ocorrencias sao geradas dinamicamente. - if (f.freq !== 'avulsa') { - // Mapeamento freq -> { type, interval, weekdays } - let type, interval, weekdays; - if (f.freq === 'semanal') { - type = 'weekly'; - interval = 1; - weekdays = [inicio.getDay()]; - } else if (f.freq === 'quinzenal') { - type = 'biweekly'; - interval = 2; - weekdays = [inicio.getDay()]; - } else if (f.freq === 'diasEspecificos') { - if (!f.diasSelecionados.length) { - toast.add({ severity: 'warn', summary: 'Selecione ao menos um dia da semana', life: 3000 }); - return; - } - type = 'custom_weekdays'; - interval = 1; - weekdays = [...f.diasSelecionados].sort(); - } - const max = qtdSessoesEfetiva.value; - if (!max || max < 1) { - toast.add({ severity: 'warn', summary: 'Quantidade de sessões inválida', life: 2500 }); - return; - } - try { - const { data: userData } = await supabase.auth.getUser(); - const ownerId = userData?.user?.id; - const rule = { - patient_id: props.patientId, - owner_id: ownerId, - type, - interval, - weekdays, - start_date: f.data, - end_date: null, - max_occurrences: max, - start_time: f.hora, - duration_min: Number(f.duracao_min) || 50, - modalidade: f.modalidade, - titulo_custom: String(f.titulo_custom || '').trim() || null, - observacoes: String(f.observacoes || '').trim() || null, - status: 'ativo' - }; - await recurrenceHook.createRule(rule); - toast.add({ - severity: 'success', - summary: 'Recorrência criada', - detail: `${max} sessões previstas. Veja em "Recorrências".`, - life: 3000 - }); - novaSessaoOpen.value = false; +// 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) ]); - } catch (e) { - toast.add({ - severity: 'error', - summary: 'Falha ao criar recorrência', - detail: e?.message || 'Erro inesperado', - life: 4000 - }); } - return; - } - - // Caminho SESSAO UNICA: insert direto em agenda_eventos. - const fim = new Date(inicio.getTime() + (Number(f.duracao_min) || 50) * 60 * 1000); - const result = await sessionsHook.createSession(props.patientId, { - inicio_em: inicio.toISOString(), - fim_em: fim.toISOString(), - tipo: f.tipo, - modalidade: f.modalidade, - titulo_custom: f.titulo_custom, - observacoes: f.observacoes }); - if (result.ok) { - toast.add({ severity: 'success', summary: 'Sessão agendada', life: 2200 }); - novaSessaoOpen.value = false; - } else { - toast.add({ - severity: 'error', - summary: 'Falha ao agendar', - detail: result.error || 'Erro inesperado', - life: 4000 - }); - } } async function salvarLancamento() { const f = novoLancForm.value; @@ -2381,185 +2204,11 @@ onBeforeUnmount(() => { - - - -
-
- - -
-
- -