add-create-doctor
This commit is contained in:
parent
0fd8cc631e
commit
a92bd87710
@ -1,7 +1,6 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { buscarPacientePorId } from "@/lib/api";
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
@ -464,58 +463,41 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
const savedDoctorProfile = await criarMedico(medicoPayload);
|
const savedDoctorProfile = await criarMedico(medicoPayload);
|
||||||
console.log("✅ Perfil do médico criado:", savedDoctorProfile);
|
console.log("✅ Perfil do médico criado:", savedDoctorProfile);
|
||||||
|
|
||||||
// 2. Cria usuário no Supabase Auth (direto via /auth/v1/signup)
|
// The server-side Edge Function `criarMedico` should perform the privileged
|
||||||
console.log('🔐 Criando usuário de autenticação...');
|
// 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 {
|
// savedDoctorProfile may be either a Medico object, an envelope with
|
||||||
const authResponse = await criarUsuarioMedico({
|
// { doctor, doctor_id, email, password, user_id } or similar shapes.
|
||||||
email: form.email,
|
const result = savedDoctorProfile as any;
|
||||||
full_name: form.full_name,
|
console.log('✅ Resultado de criarMedico:', result);
|
||||||
phone_mobile: form.celular || '',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (authResponse.success && authResponse.user) {
|
// Determine the doctor id if available
|
||||||
console.log('✅ Usuário Auth criado:', authResponse.user.id);
|
let createdDoctorId: string | null = null;
|
||||||
|
if (result) {
|
||||||
// Attempt to link the created auth user id to the doctors record
|
if (result.id) createdDoctorId = String(result.id);
|
||||||
try {
|
else if (result.doctor && result.doctor.id) createdDoctorId = String(result.doctor.id);
|
||||||
// savedDoctorProfile may be an array or object depending on API
|
else if (result.doctor_id) createdDoctorId = String(result.doctor_id);
|
||||||
const docId = (savedDoctorProfile && (savedDoctorProfile.id || (Array.isArray(savedDoctorProfile) ? savedDoctorProfile[0]?.id : undefined))) || null;
|
else if (Array.isArray(result) && result[0]?.id) createdDoctorId = String(result[0].id);
|
||||||
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({
|
setCredentials({
|
||||||
email: authResponse.email,
|
email: result.email || form.email,
|
||||||
password: authResponse.password,
|
password: result.password || "",
|
||||||
userName: form.full_name,
|
userName: form.full_name,
|
||||||
userType: 'médico',
|
userType: 'médico',
|
||||||
});
|
});
|
||||||
setShowCredentialsDialog(true);
|
setShowCredentialsDialog(true);
|
||||||
|
}
|
||||||
|
|
||||||
// 4. Limpa formulário
|
// Upload photo if provided and we have an id
|
||||||
setForm(initial);
|
if (form.photo && createdDoctorId) {
|
||||||
setPhotoPreview(null);
|
|
||||||
setServerAnexos([]);
|
|
||||||
|
|
||||||
// If a photo was selected during creation, upload it now
|
|
||||||
if (form.photo) {
|
|
||||||
try {
|
try {
|
||||||
setUploadingPhoto(true);
|
setUploadingPhoto(true);
|
||||||
const docId = (savedDoctorProfile && (savedDoctorProfile.id || (Array.isArray(savedDoctorProfile) ? savedDoctorProfile[0]?.id : undefined))) || null;
|
await uploadFotoMedico(String(createdDoctorId), form.photo);
|
||||||
if (docId) await uploadFotoMedico(String(docId), form.photo);
|
|
||||||
} catch (upErr) {
|
} catch (upErr) {
|
||||||
console.warn('[DoctorForm] Falha ao enviar foto do médico após criação:', 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.');
|
alert('Médico criado, mas falha ao enviar a foto. Você pode tentar novamente no perfil.');
|
||||||
@ -524,40 +506,7 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. Notifica componente pai
|
// Cleanup and notify parent
|
||||||
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
|
|
||||||
setForm(initial);
|
setForm(initial);
|
||||||
setPhotoPreview(null);
|
setPhotoPreview(null);
|
||||||
setServerAnexos([]);
|
setServerAnexos([]);
|
||||||
@ -565,7 +514,6 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
if (inline) onClose?.();
|
if (inline) onClose?.();
|
||||||
else onOpenChange?.(false);
|
else onOpenChange?.(false);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("❌ Erro no handleSubmit:", err);
|
console.error("❌ Erro no handleSubmit:", err);
|
||||||
// Exibe mensagem amigável ao usuário
|
// Exibe mensagem amigável ao usuário
|
||||||
|
|||||||
@ -1689,7 +1689,9 @@ export async function listarProfissionais(params?: { page?: number; limit?: numb
|
|||||||
|
|
||||||
// Dentro de lib/api.ts
|
// Dentro de lib/api.ts
|
||||||
export async function criarMedico(input: MedicoInput): Promise<Medico> {
|
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');
|
if (!input) throw new Error('Dados do médico não informados');
|
||||||
const required = ['email', 'full_name', 'cpf', 'crm', 'crm_uf'];
|
const required = ['email', 'full_name', 'cpf', 'crm', 'crm_uf'];
|
||||||
for (const r of required) {
|
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.');
|
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();
|
const crmUf = String(input.crm_uf || '').toUpperCase();
|
||||||
if (!/^[A-Z]{2}$/.test(crmUf)) {
|
if (!/^[A-Z]{2}$/.test(crmUf)) {
|
||||||
throw new Error('CRM UF inválido. Deve conter 2 letras maiúsculas (ex: SP, RJ).');
|
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 = {
|
const payload: any = {
|
||||||
email: input.email,
|
email: input.email,
|
||||||
full_name: input.full_name,
|
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.specialty) payload.specialty = input.specialty;
|
||||||
if (input.phone_mobile) payload.phone_mobile = input.phone_mobile;
|
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 url = `${API_BASE}/functions/v1/create-doctor`;
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
@ -1729,7 +1731,29 @@ export async function criarMedico(input: MedicoInput): Promise<Medico> {
|
|||||||
body: JSON.stringify(payload),
|
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');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user