develop #83

Merged
M-Gabrielly merged 426 commits from develop into main 2025-12-04 04:13:15 +00:00
2 changed files with 60 additions and 88 deletions
Showing only changes of commit a92bd87710 - Show all commits

View File

@ -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...');
// 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.
try {
const authResponse = await criarUsuarioMedico({
email: form.email,
full_name: form.full_name,
phone_mobile: form.celular || '',
});
// 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);
if (authResponse.success && authResponse.user) {
console.log('✅ Usuário Auth criado:', authResponse.user.id);
// 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);
}
// 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);
}
// 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);

View File

@ -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<Medico> {
// 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<Medico> {
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<Medico> {
};
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<Medico> {
body: JSON.stringify(payload),
});
return await parse<Medico>(res as Response);
const parsed = await parse<any>(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');
}
/**