Ajuste em Massa - Paciente, Terapeuta, Clinica e Admin - Inicio agenda
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
export const useTenantFeaturesStore = defineStore('tenantFeatures', () => {
|
||||
const loading = ref(false)
|
||||
const loadedForTenantId = ref(null)
|
||||
const features = ref({}) // { patients: true/false, ... }
|
||||
|
||||
function isEnabled(key) {
|
||||
return !!features.value?.[key]
|
||||
}
|
||||
|
||||
function invalidate() {
|
||||
loadedForTenantId.value = null
|
||||
features.value = {}
|
||||
}
|
||||
|
||||
async function fetchForTenant(tenantId, { force = false } = {}) {
|
||||
if (!tenantId) return
|
||||
if (!force && loadedForTenantId.value === tenantId) return
|
||||
|
||||
loading.value = true
|
||||
try {
|
||||
const { data, error } = await supabase
|
||||
.from('tenant_features')
|
||||
.select('feature_key, enabled')
|
||||
.eq('tenant_id', tenantId)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const map = {}
|
||||
for (const row of data || []) map[row.feature_key] = !!row.enabled
|
||||
|
||||
features.value = map
|
||||
loadedForTenantId.value = tenantId
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function setForTenant(tenantId, key, enabled) {
|
||||
if (!tenantId) throw new Error('tenantId missing')
|
||||
|
||||
const { error } = await supabase
|
||||
.from('tenant_features')
|
||||
.upsert(
|
||||
{ tenant_id: tenantId, feature_key: key, enabled: !!enabled },
|
||||
{ onConflict: 'tenant_id,feature_key' }
|
||||
)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
// atualiza cache local
|
||||
if (loadedForTenantId.value === tenantId) {
|
||||
features.value = { ...features.value, [key]: !!enabled }
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
loading,
|
||||
features,
|
||||
loadedForTenantId,
|
||||
isEnabled,
|
||||
invalidate,
|
||||
fetchForTenant,
|
||||
setForTenant
|
||||
}
|
||||
})
|
||||
+130
-55
@@ -2,6 +2,21 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
|
||||
// ✅ normaliza roles vindas do backend (tenant_members / RPC my_tenants)
|
||||
// - seu projeto quer usar clinic_admin como nome canônico
|
||||
function normalizeTenantRole (role) {
|
||||
const r = String(role || '').trim()
|
||||
if (!r) return null
|
||||
|
||||
// ✅ legado: alguns bancos / RPCs retornam tenant_admin
|
||||
if (r === 'tenant_admin') return 'clinic_admin'
|
||||
|
||||
// (opcional) se em algum lugar vier 'admin' (profiles), também normaliza:
|
||||
if (r === 'admin') return 'clinic_admin'
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
export const useTenantStore = defineStore('tenant', {
|
||||
state: () => ({
|
||||
loading: false,
|
||||
@@ -11,76 +26,136 @@ export const useTenantStore = defineStore('tenant', {
|
||||
memberships: [], // [{ tenant_id, role, status }]
|
||||
activeTenantId: null,
|
||||
activeRole: null,
|
||||
|
||||
needsTenantLink: false,
|
||||
|
||||
needsTenantLink: false,
|
||||
error: null
|
||||
}),
|
||||
|
||||
actions: {
|
||||
async loadSessionAndTenant () {
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
this.error = null
|
||||
if (this.loading) return
|
||||
this.loading = true
|
||||
this.error = null
|
||||
|
||||
try {
|
||||
// 1) auth user (estável)
|
||||
const { data, error } = await supabase.auth.getSession()
|
||||
if (error) throw error
|
||||
try {
|
||||
// 1) auth user (estável)
|
||||
const { data, error } = await supabase.auth.getSession()
|
||||
if (error) throw error
|
||||
|
||||
this.user = data?.session?.user ?? null
|
||||
this.user = data?.session?.user ?? null
|
||||
|
||||
// sem sessão -> não chama RPC, só marca estado
|
||||
if (!this.user) {
|
||||
this.memberships = []
|
||||
this.activeTenantId = null
|
||||
this.activeRole = null
|
||||
this.needsTenantLink = false
|
||||
this.loaded = true
|
||||
return
|
||||
}
|
||||
// sem sessão -> limpa estado e storage
|
||||
if (!this.user) {
|
||||
this.memberships = []
|
||||
this.activeTenantId = null
|
||||
this.activeRole = null
|
||||
this.needsTenantLink = false
|
||||
this.loaded = true
|
||||
|
||||
// 2) memberships via RPC
|
||||
const { data: mem, error: mErr } = await supabase.rpc('my_tenants')
|
||||
if (mErr) throw mErr
|
||||
localStorage.removeItem('tenant_id')
|
||||
localStorage.removeItem('tenant')
|
||||
|
||||
this.memberships = Array.isArray(mem) ? mem : []
|
||||
return
|
||||
}
|
||||
|
||||
// 3) define active tenant (primeiro active)
|
||||
const firstActive = this.memberships.find(x => x.status === 'active')
|
||||
this.activeTenantId = firstActive?.tenant_id ?? null
|
||||
this.activeRole = firstActive?.role ?? null
|
||||
// 2) memberships via RPC
|
||||
const { data: mem, error: mErr } = await supabase.rpc('my_tenants')
|
||||
if (mErr) throw mErr
|
||||
|
||||
// se logou mas não tem vínculo ativo
|
||||
this.needsTenantLink = !this.activeTenantId
|
||||
this.memberships = Array.isArray(mem) ? mem : []
|
||||
|
||||
this.loaded = true
|
||||
} catch (e) {
|
||||
console.warn('[tenantStore] loadSessionAndTenant falhou:', e)
|
||||
this.error = e
|
||||
// 3) tenta restaurar tenant salvo
|
||||
const savedTenantId = localStorage.getItem('tenant_id')
|
||||
|
||||
// ⚠️ NÃO zera tudo agressivamente por erro transitório.
|
||||
// Mantém o que já tinha (se tiver), mas marca loaded pra não travar o app.
|
||||
// Se você preferir ser mais “duro”, só zere quando não houver sessão:
|
||||
// (a sessão já foi lida acima; se der erro antes, user pode estar null)
|
||||
if (!this.user) {
|
||||
this.memberships = []
|
||||
this.activeTenantId = null
|
||||
this.activeRole = null
|
||||
this.needsTenantLink = false
|
||||
}
|
||||
let activeMembership = null
|
||||
|
||||
this.loaded = true
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
}
|
||||
,
|
||||
if (savedTenantId) {
|
||||
activeMembership = this.memberships.find(
|
||||
x => x.tenant_id === savedTenantId && x.status === 'active'
|
||||
)
|
||||
}
|
||||
|
||||
// fallback: primeiro active
|
||||
if (!activeMembership) {
|
||||
activeMembership = this.memberships.find(x => x.status === 'active')
|
||||
}
|
||||
|
||||
this.activeTenantId = activeMembership?.tenant_id ?? null
|
||||
|
||||
// ✅ normaliza role aqui (tenant_admin -> clinic_admin)
|
||||
this.activeRole = normalizeTenantRole(activeMembership?.role)
|
||||
|
||||
// persiste tenant se existir
|
||||
if (this.activeTenantId) {
|
||||
localStorage.setItem('tenant_id', this.activeTenantId)
|
||||
localStorage.setItem('tenant', JSON.stringify({
|
||||
id: this.activeTenantId,
|
||||
role: this.activeRole
|
||||
}))
|
||||
}
|
||||
|
||||
// se logou mas não tem vínculo ativo
|
||||
this.needsTenantLink = !this.activeTenantId
|
||||
|
||||
this.loaded = true
|
||||
} catch (e) {
|
||||
console.warn('[tenantStore] loadSessionAndTenant falhou:', e)
|
||||
this.error = e
|
||||
|
||||
// ⚠️ NÃO zera tudo agressivamente por erro transitório.
|
||||
// Mantém o que já tinha (se tiver), mas marca loaded pra não travar o app.
|
||||
if (!this.user) {
|
||||
this.memberships = []
|
||||
this.activeTenantId = null
|
||||
this.activeRole = null
|
||||
this.needsTenantLink = false
|
||||
|
||||
localStorage.removeItem('tenant_id')
|
||||
localStorage.removeItem('tenant')
|
||||
}
|
||||
|
||||
this.loaded = true
|
||||
} finally {
|
||||
this.loading = false
|
||||
}
|
||||
},
|
||||
|
||||
setActiveTenant (tenantId) {
|
||||
const found = this.memberships.find(x => x.tenant_id === tenantId && x.status === 'active')
|
||||
this.activeTenantId = found?.tenant_id ?? null
|
||||
this.activeRole = found?.role ?? null
|
||||
this.needsTenantLink = !this.activeTenantId
|
||||
}
|
||||
const found = this.memberships.find(
|
||||
x => x.tenant_id === tenantId && x.status === 'active'
|
||||
)
|
||||
|
||||
this.activeTenantId = found?.tenant_id ?? null
|
||||
|
||||
// ✅ normaliza role também ao trocar tenant
|
||||
this.activeRole = normalizeTenantRole(found?.role)
|
||||
|
||||
this.needsTenantLink = !this.activeTenantId
|
||||
|
||||
if (this.activeTenantId) {
|
||||
localStorage.setItem('tenant_id', this.activeTenantId)
|
||||
localStorage.setItem('tenant', JSON.stringify({
|
||||
id: this.activeTenantId,
|
||||
role: this.activeRole
|
||||
}))
|
||||
} else {
|
||||
localStorage.removeItem('tenant_id')
|
||||
localStorage.removeItem('tenant')
|
||||
}
|
||||
},
|
||||
|
||||
// opcional mas recomendado
|
||||
reset () {
|
||||
this.user = null
|
||||
this.memberships = []
|
||||
this.activeTenantId = null
|
||||
this.activeRole = null
|
||||
this.needsTenantLink = false
|
||||
this.error = null
|
||||
this.loaded = false
|
||||
|
||||
localStorage.removeItem('tenant_id')
|
||||
localStorage.removeItem('tenant')
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user