From a92bd87710dfa2d0273371ffeeb2cfc5e256f971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jo=C3=A3o=20Gustavo?= <166467972+JoaoGustavo-dev@users.noreply.github.com> Date: Tue, 21 Oct 2025 23:31:23 -0300 Subject: [PATCH] add-create-doctor --- .../forms/doctor-registration-form.tsx | 116 +++++------------- susconecta/lib/api.ts | 32 ++++- 2 files changed, 60 insertions(+), 88 deletions(-) diff --git a/susconecta/components/forms/doctor-registration-form.tsx b/susconecta/components/forms/doctor-registration-form.tsx index 8f8a213..63f49f3 100644 --- a/susconecta/components/forms/doctor-registration-form.tsx +++ b/susconecta/components/forms/doctor-registration-form.tsx @@ -1,7 +1,6 @@ "use client"; import { useEffect, useMemo, useState } from "react"; -import { buscarPacientePorId } from "@/lib/api"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; @@ -464,107 +463,56 @@ async function handleSubmit(ev: React.FormEvent) { const savedDoctorProfile = await criarMedico(medicoPayload); console.log("✅ Perfil do médico criado:", savedDoctorProfile); - // 2. Cria usuário no Supabase Auth (direto via /auth/v1/signup) - console.log('🔐 Criando usuário de autenticação...'); - - try { - const authResponse = await criarUsuarioMedico({ - email: form.email, - full_name: form.full_name, - phone_mobile: form.celular || '', - }); + // The server-side Edge Function `criarMedico` should perform the privileged + // operations (create doctor row and auth user) and return a normalized + // envelope or the created doctor object. We rely on that single-call flow + // here instead of creating the auth user from the browser. - if (authResponse.success && authResponse.user) { - console.log('✅ Usuário Auth criado:', authResponse.user.id); + // savedDoctorProfile may be either a Medico object, an envelope with + // { doctor, doctor_id, email, password, user_id } or similar shapes. + const result = savedDoctorProfile as any; + console.log('✅ Resultado de criarMedico:', result); - // Attempt to link the created auth user id to the doctors record - try { - // savedDoctorProfile may be an array or object depending on API - const docId = (savedDoctorProfile && (savedDoctorProfile.id || (Array.isArray(savedDoctorProfile) ? savedDoctorProfile[0]?.id : undefined))) || null; - if (docId) { - console.log('[DoctorForm] Vinculando user_id ao médico:', { doctorId: docId, userId: authResponse.user.id }); - // dynamic import to avoid circular deps in some bundlers - const api = await import('@/lib/api'); - if (api && typeof api.vincularUserIdMedico === 'function') { - await api.vincularUserIdMedico(String(docId), String(authResponse.user.id)); - console.log('[DoctorForm] user_id vinculado com sucesso.'); - } - } else { - console.warn('[DoctorForm] Não foi possível determinar o ID do médico para vincular user_id. Doctor profile:', savedDoctorProfile); - } - } catch (linkErr) { - console.warn('[DoctorForm] Falha ao vincular user_id ao médico:', linkErr); - } + // Determine the doctor id if available + let createdDoctorId: string | null = null; + if (result) { + if (result.id) createdDoctorId = String(result.id); + else if (result.doctor && result.doctor.id) createdDoctorId = String(result.doctor.id); + else if (result.doctor_id) createdDoctorId = String(result.doctor_id); + else if (Array.isArray(result) && result[0]?.id) createdDoctorId = String(result[0].id); + } - // 3. Exibe popup com credenciais + // If the function returned credentials, show them in the credentials dialog + if (result && (result.password || result.email || result.user)) { setCredentials({ - email: authResponse.email, - password: authResponse.password, + email: result.email || form.email, + password: result.password || "", userName: form.full_name, userType: 'médico', }); setShowCredentialsDialog(true); + } - // 4. Limpa formulário - setForm(initial); - setPhotoPreview(null); - setServerAnexos([]); - - // If a photo was selected during creation, upload it now - if (form.photo) { - try { - setUploadingPhoto(true); - const docId = (savedDoctorProfile && (savedDoctorProfile.id || (Array.isArray(savedDoctorProfile) ? savedDoctorProfile[0]?.id : undefined))) || null; - if (docId) await uploadFotoMedico(String(docId), form.photo); - } catch (upErr) { - console.warn('[DoctorForm] Falha ao enviar foto do médico após criação:', upErr); - alert('Médico criado, mas falha ao enviar a foto. Você pode tentar novamente no perfil.'); - } finally { - setUploadingPhoto(false); - } + // Upload photo if provided and we have an id + if (form.photo && createdDoctorId) { + try { + setUploadingPhoto(true); + await uploadFotoMedico(String(createdDoctorId), form.photo); + } catch (upErr) { + console.warn('[DoctorForm] Falha ao enviar foto do médico após criação:', upErr); + alert('Médico criado, mas falha ao enviar a foto. Você pode tentar novamente no perfil.'); + } finally { + setUploadingPhoto(false); } - - // 5. Notifica componente pai - onSaved?.(savedDoctorProfile); - } else { - throw new Error('Falha ao criar usuário de autenticação'); } - } catch (authError: any) { - console.error('❌ Erro ao criar usuário Auth:', authError); - - const errorMsg = authError?.message || String(authError); - - // Mensagens específicas de erro - if (errorMsg.toLowerCase().includes('already registered') || - errorMsg.toLowerCase().includes('already been registered') || - errorMsg.toLowerCase().includes('já está cadastrado')) { - alert( - `⚠️ EMAIL JÁ CADASTRADO\n\n` + - `O email "${form.email}" já possui uma conta no sistema.\n\n` + - `✅ O perfil do médico "${form.full_name}" foi salvo com sucesso.\n\n` + - `❌ Porém, não foi possível criar o login porque este email já está em uso.\n\n` + - `SOLUÇÃO:\n` + - `• Use um email diferente para este médico, OU\n` + - `• Se o médico já tem conta, edite o perfil e vincule ao usuário existente` - ); - } else { - alert( - `⚠️ Médico cadastrado com sucesso, mas houve um problema ao criar o acesso ao sistema.\n\n` + - `✅ Perfil do médico salvo: ${form.full_name}\n\n` + - `❌ Erro ao criar login: ${errorMsg}\n\n` + - `Por favor, entre em contato com o administrador para criar o acesso manualmente.` - ); - } - - // Limpa formulário mesmo com erro + // Cleanup and notify parent setForm(initial); setPhotoPreview(null); setServerAnexos([]); onSaved?.(savedDoctorProfile); if (inline) onClose?.(); else onOpenChange?.(false); - } } } catch (err: any) { console.error("❌ Erro no handleSubmit:", err); diff --git a/susconecta/lib/api.ts b/susconecta/lib/api.ts index a4a8b49..360a89f 100644 --- a/susconecta/lib/api.ts +++ b/susconecta/lib/api.ts @@ -1689,7 +1689,9 @@ export async function listarProfissionais(params?: { page?: number; limit?: numb // Dentro de lib/api.ts export async function criarMedico(input: MedicoInput): Promise { - // Validate required fields according to the OpenAPI for /functions/v1/create-doctor + // Mirror criarPaciente: validate input, normalize fields and call the server-side + // create-doctor Edge Function. Normalize possible envelope responses so callers + // always receive a `Medico` object when possible. if (!input) throw new Error('Dados do médico não informados'); const required = ['email', 'full_name', 'cpf', 'crm', 'crm_uf']; for (const r of required) { @@ -1705,13 +1707,12 @@ export async function criarMedico(input: MedicoInput): Promise { throw new Error('CPF inválido. Deve conter 11 dígitos numéricos.'); } - // Validate CRM UF (two uppercase letters) + // Normalize CRM UF const crmUf = String(input.crm_uf || '').toUpperCase(); if (!/^[A-Z]{2}$/.test(crmUf)) { throw new Error('CRM UF inválido. Deve conter 2 letras maiúsculas (ex: SP, RJ).'); } - // Build payload expected by the Function const payload: any = { email: input.email, full_name: input.full_name, @@ -1721,6 +1722,7 @@ export async function criarMedico(input: MedicoInput): Promise { }; if (input.specialty) payload.specialty = input.specialty; if (input.phone_mobile) payload.phone_mobile = input.phone_mobile; + if (typeof input.phone2 !== 'undefined') payload.phone2 = input.phone2; const url = `${API_BASE}/functions/v1/create-doctor`; const res = await fetch(url, { @@ -1729,7 +1731,29 @@ export async function criarMedico(input: MedicoInput): Promise { body: JSON.stringify(payload), }); - return await parse(res as Response); + const parsed = await parse(res as Response); + if (!parsed) throw new Error('Resposta vazia ao criar médico'); + + // If the function returns an envelope like { doctor: { ... }, doctor_id: '...' } + if (parsed.doctor && typeof parsed.doctor === 'object') return parsed.doctor as Medico; + + // If it returns only a doctor_id, try to fetch full profile + if (parsed.doctor_id) { + try { + const d = await buscarMedicoPorId(String(parsed.doctor_id)); + if (!d) throw new Error('Médico não encontrado após criação'); + return d; + } catch (e) { + throw new Error('Médico criado mas não foi possível recuperar os dados do perfil.'); + } + } + + // If the function returned the doctor object directly + if (parsed.id || parsed.full_name || parsed.cpf) { + return parsed as Medico; + } + + throw new Error('Formato de resposta inesperado ao criar médico'); } /**