Ajuste em Massa - Paciente, Terapeuta, Clinica e Admin - Inicio agenda

This commit is contained in:
Leonardo
2026-02-22 17:56:01 -03:00
parent 6eff67bf22
commit 89b4ecaba1
77 changed files with 9433 additions and 1995 deletions
+69
View File
@@ -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
View File
@@ -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')
}
}
})
})