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
@@ -55,7 +55,7 @@ function pick(obj, keys = []) {
// ------------------------------------------------------
// accordion (pode abrir vários) + scroll
// ------------------------------------------------------
const accordionValues = ['0', '1', '2', '3', '4']
const accordionValues = ['0', '1', '2', '3', '4', '5']
const activeValues = ref(['0']) // começa com o primeiro aberto
const activeValue = computed(() => activeValues.value?.[0] ?? null)
@@ -91,7 +91,8 @@ const navItems = [
{ value: '1', label: 'Endereço', icon: 'pi pi-map-marker' },
{ value: '2', label: 'Dados adicionais', icon: 'pi pi-tags' },
{ value: '3', label: 'Responsável', icon: 'pi pi-users' },
{ value: '4', label: 'Anotações', icon: 'pi pi-file-edit' }
{ value: '4', label: 'Anotações', icon: 'pi pi-file-edit' },
{ value: '5', label: 'Sessões', icon: 'pi pi-calendar' }
]
const navPopover = ref(null)
@@ -307,6 +308,74 @@ const observacaoResponsavel = computed(() => pick(patientData.value, ['observaca
// notas internas
const notasInternas = computed(() => pick(patientData.value, ['notas_internas', 'notes']))
// ------------------------------------------------------
// Sessões do paciente (integração agenda)
// ------------------------------------------------------
const sessions = ref([])
const sessionsLoading = ref(false)
const STATUS_LABEL = {
agendado: 'Agendado',
realizado: 'Realizado',
faltou: 'Faltou',
cancelado: 'Cancelado',
remarcado: 'Remarcado',
bloqueado: 'Bloqueado',
}
const STATUS_SEVERITY = {
agendado: 'info',
realizado: 'success',
faltou: 'danger',
cancelado: 'warn',
remarcado: 'secondary',
bloqueado: 'secondary',
}
function fmtDateTimeBR (iso) {
if (!iso) return '—'
const d = new Date(iso)
if (Number.isNaN(d.getTime())) return iso
const dd = String(d.getDate()).padStart(2, '0')
const mm = String(d.getMonth() + 1).padStart(2, '0')
const yy = d.getFullYear()
const hh = String(d.getHours()).padStart(2, '0')
const mi = String(d.getMinutes()).padStart(2, '0')
return `${dd}/${mm}/${yy} ${hh}:${mi}`
}
function sessionDuration (inicio, fim) {
if (!inicio || !fim) return null
const diff = new Date(fim) - new Date(inicio)
if (diff <= 0) return null
const min = Math.round(diff / 60000)
if (min < 60) return `${min} min`
const h = Math.floor(min / 60)
const m = min % 60
return m ? `${h}h ${m}min` : `${h}h`
}
async function loadSessions (patientId) {
sessionsLoading.value = true
sessions.value = []
try {
const { data, error } = await supabase
.from('agenda_eventos')
.select('id, inicio_em, fim_em, status, modalidade, tipo, titulo, titulo_custom, observacoes, tenant_id')
.eq('patient_id', patientId)
.order('inicio_em', { ascending: false })
.limit(100)
if (error) throw error
sessions.value = data || []
} catch (e) {
// falha silenciosa — prontuário continua sem a seção de sessões
sessions.value = []
} finally {
sessionsLoading.value = false
}
}
async function getPatientById(id) {
const { data, error } = await supabase
.from('patients')
@@ -385,15 +454,21 @@ async function loadProntuario(id) {
tags.value = []
try {
const p = await getPatientById(id)
const [p, rel] = await Promise.all([
getPatientById(id),
getPatientRelations(id),
])
if (!p) throw new Error('Paciente não retornou dados (RLS bloqueando ou ID não existe no banco).')
patientFull.value = p
const rel = await getPatientRelations(id)
groups.value = await getGroupsByIds(rel.groupIds || [])
tags.value = await getTagsByIds(rel.tagIds || [])
const [g, t] = await Promise.all([
getGroupsByIds(rel.groupIds || []),
getTagsByIds(rel.tagIds || []),
loadSessions(id),
])
groups.value = g
tags.value = t
} catch (e) {
loadError.value = e?.message || 'Falha ao buscar dados no Supabase.'
toast.add({ severity: 'error', summary: 'Erro ao carregar prontuário', detail: loadError.value, life: 4500 })
@@ -984,6 +1059,42 @@ Tags: ${(tags.value || []).map(t => t.name).filter(Boolean).join(', ') || '—'}
</FloatLabel>
</AccordionContent>
</AccordionPanel>
<AccordionPanel value="5">
<AccordionHeader :ref="el => setPanelHeaderRef(el, 5)">6. SESSÕES</AccordionHeader>
<AccordionContent>
<div v-if="sessionsLoading" class="text-slate-500 text-sm py-2">Carregando sessões</div>
<div v-else-if="!sessions.length" class="text-slate-500 text-sm py-2">Nenhuma sessão registrada para este paciente.</div>
<div v-else class="flex flex-col gap-2">
<div
v-for="s in sessions"
:key="s.id"
class="rounded-xl border border-slate-200 bg-white px-4 py-3 flex flex-col sm:flex-row sm:items-center sm:justify-between gap-2"
>
<div class="flex flex-col gap-1">
<div class="font-medium text-slate-800">
{{ s.titulo_custom || s.titulo || (s.tipo ? s.tipo : 'Sessão') }}
</div>
<div class="text-sm text-slate-500 flex flex-wrap gap-x-3 gap-y-1">
<span><i class="pi pi-calendar mr-1 opacity-60" />{{ fmtDateTimeBR(s.inicio_em) }}</span>
<span v-if="sessionDuration(s.inicio_em, s.fim_em)">
<i class="pi pi-clock mr-1 opacity-60" />{{ sessionDuration(s.inicio_em, s.fim_em) }}
</span>
<span v-if="s.modalidade">
<i class="pi pi-video mr-1 opacity-60" />{{ s.modalidade === 'online' ? 'Online' : 'Presencial' }}
</span>
</div>
<div v-if="s.observacoes" class="text-sm text-slate-600 mt-1 line-clamp-2">{{ s.observacoes }}</div>
</div>
<Tag
:value="STATUS_LABEL[s.status] || s.status || 'Agendado'"
:severity="STATUS_SEVERITY[s.status] || 'info'"
class="shrink-0"
/>
</div>
</div>
</AccordionContent>
</AccordionPanel>
</Accordion>
</main>
</div>