241 lines
6.9 KiB
JavaScript
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
|
|
} |