diff --git a/src/layout/melissa/MelissaPaciente.vue b/src/layout/melissa/MelissaPaciente.vue index ef95879..c9aac84 100644 --- a/src/layout/melissa/MelissaPaciente.vue +++ b/src/layout/melissa/MelissaPaciente.vue @@ -22,6 +22,7 @@ import { ref, computed, watch, nextTick, 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 DocumentsListPage from '@/features/documents/DocumentsListPage.vue'; import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue'; import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.vue'; @@ -30,6 +31,7 @@ import { usePatientSessions } from '@/features/patients/composables/usePatientSe import { usePatientFinancial } from '@/features/patients/composables/usePatientFinancial'; import { usePatientMessages } from '@/features/patients/composables/usePatientMessages'; import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments'; +import { useRecurrence } from '@/features/agenda/composables/useRecurrence'; import { pickField, calcAge, @@ -70,6 +72,7 @@ const sessionsHook = usePatientSessions(); const financialHook = usePatientFinancial(); const messagesHook = usePatientMessages(); const documentsHook = usePatientDocuments(); +const recurrenceHook = useRecurrence(); // ── Breakpoints + drawer ─────────────────────────────────── const drawerOpen = ref(false); @@ -425,7 +428,12 @@ const novaSessaoForm = ref({ duracao_min: 50, modalidade: 'presencial', titulo_custom: '', - observacoes: '' + observacoes: '', + // Recorrencia (integra com useRecurrence — schema recurrence_rules) + repetir: false, + fim_tipo: 'open', // open | data | count + fim_data: '', + fim_count: 12 }); const SESSAO_TIPOS = [ { label: 'Sessão', value: 'sessao' }, @@ -465,7 +473,11 @@ function goAgendar() { duracao_min: 50, modalidade: 'presencial', titulo_custom: '', - observacoes: '' + observacoes: '', + repetir: false, + fim_tipo: 'open', + fim_data: '', + fim_count: 12 }; novaSessaoOpen.value = true; } @@ -479,9 +491,6 @@ async function salvarSessao() { toast.add({ severity: 'warn', summary: 'Hora obrigatória', life: 2500 }); return; } - // Monta inicio_em + fim_em a partir de data + hora + duracao_min. - // Local time -> ISO. Sem timezone explicit, usa o do browser (consistente - // com o pattern do AgendaEventDialog e MelissaAgenda). 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); @@ -489,8 +498,61 @@ async function salvarSessao() { toast.add({ severity: 'warn', summary: 'Data/hora inválida', life: 2500 }); return; } - const fim = new Date(inicio.getTime() + (Number(f.duracao_min) || 50) * 60 * 1000); + // Caminho RECORRENTE: cria regra em recurrence_rules via useRecurrence. + // Ocorrencias sao geradas dinamicamente — nao precisa popular agenda_eventos + // futuros (sessoes confirmadas/realizadas viram rows reais sob demanda). + if (f.repetir) { + if (f.fim_tipo === 'data' && !f.fim_data) { + toast.add({ severity: 'warn', summary: 'Informe a data de fim', life: 2500 }); + return; + } + if (f.fim_tipo === 'count' && (!f.fim_count || Number(f.fim_count) < 1)) { + toast.add({ severity: 'warn', summary: 'Informe o número de ocorrências', 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: 'weekly', + interval: 1, + weekdays: [inicio.getDay()], + start_date: f.data, + end_date: f.fim_tipo === 'data' ? f.fim_data : null, + max_occurrences: f.fim_tipo === 'count' ? Number(f.fim_count) : null, + 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: 'A série semanal está ativa. Veja em "Recorrências".', + life: 3000 + }); + novaSessaoOpen.value = false; + // Recarrega sessoes do paciente (caso start_date seja hoje). + await sessionsHook.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(), @@ -2197,6 +2259,49 @@ onBeforeUnmount(() => { class="w-full" /> + + +