Agenda, Agendador, Configurações
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user