From 42a39ed3ea5afefaf3b72d7d0995fda6e5cd9e7c Mon Sep 17 00:00:00 2001 From: Leonardo Date: Fri, 8 May 2026 11:51:43 -0300 Subject: [PATCH] MelissaPaciente: dialog Nova Sessao usa "Frequencia" estilo AgendaEventDialog MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User pediu pra trocar o checkbox "Repetir semanalmente" + radios pelo mesmo widget de Frequencia que existe no AgendaEventDialog. Replicado 1:1 o pattern (chips + qtd sessoes). REMOVIDO - Checkbox "Repetir semanalmente" - 3 radios de fim_tipo (open/count/data) - Inputs inline associados (fim_count, fim_data) ADICIONADO no form - novaSessaoForm.freq: 'avulsa' (default) | 'semanal' | 'quinzenal' | 'diasEspecificos' - novaSessaoForm.diasSelecionados: array (so usado em diasEspecificos) - novaSessaoForm.qtdMode: '4' | '8' | '12' | 'personalizar' - novaSessaoForm.qtdCustom: number (so usado em personalizar) ADICIONADO catalogos (FREQ_OPCOES, DIAS_SEMANA_OPCOES, QTD_SESSOES_OPCOES) e helpers (toggleDiaSelecionado, qtdSessoesEfetiva computed, novaSessaoCtaLabel computed). ADICIONADO no template: - Chips horizontais "Avulsa / Semanal / Quinzenal / Dias específicos" (estilo .mpa-freq-chip — pill arredondado, primary quando active) - Preview com icon refresh: "Toda quarta, às 14:00" / "A cada 2 semanas, toda quarta..." - Grid de dias da semana (Seg Ter Qua Qui Sex Sab Dom) so quando diasEspecificos - Quantidade de sessoes: chips "4 sessoes / 8 / 12 / Personalizar" + InputNumber show-buttons quando personalizar - Label dinamica do CTA: "Agendar sessão" (avulsa) / "Criar recorrência" LOGICA salvarSessao mapeia freq -> recurrence_rules: - avulsa: caminho original (createSession + INSERT agenda_eventos) - semanal: type='weekly', interval=1, weekdays=[dow] - quinzenal: type='biweekly', interval=2, weekdays=[dow] - diasEspecificos: type='custom_weekdays', interval=1, weekdays=[selecionados] Sempre com max_occurrences (qtd efetiva) — sem mais open-ended por default. Toast detalha "{N} sessoes previstas". Validacoes: - diasEspecificos exige >=1 dia selecionado (toast warn) - qtd efetiva >= 1 (cobrindo personalizar invalido) CSS: ~120L (substitui o bloco .mpa-recur antigo). Usa accent var --p-primary-color pra match do app theme. .mpa-freq-chip / .mpa-dia-chip hover/active states. .mpa-freq-preview com bg color-mix do primary. ESLint: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/layout/melissa/MelissaPaciente.vue | 344 +++++++++++++++++-------- 1 file changed, 239 insertions(+), 105 deletions(-) diff --git a/src/layout/melissa/MelissaPaciente.vue b/src/layout/melissa/MelissaPaciente.vue index b29e637..ac40248 100644 --- a/src/layout/melissa/MelissaPaciente.vue +++ b/src/layout/melissa/MelissaPaciente.vue @@ -44,6 +44,7 @@ import { fmtDayShort, fmtRecurrenceLabel, fmtRecurrenceFim, + WEEKDAY_LABEL as WEEKDAY_LABEL_BLOCK, fmtCPF, fmtRG, fmtGender, @@ -456,6 +457,9 @@ function addFinancial() { } // 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', @@ -465,11 +469,50 @@ const novaSessaoForm = ref({ modalidade: 'presencial', titulo_custom: '', observacoes: '', - // Recorrencia (integra com useRecurrence — schema recurrence_rules) - repetir: false, - fim_tipo: 'open', // open | data | count - fim_data: '', - fim_count: 12 + 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' }, @@ -510,10 +553,10 @@ function goAgendar() { modalidade: 'presencial', titulo_custom: '', observacoes: '', - repetir: false, - fim_tipo: 'open', - fim_data: '', - fim_count: 12 + freq: 'avulsa', + diasSelecionados: [], + qtdMode: '12', + qtdCustom: 12 }; novaSessaoOpen.value = true; } @@ -536,15 +579,30 @@ async function salvarSessao() { } // 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; + // 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(); } - 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 }); + const max = qtdSessoesEfetiva.value; + if (!max || max < 1) { + toast.add({ severity: 'warn', summary: 'Quantidade de sessões inválida', life: 2500 }); return; } try { @@ -553,12 +611,12 @@ async function salvarSessao() { const rule = { patient_id: props.patientId, owner_id: ownerId, - type: 'weekly', - interval: 1, - weekdays: [inicio.getDay()], + type, + interval, + weekdays, 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, + end_date: null, + max_occurrences: max, start_time: f.hora, duration_min: Number(f.duracao_min) || 50, modalidade: f.modalidade, @@ -570,12 +628,10 @@ async function salvarSessao() { toast.add({ severity: 'success', summary: 'Recorrência criada', - detail: 'A série semanal está ativa. Veja em "Recorrências".', + detail: `${max} sessões previstas. Veja em "Recorrências".`, life: 3000 }); novaSessaoOpen.value = false; - // Recarrega sessoes (caso start_date seja hoje) + recorrencias - // (a regra recem-criada precisa aparecer no bloco da Tab Agenda). await Promise.all([ sessionsHook.load(props.patientId), recorrenciasHook.load(props.patientId) @@ -2404,46 +2460,77 @@ onBeforeUnmount(() => { /> - +
-