MelissaPaciente: usa AgendaEventDialog GLOBAL via inject (em vez de dialog local)
User pediu pra trazer o AgendaEventDialog completo da Agenda pra dentro do prontuario. Estrategia: NAO duplicar o dialog (que ja vive no MelissaLayout). Em vez disso, reusar via provide/inject — pattern que ja existe (MELISSA_AGENDA_KEY). NOVO em src/layout/melissa/composables/useMelissaAgenda.js - onCreateEventoForPatient(patientId) — espelha onCreateEvento (defaults hoje proximo slot 15min, duracao default), mas injeta paciente_id no dialogEventRow. Adicionada ao return do composable. MELISSAPACIENTE.VUE - inject(MELISSA_AGENDA_KEY) pra acessar a instancia do useMelissaAgenda do MelissaLayout. - goAgendar(): chama melissaAgenda.onCreateEventoForPatient(patientId) (defensive: warn toast se nao tem inject ou funcao). - Watch em melissaAgenda.dialogOpen pra refetchar sessions+recorrencias quando o dialog fecha (true -> false), independente se foi save ou cancel. REMOVIDO (sem mais necessario — AgendaEventDialog faz tudo) - Refs novaSessaoOpen, novaSessaoForm - Catalogos FREQ_OPCOES, DIAS_SEMANA_OPCOES, QTD_SESSOES_OPCOES, SESSAO_TIPOS, SESSAO_DURACOES, SESSAO_MODALIDADES - Helpers toggleDiaSelecionado, qtdSessoesEfetiva, novaSessaoCtaLabel - Function salvarSessao (~110L de logica avulsa+recorrencia) - Import supabase (nao usado direto mais) - Import useRecurrence (era pro createRule no salvarSessao) - Import WEEKDAY_LABEL_BLOCK (era pro preview de freq) - Template <Dialog> Nova Sessao com header custom + form + freq chips + qtd sessoes + footer (~180L) Resultado: MelissaPaciente fica mais enxuto e usa exatamente o mesmo dialog completo que MelissaAgenda — todos os recursos do AgendaEventDialog (tipos de evento, paciente picker, comprometimento de servicos/billing, freq com preview de ocorrencias + conflitos, validacao por work rules, edicao de serie etc) ficam disponiveis no prontuario sem duplicacao. ESLint: 0 errors. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -18,11 +18,11 @@
|
|||||||
*
|
*
|
||||||
* Prefixo CSS: .mpa-* (Melissa PAciente).
|
* 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 { useRouter } from 'vue-router';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
import { useConversationDrawerStore } from '@/stores/conversationDrawerStore';
|
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 DocumentsListPage from '@/features/documents/DocumentsListPage.vue';
|
||||||
import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue';
|
import PatientConversationsTab from '@/features/patients/prontuario/PatientConversationsTab.vue';
|
||||||
import PatientCadastroDialog from '@/components/ui/PatientCadastroDialog.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 { usePatientMessages } from '@/features/patients/composables/usePatientMessages';
|
||||||
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
|
import { usePatientDocuments } from '@/features/patients/composables/usePatientDocuments';
|
||||||
import { usePatientRecurrences } from '@/features/patients/composables/usePatientRecurrences';
|
import { usePatientRecurrences } from '@/features/patients/composables/usePatientRecurrences';
|
||||||
import { useRecurrence } from '@/features/agenda/composables/useRecurrence';
|
|
||||||
import {
|
import {
|
||||||
pickField,
|
pickField,
|
||||||
calcAge,
|
calcAge,
|
||||||
@@ -44,7 +43,6 @@ import {
|
|||||||
fmtDayShort,
|
fmtDayShort,
|
||||||
fmtRecurrenceLabel,
|
fmtRecurrenceLabel,
|
||||||
fmtRecurrenceFim,
|
fmtRecurrenceFim,
|
||||||
WEEKDAY_LABEL as WEEKDAY_LABEL_BLOCK,
|
|
||||||
fmtCPF,
|
fmtCPF,
|
||||||
fmtRG,
|
fmtRG,
|
||||||
fmtGender,
|
fmtGender,
|
||||||
@@ -70,6 +68,11 @@ const router = useRouter();
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const conversationDrawerStore = useConversationDrawerStore();
|
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 ───────────────────────────────────
|
// ── Composables de dados ───────────────────────────────────
|
||||||
const detail = usePatientDetail();
|
const detail = usePatientDetail();
|
||||||
const sessionsHook = usePatientSessions();
|
const sessionsHook = usePatientSessions();
|
||||||
@@ -77,7 +80,6 @@ const financialHook = usePatientFinancial();
|
|||||||
const messagesHook = usePatientMessages();
|
const messagesHook = usePatientMessages();
|
||||||
const documentsHook = usePatientDocuments();
|
const documentsHook = usePatientDocuments();
|
||||||
const recorrenciasHook = usePatientRecurrences();
|
const recorrenciasHook = usePatientRecurrences();
|
||||||
const recurrenceHook = useRecurrence();
|
|
||||||
|
|
||||||
// ── Breakpoints + drawer ───────────────────────────────────
|
// ── Breakpoints + drawer ───────────────────────────────────
|
||||||
const drawerOpen = ref(false);
|
const drawerOpen = ref(false);
|
||||||
@@ -456,85 +458,8 @@ function addFinancial() {
|
|||||||
novoLancOpen.value = true;
|
novoLancOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atalho: navega pra aba Agenda + abre dialog de nova sessao.
|
// Abre o AgendaEventDialog GLOBAL (mesmo que MelissaAgenda usa) com
|
||||||
// Frequencia espelha AgendaEventDialog: avulsa | semanal | quinzenal |
|
// paciente pre-selecionado. O dialog vive no MelissaLayout via provide.
|
||||||
// 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' }
|
|
||||||
];
|
|
||||||
function goAgendar() {
|
function goAgendar() {
|
||||||
activeTab.value = 'agenda';
|
activeTab.value = 'agenda';
|
||||||
if (isMobile.value) drawerOpen.value = false;
|
if (isMobile.value) drawerOpen.value = false;
|
||||||
@@ -542,132 +467,30 @@ function goAgendar() {
|
|||||||
toast.add({ severity: 'warn', summary: 'Paciente sem ID', life: 2500 });
|
toast.add({ severity: 'warn', summary: 'Paciente sem ID', life: 2500 });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Default: amanha as 09:00, sessao 50min presencial.
|
if (!melissaAgenda || typeof melissaAgenda.onCreateEventoForPatient !== 'function') {
|
||||||
const tomorrow = new Date();
|
toast.add({
|
||||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
severity: 'warn',
|
||||||
novaSessaoForm.value = {
|
summary: 'Agenda indisponível',
|
||||||
tipo: 'sessao',
|
detail: 'Aguarde a agenda carregar e tente novamente.',
|
||||||
data: tomorrow.toISOString().slice(0, 10),
|
life: 3000
|
||||||
hora: '09:00',
|
});
|
||||||
duracao_min: 50,
|
return;
|
||||||
modalidade: 'presencial',
|
}
|
||||||
titulo_custom: '',
|
melissaAgenda.onCreateEventoForPatient(props.patientId);
|
||||||
observacoes: '',
|
|
||||||
freq: 'avulsa',
|
|
||||||
diasSelecionados: [],
|
|
||||||
qtdMode: '12',
|
|
||||||
qtdCustom: 12
|
|
||||||
};
|
|
||||||
novaSessaoOpen.value = true;
|
|
||||||
}
|
}
|
||||||
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.
|
// Watch o dialogOpen do AgendaEventDialog: quando ele fecha (true -> false)
|
||||||
// Ocorrencias sao geradas dinamicamente.
|
// refetcha sessoes + recorrencias do paciente. Cobre tanto save quanto
|
||||||
if (f.freq !== 'avulsa') {
|
// close-sem-salvar (idempotente; load() ja eh barato).
|
||||||
// Mapeamento freq -> { type, interval, weekdays }
|
if (melissaAgenda?.dialogOpen) {
|
||||||
let type, interval, weekdays;
|
watch(melissaAgenda.dialogOpen, async (now, prev) => {
|
||||||
if (f.freq === 'semanal') {
|
if (prev && !now && props.patientId) {
|
||||||
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;
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
sessionsHook.load(props.patientId),
|
sessionsHook.load(props.patientId),
|
||||||
recorrenciasHook.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() {
|
async function salvarLancamento() {
|
||||||
const f = novoLancForm.value;
|
const f = novoLancForm.value;
|
||||||
@@ -2381,185 +2204,11 @@ onBeforeUnmount(() => {
|
|||||||
</template>
|
</template>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
<!-- Dialog: nova sessao na agenda. Mesma logica do Lancamento — form
|
<!-- Dialog Nova Sessao removido — agora usa o AgendaEventDialog
|
||||||
simples inline com tipo/data/hora/duracao/modalidade/titulo/obs.
|
GLOBAL do MelissaLayout (mesmo que a Agenda usa). MelissaPaciente
|
||||||
Header custom: icon + titulo + subtitulo com nome do paciente. -->
|
dispara via melissaAgenda.onCreateEventoForPatient(patientId).
|
||||||
<Dialog
|
Watch em melissaAgenda.dialogOpen refetcha sessions+recorrencias
|
||||||
v-model:visible="novaSessaoOpen"
|
quando o dialog fecha (cobre save e cancel). -->
|
||||||
modal
|
|
||||||
dismissable-mask
|
|
||||||
:style="{ width: '460px', maxWidth: '92vw' }"
|
|
||||||
>
|
|
||||||
<template #header>
|
|
||||||
<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>
|
|
||||||
<div class="mpa-novo-lanc">
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Tipo</label>
|
|
||||||
<Select
|
|
||||||
v-model="novaSessaoForm.tipo"
|
|
||||||
:options="SESSAO_TIPOS"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__row">
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Data *</label>
|
|
||||||
<InputText
|
|
||||||
v-model="novaSessaoForm.data"
|
|
||||||
type="date"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Hora *</label>
|
|
||||||
<InputText
|
|
||||||
v-model="novaSessaoForm.hora"
|
|
||||||
type="time"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__row">
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Duração</label>
|
|
||||||
<Select
|
|
||||||
v-model="novaSessaoForm.duracao_min"
|
|
||||||
:options="SESSAO_DURACOES"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Modalidade</label>
|
|
||||||
<Select
|
|
||||||
v-model="novaSessaoForm.modalidade"
|
|
||||||
:options="SESSAO_MODALIDADES"
|
|
||||||
option-label="label"
|
|
||||||
option-value="value"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Título <span class="mpa-novo-lanc__opt">(opcional)</span></label>
|
|
||||||
<InputText
|
|
||||||
v-model="novaSessaoForm.titulo_custom"
|
|
||||||
placeholder="Ex: Sessão de avaliação inicial"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="mpa-novo-lanc__field">
|
|
||||||
<label class="mpa-novo-lanc__label">Observações <span class="mpa-novo-lanc__opt">(opcional)</span></label>
|
|
||||||
<Textarea
|
|
||||||
v-model="novaSessaoForm.observacoes"
|
|
||||||
rows="2"
|
|
||||||
auto-resize
|
|
||||||
placeholder="Anotações que aparecem no card da agenda"
|
|
||||||
class="w-full"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Bloco de frequencia (espelha AgendaEventDialog: chips + qtd) -->
|
|
||||||
<div class="mpa-recur">
|
|
||||||
<label class="mpa-novo-lanc__label">Frequência</label>
|
|
||||||
<div class="mpa-freq-chips">
|
|
||||||
<button
|
|
||||||
v-for="opt in FREQ_OPCOES"
|
|
||||||
:key="opt.value"
|
|
||||||
type="button"
|
|
||||||
class="mpa-freq-chip"
|
|
||||||
:class="{ 'is-active': novaSessaoForm.freq === opt.value }"
|
|
||||||
@click="novaSessaoForm.freq = opt.value"
|
|
||||||
>
|
|
||||||
{{ opt.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Preview semanal/quinzenal -->
|
|
||||||
<div v-if="novaSessaoForm.freq === 'semanal' || novaSessaoForm.freq === 'quinzenal'" class="mpa-freq-preview">
|
|
||||||
<i class="pi pi-refresh" />
|
|
||||||
<span>
|
|
||||||
{{ novaSessaoForm.freq === 'quinzenal' ? 'A cada 2 semanas, toda' : 'Toda' }}
|
|
||||||
{{ novaSessaoForm.data ? (WEEKDAY_LABEL_BLOCK[new Date(novaSessaoForm.data + 'T00:00:00').getDay()] || '...').toLowerCase() : '...' }},
|
|
||||||
às {{ novaSessaoForm.hora || '—' }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Dias da semana (diasEspecificos) -->
|
|
||||||
<div v-if="novaSessaoForm.freq === 'diasEspecificos'" class="mpa-freq-dias">
|
|
||||||
<label class="mpa-novo-lanc__label">Dias da semana</label>
|
|
||||||
<div class="mpa-dias-grid">
|
|
||||||
<button
|
|
||||||
v-for="d in DIAS_SEMANA_OPCOES"
|
|
||||||
:key="d.value"
|
|
||||||
type="button"
|
|
||||||
class="mpa-dia-chip"
|
|
||||||
:class="{ 'is-active': novaSessaoForm.diasSelecionados.includes(d.value) }"
|
|
||||||
@click="toggleDiaSelecionado(d.value)"
|
|
||||||
>
|
|
||||||
{{ d.short }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Quantidade de sessoes (so quando nao avulsa) -->
|
|
||||||
<div v-if="novaSessaoForm.freq !== 'avulsa'" class="mpa-freq-qtd">
|
|
||||||
<label class="mpa-novo-lanc__label">Quantidade de sessões</label>
|
|
||||||
<div class="mpa-freq-chips">
|
|
||||||
<button
|
|
||||||
v-for="opt in QTD_SESSOES_OPCOES"
|
|
||||||
:key="opt.value"
|
|
||||||
type="button"
|
|
||||||
class="mpa-freq-chip mpa-freq-chip--sm"
|
|
||||||
:class="{ 'is-active': novaSessaoForm.qtdMode === opt.value }"
|
|
||||||
@click="novaSessaoForm.qtdMode = opt.value"
|
|
||||||
>
|
|
||||||
{{ opt.label }}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div v-if="novaSessaoForm.qtdMode === 'personalizar'" class="mpa-freq-qtd-custom">
|
|
||||||
<InputNumber
|
|
||||||
v-model="novaSessaoForm.qtdCustom"
|
|
||||||
:min="1"
|
|
||||||
:max="200"
|
|
||||||
show-buttons
|
|
||||||
button-layout="horizontal"
|
|
||||||
fluid
|
|
||||||
>
|
|
||||||
<template #decrementbuttonicon><i class="pi pi-minus" /></template>
|
|
||||||
<template #incrementbuttonicon><i class="pi pi-plus" /></template>
|
|
||||||
</InputNumber>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<template #footer>
|
|
||||||
<button class="mpa-quick-btn" @click="novaSessaoOpen = false">
|
|
||||||
<i class="pi pi-times" />
|
|
||||||
<span>Cancelar</span>
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
class="mpa-quick-btn mpa-quick-btn--cta"
|
|
||||||
:disabled="sessionsHook.busy.value"
|
|
||||||
@click="salvarSessao"
|
|
||||||
>
|
|
||||||
<i :class="sessionsHook.busy.value ? 'pi pi-spin pi-spinner' : 'pi pi-check'" :style="{ color: '#10b981' }" />
|
|
||||||
<span>{{ novaSessaoCtaLabel }}</span>
|
|
||||||
</button>
|
|
||||||
</template>
|
|
||||||
</Dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -590,6 +590,48 @@ function _buildHandlers(deps) {
|
|||||||
dialogOpen.value = true;
|
dialogOpen.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── onCreateEventoForPatient — abre o AgendaEventDialog com paciente
|
||||||
|
// pre-selecionado. Usado pelo MelissaPaciente quando o user clica
|
||||||
|
// "Agendar" na sidebar Acoes Rapidas. Mesma logica de onCreateEvento
|
||||||
|
// (defaults razoaveis: hoje proximo slot 15min, duracao default), so
|
||||||
|
// que injeta paciente_id no dialogEventRow.
|
||||||
|
function onCreateEventoForPatient(patientId) {
|
||||||
|
if (!ownerId.value) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'warn',
|
||||||
|
summary: 'Agenda',
|
||||||
|
detail: 'Aguarde carregar as configurações da agenda.',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const durMin =
|
||||||
|
settings.value?.session_duration_min ??
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
|
||||||
|
dialogEventRow.value = {
|
||||||
|
owner_id: ownerId.value,
|
||||||
|
terapeuta_id: null,
|
||||||
|
paciente_id: patientId ? String(patientId) : null,
|
||||||
|
tipo: EVENTO_TIPO.SESSAO,
|
||||||
|
status: 'agendado',
|
||||||
|
titulo: deriveTituloDefaultByTipo(EVENTO_TIPO.SESSAO),
|
||||||
|
observacoes: null,
|
||||||
|
visibility_scope: 'public',
|
||||||
|
determined_commitment_id: null
|
||||||
|
};
|
||||||
|
dialogStartISO.value = base.toISOString();
|
||||||
|
dialogEndISO.value = new Date(base.getTime() + durMin * 60000).toISOString();
|
||||||
|
dialogOpen.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
// ── onSelectTime — click-drag no FC pra criar evento novo ──
|
// ── onSelectTime — click-drag no FC pra criar evento novo ──
|
||||||
// Dinâmica de duração:
|
// Dinâmica de duração:
|
||||||
// click sem drag → settings.session_duration_min (default 50)
|
// click sem drag → settings.session_duration_min (default 50)
|
||||||
@@ -814,6 +856,7 @@ function _buildHandlers(deps) {
|
|||||||
return {
|
return {
|
||||||
onEditEvento,
|
onEditEvento,
|
||||||
onCreateEvento,
|
onCreateEvento,
|
||||||
|
onCreateEventoForPatient,
|
||||||
onSelectTime,
|
onSelectTime,
|
||||||
persistMoveOrResize,
|
persistMoveOrResize,
|
||||||
onEditSeriesOccurrence,
|
onEditSeriesOccurrence,
|
||||||
|
|||||||
Reference in New Issue
Block a user