Compare commits

...

1 Commits

4 changed files with 305 additions and 50 deletions

View File

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

View File

@ -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);

View File

@ -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 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 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);

View File

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