From d6cf44b1d483dad21bda6f2f64c9b97769adb4ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Sat, 11 Oct 2025 23:49:16 -0300 Subject: [PATCH] fixing-user-endpoint --- susconecta/app/api/assign-role/route.ts | 158 ---------------- susconecta/app/profissional/page.tsx | 237 +++++++++++++++++++----- susconecta/lib/api.ts | 100 +++++++--- 3 files changed, 267 insertions(+), 228 deletions(-) delete mode 100644 susconecta/app/api/assign-role/route.ts diff --git a/susconecta/app/api/assign-role/route.ts b/susconecta/app/api/assign-role/route.ts deleted file mode 100644 index 3363262..0000000 --- a/susconecta/app/api/assign-role/route.ts +++ /dev/null @@ -1,158 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; -import { createClient } from '@supabase/supabase-js'; - -/** - * Endpoint server-side para atribuir roles aos usuários - * Usa SUPABASE_SERVICE_ROLE_KEY para realizar operações administrativas - * - * POST /api/assign-role - * Body: { user_id: string, role: string } - */ -export async function POST(request: NextRequest) { - try { - // 1. Verificar autenticação do requisitante - const authHeader = request.headers.get('authorization'); - if (!authHeader) { - return NextResponse.json( - { error: 'Unauthorized', message: 'Token de autenticação não fornecido' }, - { status: 401 } - ); - } - - // 2. Extrair dados do body - const body = await request.json(); - const { user_id, role } = body; - - if (!user_id || !role) { - return NextResponse.json( - { error: 'Bad Request', message: 'user_id e role são obrigatórios' }, - { status: 400 } - ); - } - - // 3. Validar role - const validRoles = ['admin', 'gestor', 'medico', 'secretaria', 'user']; - if (!validRoles.includes(role)) { - return NextResponse.json( - { error: 'Bad Request', message: `Role inválido. Valores aceitos: ${validRoles.join(', ')}` }, - { status: 400 } - ); - } - - // 4. Obter service role key do ambiente - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY; - - if (!supabaseUrl || !serviceRoleKey) { - console.error('❌ [ASSIGN-ROLE] SUPABASE_SERVICE_ROLE_KEY não configurada'); - return NextResponse.json( - { - error: 'Server Configuration Error', - message: 'Service role key não configurada no servidor. Entre em contato com o administrador do sistema.', - hint: 'Configure SUPABASE_SERVICE_ROLE_KEY nas variáveis de ambiente do servidor' - }, - { status: 500 } - ); - } - - // 5. Criar cliente Supabase com service role key - const supabaseAdmin = createClient(supabaseUrl, serviceRoleKey, { - auth: { - autoRefreshToken: false, - persistSession: false, - }, - }); - - // 6. Verificar se o usuário existe - const { data: userData, error: userError } = await supabaseAdmin.auth.admin.getUserById(user_id); - - if (userError || !userData) { - console.error('❌ [ASSIGN-ROLE] Usuário não encontrado:', userError); - return NextResponse.json( - { error: 'Not Found', message: 'Usuário não encontrado no sistema de autenticação' }, - { status: 404 } - ); - } - - console.log(`🔐 [ASSIGN-ROLE] Atribuindo role "${role}" ao usuário ${user_id}`); - - // 7. Inserir role na tabela user_roles - const { data: roleData, error: roleError } = await supabaseAdmin - .from('user_roles') - .insert({ - user_id: user_id, - role: role, - created_at: new Date().toISOString(), - }) - .select() - .single(); - - if (roleError) { - // Verificar se é erro de duplicação (usuário já tem esse role) - if (roleError.code === '23505') { - console.log(`⚠️ [ASSIGN-ROLE] Usuário já possui o role "${role}"`); - return NextResponse.json( - { - success: true, - message: `Usuário já possui o role "${role}"`, - user_id, - role, - already_exists: true - }, - { status: 200 } - ); - } - - console.error('❌ [ASSIGN-ROLE] Erro ao inserir role:', roleError); - return NextResponse.json( - { - error: 'Database Error', - message: `Erro ao atribuir role: ${roleError.message}`, - code: roleError.code, - details: roleError.details - }, - { status: 500 } - ); - } - - console.log(`✅ [ASSIGN-ROLE] Role "${role}" atribuído com sucesso ao usuário ${user_id}`); - - // 8. Retornar sucesso - return NextResponse.json( - { - success: true, - message: `Role "${role}" atribuído com sucesso`, - data: roleData, - user_id, - role, - }, - { status: 200 } - ); - - } catch (error: any) { - console.error('❌ [ASSIGN-ROLE] Erro inesperado:', error); - return NextResponse.json( - { - error: 'Internal Server Error', - message: 'Erro inesperado ao atribuir role', - details: error?.message || String(error) - }, - { status: 500 } - ); - } -} - -// Método OPTIONS para CORS (se necessário) -export async function OPTIONS(request: NextRequest) { - return NextResponse.json( - {}, - { - status: 200, - headers: { - 'Access-Control-Allow-Origin': '*', - 'Access-Control-Allow-Methods': 'POST, OPTIONS', - 'Access-Control-Allow-Headers': 'Content-Type, Authorization', - }, - } - ); -} diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx index 4a399fd..40719cf 100644 --- a/susconecta/app/profissional/page.tsx +++ b/susconecta/app/profissional/page.tsx @@ -4,7 +4,7 @@ import SignatureCanvas from "react-signature-canvas"; import Link from "next/link"; import ProtectedRoute from "@/components/ProtectedRoute"; import { useAuth } from "@/hooks/useAuth"; -import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, type Paciente, buscarRelatorioPorId } from "@/lib/api"; +import { buscarPacientes, listarPacientes, buscarPacientePorId, buscarPacientesPorIds, buscarMedicoPorId, buscarMedicosPorIds, buscarMedicos, type Paciente, buscarRelatorioPorId, atualizarMedico } from "@/lib/api"; import { useReports } from "@/hooks/useReports"; import { CreateReportData } from "@/types/report-types"; import { Button } from "@/components/ui/button"; @@ -48,17 +48,9 @@ const FullCalendar = dynamic(() => import("@fullcalendar/react"), { ssr: false, }); -const pacientes = [ - { nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, statusLaudo: "Finalizado" }, - { nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, statusLaudo: "Pendente" }, - { nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, statusLaudo: "Rascunho" }, -]; +// pacientes will be loaded inside the component (hooks must run in component body) -const medico = { - nome: "Dr. Carlos Andrade", - identificacao: "CRM 000000 • Cardiologia e Dermatologia", - fotoUrl: "", -} +// removed static medico placeholder; will load real profile for logged-in user const colorsByType = { @@ -119,18 +111,112 @@ const ProfissionalPage = () => { // Estados para o perfil do médico const [isEditingProfile, setIsEditingProfile] = useState(false); + const [doctorId, setDoctorId] = useState(null); + // Removemos o placeholder extenso — inicializamos com valores minimalistas e vazios. const [profileData, setProfileData] = useState({ - nome: "Dr. Carlos Andrade", - email: user?.email || "carlos.andrade@hospital.com", - telefone: "(11) 99999-9999", - endereco: "Rua das Flores, 123 - Centro", - cidade: "São Paulo", - cep: "01234-567", - crm: "CRM 000000", - especialidade: "Cardiologia e Dermatologia", - biografia: "Médico especialista em cardiologia e dermatologia com mais de 15 anos de experiência em tratamentos clínicos e cirúrgicos." + nome: '', + email: user?.email || '', + telefone: '', + endereco: '', + cidade: '', + cep: '', + crm: '', + especialidade: '', + // biografia field removed — not present in Medico records + fotoUrl: '' }); + // pacientes carregados dinamicamente (hooks devem ficar dentro do componente) + const [pacientes, setPacientes] = useState([]); + useEffect(() => { + let mounted = true; + (async () => { + try { + if (!user || !user.id) { + if (mounted) setPacientes([]); + return; + } + + const assignmentsMod = await import('@/lib/assignment'); + if (!assignmentsMod || typeof assignmentsMod.listAssignmentsForUser !== 'function') { + if (mounted) setPacientes([]); + return; + } + + const assignments = await assignmentsMod.listAssignmentsForUser(user.id || ''); + const patientIds = Array.isArray(assignments) ? assignments.map((a:any) => String(a.patient_id)).filter(Boolean) : []; + if (!patientIds.length) { + if (mounted) setPacientes([]); + return; + } + + const patients = await buscarPacientesPorIds(patientIds); + const normalized = (patients || []).map((p: any) => ({ + ...p, + nome: p.full_name ?? (p as any).nome ?? '', + cpf: p.cpf ?? '', + idade: getPatientAge(p) // preencher idade para a tabela de pacientes + })); + if (mounted) setPacientes(normalized); + } catch (err) { + console.warn('[ProfissionalPage] falha ao carregar pacientes atribuídos:', err); + if (mounted) setPacientes([]); + } + })(); + return () => { mounted = false; }; + }, [user?.id, doctorId]); + + // Carregar perfil do médico correspondente ao usuário logado + useEffect(() => { + let mounted = true; + (async () => { + try { + if (!user || !user.email) return; + // Tenta buscar médicos pelo email do usuário (buscarMedicos lida com queries por email) + const docs = await buscarMedicos(user.email); + if (!mounted) return; + if (Array.isArray(docs) && docs.length > 0) { + // preferir registro cujo user_id bate com user.id + let chosen = docs.find(d => String((d as any).user_id) === String(user.id)) || docs[0]; + if (chosen) { + // store the doctor's id so we can update it later + try { setDoctorId((chosen as any).id ?? null); } catch {}; + // Especialidade pode vir como 'specialty' (inglês), 'especialidade' (pt), + // ou até uma lista/array. Normalizamos para string. + const rawSpecialty = (chosen as any).specialty ?? (chosen as any).especialidade ?? (chosen as any).especialidades ?? (chosen as any).especiality; + let specialtyStr = ''; + if (Array.isArray(rawSpecialty)) { + specialtyStr = rawSpecialty.join(', '); + } else if (rawSpecialty) { + specialtyStr = String(rawSpecialty); + } + + // Foto pode vir como 'foto_url' ou 'fotoUrl' ou 'avatar_url' + const foto = (chosen as any).foto_url || (chosen as any).fotoUrl || (chosen as any).avatar_url || ''; + + setProfileData((prev) => ({ + ...prev, + nome: (chosen as any).full_name || (chosen as any).nome_social || prev.nome || user?.email?.split('@')[0] || '', + email: (chosen as any).email || user?.email || prev.email, + telefone: (chosen as any).phone_mobile || (chosen as any).celular || (chosen as any).telefone || (chosen as any).phone || (chosen as any).mobile || (user as any)?.user_metadata?.phone || prev.telefone, + endereco: (chosen as any).street || (chosen as any).endereco || prev.endereco, + cidade: (chosen as any).city || (chosen as any).cidade || prev.cidade, + cep: (chosen as any).cep || prev.cep, + crm: (chosen as any).crm ? `CRM ${(chosen as any).crm}` : (prev.crm || ''), + especialidade: specialtyStr || prev.especialidade || '', + // biografia removed: prefer to ignore observacoes/curriculo_url here + // (if needed elsewhere, render directly from chosen.observacoes) + fotoUrl: foto || prev.fotoUrl || '' + })); + } + } + } catch (e) { + console.warn('[ProfissionalPage] falha ao carregar perfil do médico pelo email:', e); + } + })(); + return () => { mounted = false; }; + }, [user?.id, user?.email]); + // Estados para campos principais da consulta @@ -251,8 +337,37 @@ const ProfissionalPage = () => { }; const handleSaveProfile = () => { - setIsEditingProfile(false); - alert('Perfil atualizado com sucesso!'); + (async () => { + if (!doctorId) { + alert('Não foi possível localizar o registro do médico para atualizar.'); + setIsEditingProfile(false); + return; + } + + // Build payload mapping UI fields to DB columns + const payload: any = {}; + if (profileData.email) payload.email = profileData.email; + if (profileData.telefone) payload.phone_mobile = profileData.telefone; + if (profileData.endereco) payload.street = profileData.endereco; + if (profileData.cidade) payload.city = profileData.cidade; + if (profileData.cep) payload.cep = profileData.cep; + if (profileData.especialidade) payload.specialty = profileData.especialidade || profileData.especialidade; + if (profileData.fotoUrl) payload.foto_url = profileData.fotoUrl; + + // Don't allow updating full_name or crm from this UI + + try { + const updated = await atualizarMedico(doctorId, payload as any); + console.debug('[ProfissionalPage] médico atualizado:', updated); + alert('Perfil atualizado com sucesso!'); + } catch (err: any) { + console.error('[ProfissionalPage] falha ao atualizar médico:', err); + // Mostrar mensagem amigável (o erro já é tratado em lib/api) + alert(err?.message || 'Falha ao atualizar perfil. Verifique logs.'); + } finally { + setIsEditingProfile(false); + } + })(); }; const handleCancelEdit = () => { @@ -756,7 +871,23 @@ const ProfissionalPage = () => { const reportsMod = await import('@/lib/reports'); if (typeof reportsMod.listarRelatoriosPorPacientes === 'function') { const batch = await reportsMod.listarRelatoriosPorPacientes(patientIds); - if (mounted) setLaudos(batch || []); + // Enrich reports with paciente objects so UI shows name/cpf immediately + const enriched = await (async (reportsArr: any[]) => { + if (!reportsArr || !reportsArr.length) return reportsArr; + const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean); + if (!pids.length) return reportsArr; + try { + const patients = await buscarPacientesPorIds(pids); + const map = new Map((patients || []).map((p: any) => [String(p.id), p])); + return reportsArr.map(r => { + const pid = String(getReportPatientId(r)); + return { ...r, paciente: r.paciente ?? map.get(pid) ?? r.paciente }; + }); + } catch (e) { + return reportsArr; + } + })(batch); + if (mounted) setLaudos(enriched || []); } else { // fallback: 请求 por paciente individual const allReports: any[] = []; @@ -768,7 +899,20 @@ const ProfissionalPage = () => { console.warn('[LaudoManager] falha ao carregar relatórios para paciente', pid, err); } } - if (mounted) setLaudos(allReports); + // enrich fallback results too + const enrichedAll = await (async (reportsArr: any[]) => { + if (!reportsArr || !reportsArr.length) return reportsArr; + const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean); + if (!pids.length) return reportsArr; + try { + const patients = await buscarPacientesPorIds(pids); + const map = new Map((patients || []).map((p: any) => [String(p.id), p])); + return reportsArr.map(r => ({ ...r, paciente: r.paciente ?? map.get(String(getReportPatientId(r))) ?? r.paciente })); + } catch (e) { + return reportsArr; + } + })(allReports); + if (mounted) setLaudos(enrichedAll); } } catch (err) { console.warn('[LaudoManager] erro ao carregar relatórios em batch, tentando por paciente individual', err); @@ -781,7 +925,19 @@ const ProfissionalPage = () => { console.warn('[LaudoManager] falha ao carregar relatórios para paciente', pid, e); } } - if (mounted) setLaudos(allReports); + const enrichedAll = await (async (reportsArr: any[]) => { + if (!reportsArr || !reportsArr.length) return reportsArr; + const pids = reportsArr.map(r => String(getReportPatientId(r))).filter(Boolean); + if (!pids.length) return reportsArr; + try { + const patients = await buscarPacientesPorIds(pids); + const map = new Map((patients || []).map((p: any) => [String(p.id), p])); + return reportsArr.map(r => ({ ...r, paciente: r.paciente ?? map.get(String(getReportPatientId(r))) ?? r.paciente })); + } catch (e) { + return reportsArr; + } + })(allReports); + if (mounted) setLaudos(enrichedAll); } } catch (e) { console.warn('[LaudoManager] erro ao carregar laudos para pacientes atribuídos:', e); @@ -1135,11 +1291,11 @@ const ProfissionalPage = () => {
{(() => { - const signatureName = laudo?.created_by_name ?? laudo?.createdByName ?? ((laudo?.created_by && user?.id && laudo.created_by === user.id) ? 'Squad-20' : medico.nome ?? 'Squad-20'); + const signatureName = laudo?.created_by_name ?? laudo?.createdByName ?? ((laudo?.created_by && user?.id && laudo.created_by === user.id) ? profileData.nome : (laudo?.created_by_name || profileData.nome)); return ( <>

{signatureName}

-

CRM 000000 - {laudo.especialidade}

+

{profileData.crm || 'CRM não informado'} - {laudo.especialidade}

Data: {formatReportDate(getReportDate(laudo))}

); @@ -2255,20 +2411,7 @@ const ProfissionalPage = () => { )}
-
- - {isEditingProfile ? ( -