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
+94 -17
View File
@@ -70,13 +70,16 @@ const enabledFeatureIdsByPlanId = computed(() => {
const currentPlanId = computed(() => subscription.value?.plan_id || null)
function planKeyById(id) {
function planKeyById (id) {
return planById.value.get(id)?.key || null
}
const currentPlanKey = computed(() => planKeyById(currentPlanId.value))
const currentPlanKey = computed(() => {
// ✅ fallback: se não carregou plans ainda, usa o plan_key da subscription
return planKeyById(currentPlanId.value) || subscription.value?.plan_key || null
})
function friendlyFeatureLabel(featureKey) {
function friendlyFeatureLabel (featureKey) {
return featureLabels[featureKey] || featureKey
}
@@ -92,7 +95,7 @@ const sortedPlans = computed(() => {
return arr
})
function planBenefits(planId) {
function planBenefits (planId) {
const set = enabledFeatureIdsByPlanId.value.get(planId) || new Set()
const list = features.value.map((f) => ({
ok: set.has(f.id),
@@ -104,34 +107,82 @@ function planBenefits(planId) {
return list
}
function goBack() {
function goBack () {
router.back()
}
function goBilling() {
function goBilling () {
router.push('/admin/billing')
}
function contactSupport() {
function contactSupport () {
router.push('/admin/billing')
}
async function fetchAll() {
// ✅ revalida a rota atual para o guard reavaliar features após troca de plano
async function revalidateCurrentRoute () {
// tenta respeitar um redirectTo (quando usuário veio por recurso bloqueado)
const redirectTo = route.query.redirectTo ? String(route.query.redirectTo) : null
// se existe redirectTo, tente ir para ele (guard decide se entra ou volta ao upgrade)
if (redirectTo) {
try {
await router.replace(redirectTo)
return
} catch (_) {
// se falhar, cai no refresh da rota atual
}
}
// força o vue-router a reprocessar a rota (dispara beforeEach)
try {
await router.replace(router.currentRoute.value.fullPath)
} catch (_) {}
}
async function fetchAll () {
loading.value = true
try {
const tid = tenantId.value
if (!tid) throw new Error('Tenant ativo não encontrado.')
const [pRes, fRes, pfRes, sRes] = await Promise.all([
supabase.from('plans').select('*').order('key', { ascending: true }),
supabase.from('plans').select('*').eq('is_active', true).order('key', { ascending: true }),
supabase.from('features').select('*').order('key', { ascending: true }),
supabase.from('plan_features').select('plan_id, feature_id'),
supabase
.from('subscriptions')
.select('id, tenant_id, plan_id, plan_key, interval, status, created_at, updated_at')
// ✅ pega mais campos úteis e faz join do plano (ajuda a exibir e debugar)
.select(`
id,
tenant_id,
user_id,
plan_id,
plan_key,
"interval",
status,
provider,
source,
started_at,
current_period_start,
current_period_end,
created_at,
updated_at,
plan:plan_id (
id,
key,
name,
description,
price_cents,
currency,
billing_interval,
is_active
)
`)
.eq('tenant_id', tid)
.eq('status', 'active')
.order('updated_at', { ascending: false })
// ✅ created_at é mais confiável que updated_at em assinaturas manuais
.order('created_at', { ascending: false })
.limit(1)
.maybeSingle()
])
@@ -142,13 +193,20 @@ async function fetchAll() {
// ✅ subscription pode ser null sem quebrar a página
if (sRes.error) {
console.warn('[Upgrade] sem subscription ativa (ok):', sRes.error)
console.warn('[Upgrade] erro ao buscar subscription:', sRes.error)
}
plans.value = pRes.data || []
features.value = fRes.data || []
planFeatures.value = pfRes.data || []
subscription.value = sRes.data || null
// pode remover esses logs depois
console.groupCollapsed('[Upgrade] fetchAll')
console.log('tenantId:', tid)
console.log('subscription:', subscription.value)
console.log('currentPlanKey:', currentPlanKey.value)
console.groupEnd()
} catch (e) {
console.error(e)
toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 5000 })
@@ -157,7 +215,7 @@ async function fetchAll() {
}
}
async function changePlan(targetPlanId) {
async function changePlan (targetPlanId) {
if (!subscription.value?.id) {
toast.add({
severity: 'warn',
@@ -187,17 +245,32 @@ async function changePlan(targetPlanId) {
// atualiza estado local
subscription.value.plan_id = data?.plan_id || targetPlanId
subscription.value.plan_key = data?.plan_key || planKeyById(subscription.value.plan_id) || subscription.value.plan_key
// ✅ recarrega entitlements (sem reload)
// (importante pra refletir o plano imediatamente)
entitlementsStore.clear?.()
await entitlementsStore.fetch(tid, { force: true })
// seu store tem loadForTenant no guard; se existir aqui também, use primeiro
if (typeof entitlementsStore.loadForTenant === 'function') {
await entitlementsStore.loadForTenant(tid, { force: true })
} else if (typeof entitlementsStore.fetch === 'function') {
await entitlementsStore.fetch(tid, { force: true })
}
toast.add({
severity: 'success',
summary: 'Plano atualizado',
detail: `Agora você está no plano ${planKeyById(subscription.value.plan_id) || ''}`.trim(),
detail: `Agora você está no plano ${planKeyById(subscription.value.plan_id) || subscription.value.plan_key || ''}`.trim(),
life: 3000
})
// ✅ garante consistência (principalmente se RPC mexer em mais campos)
await fetchAll()
// ✅ dispara o guard novamente: se o usuário perdeu acesso a uma rota PRO,
// ele deve ser redirecionado automaticamente.
await revalidateCurrentRoute()
} catch (e) {
toast.add({ severity: 'error', summary: 'Erro', detail: e.message || String(e), life: 5000 })
} finally {
@@ -205,7 +278,11 @@ async function changePlan(targetPlanId) {
}
}
onMounted(fetchAll)
onMounted(async () => {
// ✅ garante que o tenant já foi carregado antes de buscar planos
if (!tenantStore.loaded) await tenantStore.loadSessionAndTenant()
await fetchAll()
})
// se trocar tenant ativo, recarrega
watch(
@@ -393,4 +470,4 @@ watch(
Observação: alguns recursos PRO podem depender de configuração inicial (ex.: SMS exige provedor).
</div>
</div>
</template>
</template>