Compare commits
1 Commits
main
...
criar-paci
| Author | SHA1 | Date | |
|---|---|---|---|
| 2ed1363528 |
@ -40,6 +40,28 @@ const buscarEnderecoViaCEP = async (cep: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Função para validar campos numéricos e evitar overflow
|
||||
const validateNumericField = (
|
||||
value: number,
|
||||
fieldName: string,
|
||||
min: number,
|
||||
max: number
|
||||
): number | null => {
|
||||
if (isNaN(value)) {
|
||||
console.warn(`[SecretaryPatientList] Campo ${fieldName} tem valor NaN`);
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value < min || value > max) {
|
||||
console.warn(
|
||||
`[SecretaryPatientList] Campo ${fieldName} fora do intervalo [${min}, ${max}]: ${value}`
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
export function SecretaryPatientList({
|
||||
onOpenAppointment,
|
||||
}: {
|
||||
@ -296,8 +318,8 @@ export function SecretaryPatientList({
|
||||
email: formData.email,
|
||||
phone_mobile: cleanPhone,
|
||||
blood_type: formData.tipo_sanguineo || null,
|
||||
height_m: formData.altura ? parseFloat(formData.altura) : null,
|
||||
weight_kg: formData.peso ? parseFloat(formData.peso) : null,
|
||||
height_m: formData.altura ? validateNumericField(parseFloat(formData.altura), "height_m", 0.5, 3.0) : null,
|
||||
weight_kg: formData.peso ? validateNumericField(parseFloat(formData.peso), "weight_kg", 0, 500) : null,
|
||||
cep: cleanCep,
|
||||
street: formData.endereco.rua || null,
|
||||
number: formData.endereco.numero || null,
|
||||
@ -317,17 +339,56 @@ export function SecretaryPatientList({
|
||||
? formData.endereco.cep.replace(/\D/g, "")
|
||||
: null;
|
||||
|
||||
// Validações básicas obrigatórias
|
||||
if (!formData.nome.trim()) {
|
||||
toast.error("Nome do paciente é obrigatório");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!cleanCpf) {
|
||||
toast.error("CPF é obrigatório");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (cleanCpf.length !== 11) {
|
||||
toast.error("CPF deve ter 11 dígitos");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar se CPF já existe (apenas para criação)
|
||||
if (!formData.id) {
|
||||
console.log("[SecretaryPatientList] Verificando se CPF já existe:", cleanCpf);
|
||||
const cpfJaExiste = await patientService.cpfExists(cleanCpf);
|
||||
if (cpfJaExiste) {
|
||||
toast.error("Este CPF já está registrado no sistema");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!formData.email.trim()) {
|
||||
toast.error("Email é obrigatório");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
if (!cleanPhone) {
|
||||
toast.error("Telefone é obrigatório");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const createData = {
|
||||
full_name: formData.nome,
|
||||
full_name: formData.nome.trim(),
|
||||
cpf: cleanCpf,
|
||||
email: formData.email,
|
||||
email: formData.email.trim(),
|
||||
phone_mobile: cleanPhone,
|
||||
birth_date: formData.dataNascimento || null,
|
||||
social_name: formData.social_name || null,
|
||||
social_name: formData.social_name?.trim() || null,
|
||||
sex: formData.sexo || null,
|
||||
blood_type: formData.tipo_sanguineo || null,
|
||||
weight_kg: formData.peso ? parseFloat(formData.peso) : null,
|
||||
height_m: formData.altura ? parseFloat(formData.altura) : null,
|
||||
weight_kg: formData.peso ? validateNumericField(parseFloat(formData.peso), "weight_kg", 0, 500) : null,
|
||||
height_m: formData.altura ? validateNumericField(parseFloat(formData.altura), "height_m", 0.5, 3.0) : null,
|
||||
street: formData.endereco.rua || null,
|
||||
number: formData.endereco.numero || null,
|
||||
complement: formData.endereco.complemento || null,
|
||||
@ -338,15 +399,45 @@ export function SecretaryPatientList({
|
||||
created_by: user?.id || undefined,
|
||||
};
|
||||
|
||||
console.log("[SecretaryPatientList] Enviando dados:", createData);
|
||||
await patientService.create(createData);
|
||||
toast.success("Paciente cadastrado com sucesso!");
|
||||
}
|
||||
|
||||
setShowModal(false);
|
||||
loadPatients();
|
||||
} catch (error) {
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao salvar paciente:", error);
|
||||
toast.error("Erro ao salvar paciente");
|
||||
|
||||
// Extrair mensagem de erro mais útil
|
||||
let errorMessage = "Erro ao salvar paciente";
|
||||
|
||||
// Verificar se é erro de duplicação de CPF
|
||||
const errorMsg = error?.message ||
|
||||
error?.response?.data?.message ||
|
||||
error?.response?.data?.error ||
|
||||
"";
|
||||
|
||||
if (errorMsg.includes("duplicate key") || errorMsg.includes("patients_cpf_key")) {
|
||||
errorMessage = "Este CPF já está registrado no sistema";
|
||||
} else if (errorMsg.includes("email")) {
|
||||
errorMessage = "Este email já está registrado no sistema";
|
||||
} else if (error?.message) {
|
||||
errorMessage = error.message;
|
||||
} else if (error?.response?.data?.message) {
|
||||
errorMessage = error.response.data.message;
|
||||
} else if (error?.response?.data?.error) {
|
||||
errorMessage = error.response.data.error;
|
||||
} else if (typeof error === "string") {
|
||||
errorMessage = error;
|
||||
}
|
||||
|
||||
console.error("[SecretaryPatientList] Detalhes do erro:", {
|
||||
errorMessage,
|
||||
fullError: error,
|
||||
});
|
||||
|
||||
toast.error(errorMessage);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
|
||||
@ -516,6 +516,17 @@ const PainelAdmin: React.FC = () => {
|
||||
return;
|
||||
}
|
||||
|
||||
// Verificar se CPF já existe (apenas para criação nova)
|
||||
if (!editingPaciente) {
|
||||
console.log("[PainelAdmin] Verificando se CPF já existe:", cpfLimpo);
|
||||
const cpfJaExiste = await patientService.cpfExists(cpfLimpo);
|
||||
if (cpfJaExiste) {
|
||||
toast.error("Este CPF já está registrado no sistema");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Limpar telefone (remover formatação)
|
||||
const phoneLimpo = formPaciente.phone_mobile.replace(/\D/g, "");
|
||||
|
||||
@ -552,20 +563,30 @@ const PainelAdmin: React.FC = () => {
|
||||
loadPacientes();
|
||||
} else {
|
||||
// API create-patient já cria auth user + registro na tabela patients
|
||||
// Enviar apenas os campos obrigatórios/esperados
|
||||
|
||||
if (!user?.id) {
|
||||
toast.error("Erro: ID do usuário não disponível");
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log("[PainelAdmin] Criando paciente com API /create-patient:", {
|
||||
email: patientData.email,
|
||||
full_name: patientData.full_name,
|
||||
cpf: cpfLimpo,
|
||||
phone_mobile: patientData.phone_mobile,
|
||||
phone_mobile: phoneLimpo,
|
||||
birth_date: patientData.birth_date,
|
||||
created_by: user.id,
|
||||
});
|
||||
|
||||
await userService.createPatient({
|
||||
email: patientData.email,
|
||||
full_name: patientData.full_name,
|
||||
cpf: cpfLimpo,
|
||||
phone_mobile: patientData.phone_mobile,
|
||||
birth_date: patientData.birth_date,
|
||||
created_by: user?.id || "", // ID do admin/secretaria que está criando
|
||||
phone_mobile: phoneLimpo,
|
||||
birth_date: patientData.birth_date || undefined,
|
||||
created_by: user.id, // ID do admin/secretaria que está criando
|
||||
});
|
||||
|
||||
toast.success(
|
||||
@ -577,23 +598,58 @@ const PainelAdmin: React.FC = () => {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error("Erro ao salvar paciente:", error);
|
||||
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: { message?: string; error?: string };
|
||||
data?: { message?: string; error?: string; detail?: string };
|
||||
status?: number;
|
||||
statusText?: string;
|
||||
};
|
||||
message?: string;
|
||||
};
|
||||
const errorMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.error ||
|
||||
axiosError?.message ||
|
||||
"Erro ao salvar paciente";
|
||||
|
||||
let errorMessage = "Erro ao salvar paciente";
|
||||
|
||||
// Tentar extrair mensagem de erro mais detalhada
|
||||
if (axiosError?.response?.status === 500) {
|
||||
// Erro do servidor - pode conter detalhes úteis
|
||||
const serverMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.detail ||
|
||||
axiosError?.response?.data?.error ||
|
||||
"";
|
||||
|
||||
// Verificar se é erro de chave duplicada
|
||||
if (serverMessage.includes("duplicate key") || serverMessage.includes("patients_cpf_key")) {
|
||||
errorMessage = "Este CPF já está registrado no sistema";
|
||||
} else if (serverMessage.includes("email")) {
|
||||
errorMessage = "Este email já está registrado no sistema";
|
||||
} else {
|
||||
errorMessage = serverMessage || "Erro do servidor (500) - verifique console para mais detalhes";
|
||||
}
|
||||
} else if (axiosError?.response?.status === 400) {
|
||||
errorMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.error ||
|
||||
"Dados inválidos";
|
||||
} else if (axiosError?.response?.status === 409) {
|
||||
errorMessage = "Paciente com este CPF ou email já existe";
|
||||
} else if (axiosError?.response?.status === 401) {
|
||||
errorMessage = "Não autorizado. Verifique sua sessão";
|
||||
} else if (axiosError?.response?.status === 403) {
|
||||
errorMessage = "Você não tem permissão para criar pacientes";
|
||||
} else if (axiosError?.message) {
|
||||
errorMessage = axiosError.message;
|
||||
}
|
||||
|
||||
toast.error(`Erro: ${errorMessage}`);
|
||||
|
||||
if (axiosError?.response) {
|
||||
console.error("Status:", axiosError.response.status);
|
||||
console.error("Data:", axiosError.response.data);
|
||||
console.error("[PainelAdmin] Detalhes do erro:", {
|
||||
status: axiosError.response.status,
|
||||
statusText: axiosError.response.statusText,
|
||||
data: axiosError.response.data,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
@ -719,6 +775,8 @@ const PainelAdmin: React.FC = () => {
|
||||
cpf: cpfLimpo,
|
||||
crm: medicoData.crm,
|
||||
crm_uf: medicoData.crm_uf,
|
||||
specialty: medicoData.specialty,
|
||||
phone_mobile: phoneLimpo,
|
||||
});
|
||||
|
||||
await userService.createDoctor({
|
||||
@ -740,26 +798,50 @@ const PainelAdmin: React.FC = () => {
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
console.error("Erro ao salvar médico:", error);
|
||||
|
||||
const axiosError = error as {
|
||||
response?: {
|
||||
data?: { message?: string; error?: string };
|
||||
data?: { message?: string; error?: string; detail?: string };
|
||||
status?: number;
|
||||
headers?: unknown;
|
||||
statusText?: string;
|
||||
};
|
||||
message?: string;
|
||||
};
|
||||
const errorMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.error ||
|
||||
axiosError?.message ||
|
||||
"Erro ao salvar médico";
|
||||
|
||||
let errorMessage = "Erro ao salvar médico";
|
||||
|
||||
// Tentar extrair mensagem de erro mais detalhada
|
||||
if (axiosError?.response?.status === 500) {
|
||||
// Erro do servidor - pode conter detalhes úteis
|
||||
errorMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.detail ||
|
||||
axiosError?.response?.data?.error ||
|
||||
"Erro do servidor (500) - verifique console para mais detalhes";
|
||||
} else if (axiosError?.response?.status === 400) {
|
||||
errorMessage =
|
||||
axiosError?.response?.data?.message ||
|
||||
axiosError?.response?.data?.error ||
|
||||
"Dados inválidos";
|
||||
} else if (axiosError?.response?.status === 409) {
|
||||
errorMessage = "Médico com este CPF, email ou CRM já existe";
|
||||
} else if (axiosError?.response?.status === 401) {
|
||||
errorMessage = "Não autorizado. Verifique sua sessão";
|
||||
} else if (axiosError?.response?.status === 403) {
|
||||
errorMessage = "Você não tem permissão para criar médicos";
|
||||
} else if (axiosError?.message) {
|
||||
errorMessage = axiosError.message;
|
||||
}
|
||||
|
||||
toast.error(`Erro: ${errorMessage}`);
|
||||
|
||||
// Log detalhado para debug
|
||||
if (axiosError?.response) {
|
||||
console.error("Status:", axiosError.response.status);
|
||||
console.error("Data:", axiosError.response.data);
|
||||
console.error("Headers:", axiosError.response.headers);
|
||||
console.error("[PainelAdmin] Detalhes do erro:", {
|
||||
status: axiosError.response.status,
|
||||
statusText: axiosError.response.statusText,
|
||||
data: axiosError.response.data,
|
||||
});
|
||||
}
|
||||
} finally {
|
||||
setLoading(false);
|
||||
|
||||
@ -104,11 +104,34 @@ class PatientService {
|
||||
*/
|
||||
async create(data: CreatePatientInput): Promise<Patient> {
|
||||
try {
|
||||
console.log("[patientService] Criando paciente com dados:", data);
|
||||
const response = await apiClient.post<Patient>("/patients", data);
|
||||
console.log("[patientService] Paciente criado com sucesso:", response.data);
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
console.error("Erro ao criar paciente:", error);
|
||||
throw error;
|
||||
} catch (error: any) {
|
||||
console.error("Erro ao criar paciente:", {
|
||||
message: error.message,
|
||||
status: error?.response?.status,
|
||||
statusText: error?.response?.statusText,
|
||||
data: error?.response?.data,
|
||||
originalError: error,
|
||||
});
|
||||
|
||||
// Melhorar mensagem de erro
|
||||
let userMessage = "Erro ao criar paciente";
|
||||
if (error?.response?.status === 400) {
|
||||
userMessage = error?.response?.data?.message || "Dados inválidos";
|
||||
} else if (error?.response?.status === 409) {
|
||||
userMessage = "Paciente com este CPF ou email já existe";
|
||||
} else if (error?.response?.status === 401) {
|
||||
userMessage = "Não autorizado. Verifique sua sessão";
|
||||
} else if (error?.response?.status === 403) {
|
||||
userMessage = "Você não tem permissão para criar pacientes";
|
||||
}
|
||||
|
||||
const customError = new Error(userMessage);
|
||||
(customError as any).originalError = error;
|
||||
throw customError;
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +197,43 @@ class PatientService {
|
||||
* Registro público de paciente (não requer autenticação)
|
||||
* Usa o endpoint register-patient que tem validações rigorosas
|
||||
*/
|
||||
/**
|
||||
* Verifica se um CPF já está registrado
|
||||
*/
|
||||
async cpfExists(cpf: string): Promise<boolean> {
|
||||
try {
|
||||
const cleanCpf = cpf.replace(/\D/g, "");
|
||||
const response = await apiClient.get<Patient[]>(
|
||||
`/patients?cpf=eq.${cleanCpf}`
|
||||
);
|
||||
return response.data && response.data.length > 0;
|
||||
} catch (error: any) {
|
||||
// Se 401, silenciar (não autenticado é ok para essa verificação)
|
||||
if (error?.response?.status !== 401) {
|
||||
console.error("[patientService] Erro ao verificar CPF:", error);
|
||||
}
|
||||
// Em caso de erro, retornar false (deixar o servidor validar)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica se um email já está registrado
|
||||
*/
|
||||
async emailExists(email: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await apiClient.get<Patient[]>(
|
||||
`/patients?email=ilike.${email}`
|
||||
);
|
||||
return response.data && response.data.length > 0;
|
||||
} catch (error: any) {
|
||||
if (error?.response?.status !== 401) {
|
||||
console.error("[patientService] Erro ao verificar email:", error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async register(data: RegisterPatientInput): Promise<RegisterPatientResponse> {
|
||||
try {
|
||||
console.log("[patientService.register] Enviando dados:", data);
|
||||
|
||||
@ -172,14 +172,25 @@ class UserService {
|
||||
console.log("[userService.createDoctor] URL:", url);
|
||||
console.log("[userService.createDoctor] Data:", data);
|
||||
|
||||
const response = await axios.post<CreateDoctorResponse>(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
try {
|
||||
const response = await axios.post<CreateDoctorResponse>(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
console.log("[userService.createDoctor] Sucesso:", response.data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error("[userService.createDoctor] Erro completo:", {
|
||||
message: error.message,
|
||||
status: error?.response?.status,
|
||||
statusText: error?.response?.statusText,
|
||||
data: error?.response?.data,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -198,14 +209,25 @@ class UserService {
|
||||
console.log("[userService.createPatient] URL:", url);
|
||||
console.log("[userService.createPatient] Data:", data);
|
||||
|
||||
const response = await axios.post<CreatePatientResponse>(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
try {
|
||||
const response = await axios.post<CreatePatientResponse>(url, data, {
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
apikey: API_CONFIG.SUPABASE_ANON_KEY,
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
});
|
||||
console.log("[userService.createPatient] Sucesso:", response.data);
|
||||
return response.data;
|
||||
} catch (error: any) {
|
||||
console.error("[userService.createPatient] Erro completo:", {
|
||||
message: error.message,
|
||||
status: error?.response?.status,
|
||||
statusText: error?.response?.statusText,
|
||||
data: error?.response?.data,
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user