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
+166 -40
View File
@@ -2,68 +2,76 @@
import { ref, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { supabase } from '../../lib/supabase/client' // ajuste se o caminho for outro
import { useTenantStore } from '@/stores/tenantStore'
const router = useRouter()
const tenant = useTenantStore()
const checking = ref(true)
const userEmail = ref('')
const role = ref(null)
const role = ref(null) // aqui vai guardar o role REAL do tenant: clinic_admin/therapist/patient
const TEST_ACCOUNTS = {
admin: { email: 'admin@agenciapsi.com.br', password: '123Mudar@' },
clinic_admin: { email: 'clinic@agenciapsi.com.br', password: '123Mudar@' },
therapist: { email: 'therapist@agenciapsi.com.br', password: '123Mudar@' },
patient: { email: 'patient@agenciapsi.com.br', password: '123Mudar@' }
patient: { email: 'patient@agenciapsi.com.br', password: '123Mudar@' },
saas: { email: 'saas@agenciapsi.com.br', password: '123Mudar@' }
}
function roleToPath(r) {
if (r === 'admin') return '/admin'
function roleToPath (r) {
// ✅ role REAL (tenant_members via my_tenants)
if (r === 'clinic_admin' || r === 'tenant_admin' || r === 'admin') return '/admin'
if (r === 'therapist') return '/therapist'
if (r === 'patient') return '/patient'
if (r === 'patient') return '/portal'
return '/'
}
async function fetchMyRole() {
async function isSaasAdmin () {
const { data: userData, error: userErr } = await supabase.auth.getUser()
if (userErr) return null
if (userErr) return false
const user = userData?.user
if (!user) return null
userEmail.value = user.email || ''
if (!user?.id) return false
const { data, error } = await supabase
.from('profiles')
.select('role')
.eq('id', user.id)
.single()
.from('saas_admins')
.select('user_id')
.eq('user_id', user.id)
.maybeSingle()
if (error) return null
return data?.role || null
if (error) return false
return !!data
}
async function go(area) {
// Se já estiver logado, respeita role real (não o card)
// ✅ carrega tenant/role real (my_tenants) e atualiza UI
async function syncTenantRole () {
await tenant.loadSessionAndTenant()
role.value = tenant.activeRole || null
return role.value
}
async function go (area) {
// Se já estiver logado:
const { data: sessionData } = await supabase.auth.getSession()
const session = sessionData?.session
if (session) {
const r = role.value || (await fetchMyRole())
userEmail.value = session.user?.email || userEmail.value || ''
// ✅ se for SaaS master, SEMPRE manda pra /saas (independente do card clicado)
const saas = await isSaasAdmin()
if (saas) return router.push('/saas')
const r = role.value || (await syncTenantRole())
if (!r) return router.push('/auth/login')
return router.push(roleToPath(r))
}
// Se não estiver logado, manda pro login guardando a intenção
sessionStorage.setItem('intended_area', area) // admin/therapist/patient
sessionStorage.setItem('intended_area', area) // clinic_admin/therapist/patient/saas
// ✅ Prefill de login (apenas DEV)
const DEV_PREFILL = import.meta.env.DEV
if (DEV_PREFILL) {
const TEST_ACCOUNTS = {
admin: { email: 'admin@agenciapsi.com.br', password: '123Mudar@' },
therapist: { email: 'therapist@agenciapsi.com.br', password: '123Mudar@' },
patient: { email: 'patient@agenciapsi.com.br', password: '123Mudar@' }
}
const acc = TEST_ACCOUNTS[area]
if (acc) {
sessionStorage.setItem('login_prefill_email', acc.email)
@@ -77,15 +85,32 @@ async function go(area) {
router.push('/auth/login')
}
async function goMyPanel() {
async function goMyPanel () {
if (!role.value) return
// ✅ se for SaaS master, sempre /saas
const saas = await isSaasAdmin()
if (saas) return router.push('/saas')
router.push(roleToPath(role.value))
}
async function logout() {
await supabase.auth.signOut()
role.value = null
userEmail.value = ''
async function logout () {
try {
await supabase.auth.signOut()
} finally {
role.value = null
userEmail.value = ''
// limpa qualquer intenção pendente
sessionStorage.removeItem('redirect_after_login')
sessionStorage.removeItem('intended_area')
// ✅ força redirecionamento para HomeCards (/)
router.replace('/')
// Use router.replace('/') e não push,
// assim o usuário não consegue voltar com o botão "voltar" para uma rota protegida.
}
}
onMounted(async () => {
@@ -94,7 +119,17 @@ onMounted(async () => {
const session = sessionData?.session
if (session) {
role.value = await fetchMyRole()
userEmail.value = session.user?.email || ''
// ✅ se for SaaS master, manda direto pro SaaS
const saas = await isSaasAdmin()
if (saas) {
router.replace('/saas')
return
}
// ✅ role REAL vem do tenantStore (my_tenants)
role.value = await syncTenantRole()
// Se está logado e tem role, manda direto pro painel
if (role.value) {
@@ -201,15 +236,15 @@ onMounted(async () => {
<div class="px-8 pb-10">
<div class="grid grid-cols-12 gap-6">
<!-- ADMIN -->
<div class="col-span-12 md:col-span-4">
<!-- CLÍNICA (antigo ADMIN) -->
<div class="col-span-12 md:col-span-3">
<div
class="group h-full cursor-pointer rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-6 transition-all hover:shadow-xl hover:-translate-y-1"
@click="go('admin')"
@click="go('clinic_admin')"
>
<div class="flex items-center justify-between mb-4">
<div class="text-xl font-semibold text-[var(--text-color)]">
Admin
Clínica
</div>
<i class="pi pi-building text-sm opacity-70" />
</div>
@@ -225,7 +260,7 @@ onMounted(async () => {
</div>
<!-- TERAPEUTA -->
<div class="col-span-12 md:col-span-4">
<div class="col-span-12 md:col-span-3">
<div
class="group h-full cursor-pointer rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-6 transition-all hover:shadow-xl hover:-translate-y-1"
@click="go('therapist')"
@@ -248,7 +283,7 @@ onMounted(async () => {
</div>
<!-- PACIENTE -->
<div class="col-span-12 md:col-span-4">
<div class="col-span-12 md:col-span-3">
<div
class="group h-full cursor-pointer rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-6 transition-all hover:shadow-xl hover:-translate-y-1"
@click="go('patient')"
@@ -270,7 +305,98 @@ onMounted(async () => {
</div>
</div>
<!-- SAAS MASTER -->
<div class="col-span-12 md:col-span-3">
<div
class="group h-full cursor-pointer rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-6 transition-all hover:shadow-xl hover:-translate-y-1"
@click="go('saas')"
>
<div class="flex items-center justify-between mb-4">
<div class="text-xl font-semibold text-[var(--text-color)]">
SaaS (Master)
</div>
<i class="pi pi-shield text-sm opacity-70" />
</div>
<div class="text-sm text-[var(--text-color-secondary)] leading-relaxed">
Acesso global: planos, assinaturas, tenants e saúde da plataforma.
</div>
<div class="mt-6 text-sm font-medium text-primary opacity-80 group-hover:opacity-100 transition">
Acessar painel
</div>
</div>
</div>
</div>
<!-- DEV Usuários cadastrados -->
<div class="mt-10 w-full rounded-2xl border border-[var(--surface-border)] bg-[var(--surface-ground)] p-6">
<div class="flex items-center justify-between mb-4">
<div class="text-sm font-semibold text-[var(--text-color)]">
Usuários do ambiente (Desenvolvimento)
</div>
<span class="text-xs text-[var(--text-color-secondary)] opacity-70">
Identificadores internos
</span>
</div>
<div class="overflow-x-auto">
<table class="w-full text-xs md:text-sm">
<thead>
<tr class="text-left border-b border-[var(--surface-border)]">
<th class="py-2 pr-4 font-medium text-[var(--text-color-secondary)]">ID</th>
<th class="py-2 font-medium text-[var(--text-color-secondary)]">E-mail</th>
</tr>
</thead>
<tbody class="text-[var(--text-color)]">
<tr class="border-b border-[var(--surface-border)]/60">
<td class="py-2 pr-4 font-mono opacity-80">
40a4b683-a0c9-4890-a201-20faf41fca06
</td>
<td class="py-2">
saas@agenciapsi.com.br
</td>
</tr>
<tr class="border-b border-[var(--surface-border)]/60">
<td class="py-2 pr-4 font-mono opacity-80">
523003e7-17ab-4375-b912-040027a75c22
</td>
<td class="py-2">
patient@agenciapsi.com.br
</td>
</tr>
<tr class="border-b border-[var(--surface-border)]/60">
<td class="py-2 pr-4 font-mono opacity-80">
816b24fe-a0c3-4409-b79b-c6c0a6935d03
</td>
<td class="py-2">
clinic@agenciapsi.com.br
</td>
</tr>
<tr>
<td class="py-2 pr-4 font-mono opacity-80">
824f125c-55bb-40f5-a8c4-7a33618b91c7
</td>
<td class="py-2">
therapist@agenciapsi.com.br
</td>
</tr>
</tbody>
</table>
</div>
<div class="mt-4 text-[11px] text-[var(--text-color-secondary)] opacity-70">
Estes usuários existem apenas para fins de teste no ambiente de desenvolvimento.
</div>
</div>
<!-- Rodapé explicativo -->
<div class="mt-10 text-center text-xs text-[var(--text-color-secondary)] opacity-80">