Agenda, Agendador, Configurações

This commit is contained in:
Leonardo
2026-03-12 08:58:36 -03:00
parent f733db8436
commit f4b185ae17
197 changed files with 33405 additions and 6507 deletions

View File

@@ -467,9 +467,10 @@
</template>
</Column>
<Column :key="'col-acoes'" header="Ações" style="width: 16rem;" frozen alignFrozen="right">
<Column :key="'col-acoes'" header="Ações" style="width: 20rem;" frozen alignFrozen="right">
<template #body="{ data }">
<div class="flex gap-2 justify-end">
<Button label="Sessões" icon="pi pi-calendar" size="small" severity="info" outlined @click="abrirSessoes(data)" />
<Button label="Prontuário" icon="pi pi-file" size="small" @click="openProntuario(data)" />
<Button icon="pi pi-pencil" severity="secondary" outlined size="small" v-tooltip.top="'Editar'" @click="goEdit(data)" />
<Button icon="pi pi-trash" severity="danger" outlined size="small" v-tooltip.top="'Excluir'" @click="confirmDeleteOne(data)" />
@@ -521,7 +522,8 @@
</div>
<!-- Ações -->
<div class="mt-3 flex gap-2 justify-end">
<div class="mt-3 flex gap-2 justify-end flex-wrap">
<Button label="Sessões" icon="pi pi-calendar" size="small" severity="info" outlined @click="abrirSessoes(pat)" />
<Button label="Prontuário" icon="pi pi-file" size="small" @click="openProntuario(pat)" />
<Button icon="pi pi-pencil" severity="secondary" outlined size="small" @click="goEdit(pat)" />
<Button icon="pi pi-trash" severity="danger" outlined size="small" @click="confirmDeleteOne(pat)" />
@@ -585,6 +587,61 @@
/>
<ConfirmDialog />
<!-- ── DIALOG SESSÕES DO PACIENTE ─────────────────────────── -->
<Dialog
v-model:visible="sessoesOpen"
modal
:draggable="false"
:style="{ width: '700px', maxWidth: '96vw' }"
:header="sessoesPaciente ? `Sessões — ${sessoesPaciente.nome_completo}` : 'Sessões'"
>
<div v-if="sessoesLoading" class="flex justify-center py-8">
<ProgressSpinner />
</div>
<div v-else>
<!-- Recorrências ativas -->
<div v-if="recorrencias.length" class="mb-5">
<div class="text-sm font-semibold text-color-secondary mb-2 flex items-center gap-2">
<i class="pi pi-sync" /> Recorrências
</div>
<div class="flex flex-col gap-2">
<div
v-for="r in recorrencias"
:key="r.id"
class="sess-rec-card"
>
<Tag :value="r.status === 'ativo' ? 'Ativa' : 'Encerrada'" :severity="r.status === 'ativo' ? 'success' : 'secondary'" />
<span class="text-sm">{{ fmtRecorrencia(r) }}</span>
<span class="text-xs text-color-secondary ml-auto">
{{ r.start_date }} {{ r.end_date ? `→ ${r.end_date}` : '(em aberto)' }}
</span>
</div>
</div>
</div>
<!-- Lista de sessões -->
<div class="text-sm font-semibold text-color-secondary mb-2 flex items-center gap-2">
<i class="pi pi-calendar" /> Sessões ({{ sessoesLista.length }})
</div>
<div v-if="sessoesLista.length === 0" class="text-center py-6 text-color-secondary text-sm">
Nenhuma sessão encontrada para este paciente.
</div>
<div v-else class="sess-list">
<div v-for="ev in sessoesLista" :key="ev.id" class="sess-item">
<div class="flex items-center gap-3">
<Tag :value="ev.status || 'agendado'" :severity="statusSessaoSev(ev.status)" />
<span class="font-semibold text-sm">{{ fmtDataSessao(ev.inicio_em) }}</span>
<Tag v-if="ev.modalidade" :value="ev.modalidade" severity="secondary" class="ml-auto" />
</div>
<div v-if="ev.titulo" class="text-xs text-color-secondary mt-1">{{ ev.titulo }}</div>
</div>
</div>
</div>
</Dialog>
</template>
<script setup>
@@ -602,6 +659,59 @@ import ProgressSpinner from 'primevue/progressspinner'
import PatientProntuario from '@/features/patients/prontuario/PatientProntuario.vue'
import ComponentCadastroRapido from '@/components/ComponentCadastroRapido.vue'
// ── Sessões do paciente ──────────────────────────────────────────
const sessoesOpen = ref(false)
const sessoesPaciente = ref(null) // { id, nome_completo }
const sessoesLoading = ref(false)
const sessoesLista = ref([])
const recorrencias = ref([])
const MESES_BR = ['Jan','Fev','Mar','Abr','Mai','Jun','Jul','Ago','Set','Out','Nov','Dez']
function fmtDataSessao (iso) {
if (!iso) return '—'
const d = new Date(iso)
return `${String(d.getDate()).padStart(2,'0')} ${MESES_BR[d.getMonth()]} ${d.getFullYear()} ${String(d.getHours()).padStart(2,'0')}:${String(d.getMinutes()).padStart(2,'0')}`
}
function statusSessaoSev (st) {
return { agendado: 'info', realizado: 'success', cancelado: 'danger', faltou: 'warn' }[st] || 'secondary'
}
async function abrirSessoes (pat) {
sessoesPaciente.value = pat
sessoesOpen.value = true
sessoesLoading.value = true
sessoesLista.value = []
recorrencias.value = []
try {
const [evts, recs] = await Promise.all([
supabase
.from('agenda_eventos')
.select('id, titulo, tipo, status, inicio_em, fim_em, modalidade')
.eq('patient_id', pat.id)
.order('inicio_em', { ascending: false })
.limit(100),
supabase
.from('recurrence_rules')
.select('id, type, interval, weekdays, start_date, end_date, start_time, duration_min, status')
.eq('patient_id', pat.id)
.order('start_date', { ascending: false }),
])
sessoesLista.value = evts.data || []
recorrencias.value = recs.data || []
} catch (e) {
console.error('Erro ao carregar sessões:', e)
} finally {
sessoesLoading.value = false
}
}
const DIAS_SEMANA = ['Dom','Seg','Ter','Qua','Qui','Sex','Sáb']
function fmtRecorrencia (r) {
const dias = (r.weekdays || []).map(d => DIAS_SEMANA[d]).join(', ')
const freq = r.type === 'weekly' && r.interval === 2 ? 'Quinzenal' : r.type === 'weekly' ? 'Semanal' : 'Personalizado'
return `${freq} · ${dias} · ${r.start_time?.slice(0,5) || '—'} · ${r.duration_min || 50}min`
}
const router = useRouter()
const route = useRoute()
@@ -1328,4 +1438,25 @@ function updateKpis() {
/* Fade */
.fade-enter-active, .fade-leave-active { transition: opacity 0.15s ease; }
.fade-enter-from, .fade-leave-to { opacity: 0; }
/* ── Dialog Sessões ──────────────────────────────────────────── */
.sess-list {
display: flex; flex-direction: column; gap: .5rem;
max-height: 55vh; overflow-y: auto;
padding-right: .25rem;
}
.sess-item {
padding: 10px 14px;
border: 1px solid var(--surface-border);
border-radius: .75rem;
background: var(--surface-ground);
}
.sess-rec-card {
display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
padding: 10px 14px;
border: 1px solid var(--surface-border);
border-radius: .75rem;
background: var(--surface-ground);
font-size: .85rem;
}
</style>