first commit
This commit is contained in:
@@ -0,0 +1,173 @@
|
||||
// src/services/patientGroups.js
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
function pickCount (row) {
|
||||
return row?.patients_count ?? row?.patient_count ?? 0
|
||||
}
|
||||
|
||||
async function getOwnerId () {
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
if (error) throw error
|
||||
const uid = data?.user?.id
|
||||
if (!uid) throw new Error('Sessão inválida.')
|
||||
return uid
|
||||
}
|
||||
|
||||
function normalizeNome (s) {
|
||||
return String(s || '').trim().toLowerCase().replace(/\s+/g, ' ')
|
||||
}
|
||||
|
||||
function isUniqueViolation (err) {
|
||||
if (!err) return false
|
||||
if (err.code === '23505') return true
|
||||
const msg = String(err.message || '')
|
||||
return /duplicate key value violates unique constraint/i.test(msg)
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista grupos do usuário + grupos do sistema, já com contagem.
|
||||
* Usa a view v_patient_groups_with_counts (preferencial).
|
||||
* Fallback: tabela patient_groups + contagem pela pivot.
|
||||
*/
|
||||
export async function listGroupsWithCounts () {
|
||||
const ownerId = await getOwnerId()
|
||||
|
||||
// 1) View (preferencial) — agora já é a fonte correta
|
||||
const { data: vData, error: vErr } = await supabase
|
||||
.from('v_patient_groups_with_counts')
|
||||
.select('*')
|
||||
.or(`owner_id.eq.${ownerId},is_system.eq.true`)
|
||||
.order('nome', { ascending: true })
|
||||
|
||||
if (!vErr) {
|
||||
return (vData || []).map(r => ({
|
||||
...r,
|
||||
patients_count: pickCount(r)
|
||||
}))
|
||||
}
|
||||
|
||||
// 2) Fallback (caso view não exista / erro de schema)
|
||||
const { data: groups, error: gErr } = await supabase
|
||||
.from('patient_groups')
|
||||
.select('id,nome,cor,is_system,is_active,owner_id,created_at,updated_at')
|
||||
.or(`owner_id.eq.${ownerId},is_system.eq.true`)
|
||||
.order('nome', { ascending: true })
|
||||
if (gErr) throw gErr
|
||||
|
||||
const ids = (groups || []).map(g => g.id).filter(Boolean)
|
||||
if (!ids.length) return []
|
||||
|
||||
// conta pacientes por grupo na pivot
|
||||
const { data: rel, error: rErr } = await supabase
|
||||
.from('patient_group_patient')
|
||||
.select('patient_group_id')
|
||||
.in('patient_group_id', ids)
|
||||
if (rErr) throw rErr
|
||||
|
||||
const counts = new Map()
|
||||
for (const row of rel || []) {
|
||||
const gid = row.patient_group_id
|
||||
if (!gid) continue
|
||||
counts.set(gid, (counts.get(gid) || 0) + 1)
|
||||
}
|
||||
|
||||
return (groups || []).map(g => ({
|
||||
...g,
|
||||
patients_count: counts.get(g.id) || 0
|
||||
}))
|
||||
}
|
||||
|
||||
export async function createGroup (nome, cor = null) {
|
||||
const ownerId = await getOwnerId()
|
||||
|
||||
const raw = String(nome || '').trim()
|
||||
if (!raw) throw new Error('Nome do grupo é obrigatório.')
|
||||
|
||||
const nNorm = normalizeNome(raw)
|
||||
|
||||
// proteção extra no front: busca por igualdade "normalizada"
|
||||
// (mantém RLS como autoridade final, mas evita UX ruim)
|
||||
const { data: existing, error: exErr } = await supabase
|
||||
.from('patient_groups')
|
||||
.select('id,nome')
|
||||
.eq('owner_id', ownerId)
|
||||
.eq('is_system', false)
|
||||
.limit(50)
|
||||
|
||||
if (!exErr && (existing || []).some(r => normalizeNome(r.nome) === nNorm)) {
|
||||
throw new Error('Já existe um grupo com esse nome.')
|
||||
}
|
||||
|
||||
const payload = {
|
||||
owner_id: ownerId,
|
||||
nome: raw,
|
||||
cor: cor || null
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('patient_groups')
|
||||
.insert(payload)
|
||||
.select('id,nome,cor,is_system,owner_id,is_active,created_at,updated_at')
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
if (isUniqueViolation(error)) throw new Error('Já existe um grupo com esse nome.')
|
||||
throw error
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateGroup (id, nome, cor = null) {
|
||||
const ownerId = await getOwnerId()
|
||||
|
||||
const raw = String(nome || '').trim()
|
||||
if (!id) throw new Error('ID inválido.')
|
||||
if (!raw) throw new Error('Nome do grupo é obrigatório.')
|
||||
|
||||
// (opcional) valida duplicidade entre os grupos do owner (não-system)
|
||||
const nNorm = normalizeNome(raw)
|
||||
const { data: existing, error: exErr } = await supabase
|
||||
.from('patient_groups')
|
||||
.select('id,nome')
|
||||
.eq('owner_id', ownerId)
|
||||
.eq('is_system', false)
|
||||
.neq('id', id)
|
||||
.limit(80)
|
||||
|
||||
if (!exErr && (existing || []).some(r => normalizeNome(r.nome) === nNorm)) {
|
||||
throw new Error('Já existe um grupo com esse nome.')
|
||||
}
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('patient_groups')
|
||||
.update({ nome: raw, cor: cor || null, updated_at: new Date().toISOString() })
|
||||
.eq('id', id)
|
||||
.eq('owner_id', ownerId)
|
||||
.eq('is_system', false)
|
||||
.select('id,nome,cor,is_system,owner_id,is_active,created_at,updated_at')
|
||||
.single()
|
||||
|
||||
if (error) {
|
||||
if (isUniqueViolation(error)) throw new Error('Já existe um grupo com esse nome.')
|
||||
throw error
|
||||
}
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteGroup (id) {
|
||||
const ownerId = await getOwnerId()
|
||||
|
||||
if (!id) throw new Error('ID inválido.')
|
||||
|
||||
const { error } = await supabase
|
||||
.from('patient_groups')
|
||||
.delete()
|
||||
.eq('id', id)
|
||||
.eq('owner_id', ownerId)
|
||||
.eq('is_system', false)
|
||||
|
||||
if (error) throw error
|
||||
return true
|
||||
}
|
||||
Reference in New Issue
Block a user