// 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 }