Files
agenciapsilmno/src/features/agenda/services/agendaRepository.js
Leonardo f733db8436 ZERADO
2026-03-06 06:37:13 -03:00

241 lines
6.9 KiB
JavaScript

// src/features/agenda/services/agendaRepository.js
import { supabase } from '@/lib/supabase/client'
import { useTenantStore } from '@/stores/tenantStore'
function assertValidTenantId (tenantId) {
if (!tenantId || tenantId === 'null' || tenantId === 'undefined') {
throw new Error('Tenant ativo inválido. Selecione a clínica/tenant antes de carregar a agenda.')
}
}
async function getUid () {
const { data: userRes, error: userErr } = await supabase.auth.getUser()
if (userErr) throw userErr
const uid = userRes?.user?.id
if (!uid) throw new Error('Usuário não autenticado.')
return uid
}
/**
* Configurações da agenda (por owner)
* Se você decidir que configurações são por tenant também, adicionamos tenant_id aqui.
*/
export async function getMyAgendaSettings () {
const uid = await getUid()
const { data, error } = await supabase
.from('agenda_configuracoes')
.select('*')
.eq('owner_id', uid)
.order('created_at', { ascending: false })
.limit(1)
.maybeSingle()
if (error) throw error
return data
}
/**
* Lista agenda do terapeuta (somente do owner logado) dentro do tenant ativo.
* Isso impede misturar eventos caso o terapeuta atue em múltiplas clínicas.
*/
export async function listMyAgendaEvents ({ startISO, endISO, tenantId: tenantIdArg } = {}) {
const uid = await getUid()
const tenantStore = useTenantStore()
const tenantId = tenantIdArg || tenantStore.activeTenantId
assertValidTenantId(tenantId)
if (!startISO || !endISO) throw new Error('Intervalo inválido (startISO/endISO).')
const { data, error } = await supabase
.from('agenda_eventos')
.select('*, patients(id, nome_completo, avatar_url), determined_commitments!determined_commitment_id(id, bg_color, text_color)')
.eq('tenant_id', tenantId)
.eq('owner_id', uid)
.gte('inicio_em', startISO)
.lt('inicio_em', endISO)
.order('inicio_em', { ascending: true })
if (error) throw error
const rows = data || []
// Eventos antigos têm paciente_id mas patient_id=null (sem FK) → join retorna null.
// Fazemos um segundo fetch para esses casos e mesclamos.
const orphanIds = [...new Set(
rows.filter(r => r.paciente_id && !r.patients).map(r => r.paciente_id)
)]
if (orphanIds.length) {
const { data: pts } = await supabase
.from('patients')
.select('id, nome_completo, avatar_url')
.in('id', orphanIds)
if (pts?.length) {
const map = Object.fromEntries(pts.map(p => [p.id, p]))
for (const r of rows) {
if (r.paciente_id && !r.patients) r.patients = map[r.paciente_id] || null
}
}
}
return rows
}
/**
* Lista eventos para mosaico da clínica (admin/secretaria) dentro de um tenant específico.
* IMPORTANTE: SEM tenant_id aqui vira vazamento multi-tenant.
*/
export async function listClinicEvents ({ tenantId, ownerIds, startISO, endISO }) {
assertValidTenantId(tenantId)
if (!ownerIds?.length) return []
if (!startISO || !endISO) throw new Error('Intervalo inválido (startISO/endISO).')
// Sanitiza ownerIds
const safeOwnerIds = ownerIds
.filter(id => typeof id === 'string' && id && id !== 'null' && id !== 'undefined')
if (!safeOwnerIds.length) return []
const { data, error } = await supabase
.from('agenda_eventos')
.select('*, determined_commitments!determined_commitment_id(id, bg_color, text_color)')
.eq('tenant_id', tenantId)
.in('owner_id', safeOwnerIds)
.gte('inicio_em', startISO)
.lt('inicio_em', endISO)
.order('inicio_em', { ascending: true })
if (error) throw error
return data || []
}
export async function listTenantStaff (tenantId) {
assertValidTenantId(tenantId)
const { data, error } = await supabase
.from('v_tenant_staff')
.select('*')
.eq('tenant_id', tenantId)
if (error) throw error
return data || []
}
/**
* Criação segura:
* - injeta tenant_id do tenantStore
* - injeta owner_id do usuário logado (ignora owner_id vindo de fora)
*
* Observação:
* - Para admin/secretária criar para outros owners, o ideal é ter uma função separada
* (ex.: createClinicAgendaEvento) que permita owner_id explicitamente.
* Por enquanto, deixo esta função como "safe default" para terapeuta.
*/
export async function createAgendaEvento (payload) {
const uid = await getUid()
const tenantStore = useTenantStore()
const tenantId = tenantStore.activeTenantId
assertValidTenantId(tenantId)
if (!payload) throw new Error('Payload vazio.')
const insertPayload = {
...payload,
tenant_id: tenantId,
owner_id: uid
}
const { data, error } = await supabase
.from('agenda_eventos')
.insert(insertPayload)
.select('*')
.single()
if (error) throw error
return data
}
/**
* Atualização segura:
* - filtra por id + tenant_id (evita update cruzado por acidente)
* RLS deve reforçar isso no banco.
*/
export async function updateAgendaEvento (id, patch, { tenantId: tenantIdArg } = {}) {
if (!id) throw new Error('ID inválido.')
if (!patch) throw new Error('Patch vazio.')
const tenantStore = useTenantStore()
const tenantId = tenantIdArg || tenantStore.activeTenantId
assertValidTenantId(tenantId)
const { data, error } = await supabase
.from('agenda_eventos')
.update(patch)
.eq('id', id)
.eq('tenant_id', tenantId)
.select('*')
.single()
if (error) throw error
return data
}
/**
* Delete seguro:
* - filtra por id + tenant_id
*/
export async function deleteAgendaEvento (id, { tenantId: tenantIdArg } = {}) {
if (!id) throw new Error('ID inválido.')
const tenantStore = useTenantStore()
const tenantId = tenantIdArg || tenantStore.activeTenantId
assertValidTenantId(tenantId)
const { error } = await supabase
.from('agenda_eventos')
.delete()
.eq('id', id)
.eq('tenant_id', tenantId)
if (error) throw error
return true
}
// Adicione no mesmo arquivo: src/features/agenda/services/agendaRepository.js
/**
* Criação para a área da clínica (admin/secretária):
* - exige tenantId explícito (ou cai no tenantStore)
* - permite definir owner_id (terapeuta dono do compromisso)
*
* Segurança real deve ser garantida por RLS:
* - clinic_admin/tenant_admin pode criar para qualquer owner dentro do tenant
* - therapist não deve conseguir passar daqui (guard + RLS)
*/
export async function createClinicAgendaEvento (payload, { tenantId: tenantIdArg } = {}) {
const tenantStore = useTenantStore()
const tenantId = tenantIdArg || tenantStore.activeTenantId
assertValidTenantId(tenantId)
if (!payload) throw new Error('Payload vazio.')
const ownerId = payload.owner_id
if (!ownerId || ownerId === 'null' || ownerId === 'undefined') {
throw new Error('owner_id é obrigatório para criação pela clínica.')
}
const insertPayload = {
...payload,
tenant_id: tenantId
}
const { data, error } = await supabase
.from('agenda_eventos')
.insert(insertPayload)
.select('*')
.single()
if (error) throw error
return data
}