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"; "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,107 +463,56 @@ 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) {
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 // If the function returned credentials, show them in the credentials dialog
try { if (result && (result.password || result.email || result.user)) {
// 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
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); try {
setServerAnexos([]); setUploadingPhoto(true);
await uploadFotoMedico(String(createdDoctorId), form.photo);
// If a photo was selected during creation, upload it now } catch (upErr) {
if (form.photo) { console.warn('[DoctorForm] Falha ao enviar foto do médico após criação:', upErr);
try { alert('Médico criado, mas falha ao enviar a foto. Você pode tentar novamente no perfil.');
setUploadingPhoto(true); } finally {
const docId = (savedDoctorProfile && (savedDoctorProfile.id || (Array.isArray(savedDoctorProfile) ? savedDoctorProfile[0]?.id : undefined))) || null; setUploadingPhoto(false);
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);
}
} }
// 5. Notifica componente pai
onSaved?.(savedDoctorProfile);
} else {
throw new Error('Falha ao criar usuário de autenticação');
} }
} catch (authError: any) { // Cleanup and notify parent
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([]);
onSaved?.(savedDoctorProfile); onSaved?.(savedDoctorProfile);
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);

View File

@ -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');
} }
/** /**