diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index e1cc85a..b211975 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -1,138 +1,271 @@ import React, { useState } from "react"; -import { Link } from "react-router-dom"; -import menuItems from "../data/sidebar-items-medico.json"; // Use "sidebar-items-secretaria.json" para secretaria e "sidebar-items-adm.json" para ADM -import TrocardePerfis from "./TrocardePerfis"; +import { Link, useNavigate } from "react-router-dom"; +import menuItems from "../data/sidebar-items-medico.json"; +import TrocardePerfis from "./TrocardePerfis"; +function Sidebar({ menuItems }) { + const [isActive, setIsActive] = useState(true); + const [openSubmenu, setOpenSubmenu] = useState(null); + const [showLogoutModal, setShowLogoutModal] = useState(false); + const navigate = useNavigate(); -// 1. Recebe 'menuItems' e 'onLogout' como props -function Sidebar({ menuItems, onLogout }) { - const [isActive, setIsActive] = useState(true); - const [openSubmenu, setOpenSubmenu] = useState(null); + const toggleSidebar = () => { + setIsActive(!isActive); + }; - const toggleSidebar = () => { - setIsActive(!isActive); - }; + const handleSubmenuClick = (submenuName) => { + setOpenSubmenu(openSubmenu === submenuName ? null : submenuName); + }; - const handleSubmenuClick = (submenuName) => { - setOpenSubmenu(openSubmenu === submenuName ? null : submenuName); - }; + const handleLogoutClick = () => { + setShowLogoutModal(true); + }; - const renderLink = (item) => { - // Links internos (rotas do React Router) - if (item.url && item.url.startsWith("/")) { - return ( - - {item.icon && } - {item.name} - - ); - } + const handleLogoutConfirm = async () => { + try { + const token = localStorage.getItem('token') || + localStorage.getItem('authToken') || + localStorage.getItem('userToken') || + localStorage.getItem('access_token') || + sessionStorage.getItem('token') || + sessionStorage.getItem('authToken'); - // Links externos - return ( - - {item.icon && } - {item.name} - - ); - }; + if (token) { + const response = await fetch('https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); - return ( - + + ); } export default Sidebar; \ No newline at end of file diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 8b4ceb8..45ec0bf 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -1,11 +1,10 @@ -import React, { useState } from 'react'; -import { Link,useNavigate, useLocation } from 'react-router-dom'; +import React, { useState, useRef } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; -function DoctorForm({ onSave, onCancel, formData, setFormData }) { +function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const navigate = useNavigate(); const location = useLocation(); - // Funções para formatar telefone e CPF const FormatTelefones = (valor) => { const digits = String(valor).replace(/\D/g, '').slice(0, 11); return digits @@ -23,20 +22,53 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); }; + + const validarCPF = (cpf) => { + const cpfLimpo = cpf.replace(/\D/g, ''); + + if (cpfLimpo.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpfLimpo)) return false; + + + let soma = 0; + for (let i = 0; i < 9; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); + } + let resto = 11 - (soma % 11); + let digito1 = resto === 10 || resto === 11 ? 0 : resto; + + + soma = 0; + for (let i = 0; i < 10; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (11 - i); + } + resto = 11 - (soma % 11); + let digito2 = resto === 10 || resto === 11 ? 0 : resto; + + + return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + }; + const [avatarUrl, setAvatarUrl] = useState(null); + const [showRequiredModal, setShowRequiredModal] = useState(false); + const [emptyFields, setEmptyFields] = useState([]); + const [cpfError, setCpfError] = useState(''); + + const nomeRef = useRef(null); + const cpfRef = useRef(null); + const emailRef = useRef(null); + const telefoneRef = useRef(null); + const crmUfRef = useRef(null); + const crmRef = useRef(null); const [collapsedSections, setCollapsedSections] = useState({ dadosPessoais: true, - infoMedicas: false, - infoConvenio: false, - endereco: false, contato: false, + endereco: false, }); - const [showModal, setShowModal] = useState(false); - const [showSuccessModal, setShowSuccessModal] = useState(false); - const [errorModalMsg, setErrorModalMsg] = useState(''); - const handleToggleCollapse = (section) => { setCollapsedSections(prevState => ({ ...prevState, @@ -47,6 +79,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { const handleChange = (e) => { const { name, value, type, checked, files } = e.target; + + if (value && emptyFields.includes(name)) { + setEmptyFields(prev => prev.filter(field => field !== name)); + } + + + if (name === 'cpf' && cpfError) { + setCpfError(''); + } + if (type === 'checkbox') { setFormData(prev => ({ ...prev, [name]: checked })); } else if (type === 'file') { @@ -65,6 +107,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { } else if (name.includes('cpf')) { let cpfFormatado = FormatCPF(value); setFormData(prev => ({ ...prev, [name]: cpfFormatado })); + + + const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + if (cpfLimpo.length === 11) { + if (!validarCPF(cpfFormatado)) { + setCpfError('CPF inválido'); + } else { + setCpfError(''); + } + } } else if (name.includes('phone')) { let telefoneFormatado = FormatTelefones(value); setFormData(prev => ({ ...prev, [name]: telefoneFormatado })); @@ -88,48 +140,133 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { state: data.uf || '' })); } else { - setErrorModalMsg('CEP não encontrado!'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cep']); } } catch (error) { - setErrorModalMsg('Erro ao buscar o CEP.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cep']); } } }; + + const scrollToEmptyField = (fieldName) => { + let fieldRef = null; + + switch (fieldName) { + case 'full_name': + fieldRef = nomeRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'cpf': + fieldRef = cpfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'email': + fieldRef = emailRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'phone_mobile': + fieldRef = telefoneRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'crm_uf': + fieldRef = crmUfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'crm': + fieldRef = crmRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + default: + return; + } + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + fieldRef.current.focus(); + + + fieldRef.current.style.border = '2px solid #dc3545'; + fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.style.border = ''; + fieldRef.current.style.boxShadow = ''; + } + }, 3000); + } + }, 300); + }; + const handleSubmit = async () => { - if (!formData.full_name || !formData.cpf || !formData.email || !formData.phone_mobile || !formData.crm_uf || !formData.crm) { - setErrorModalMsg('Por favor, preencha todos os campos obrigatórios.'); - setShowModal(true); + + const missingFields = []; + if (!formData.full_name) missingFields.push('full_name'); + if (!formData.cpf) missingFields.push('cpf'); + if (!formData.email) missingFields.push('email'); + if (!formData.phone_mobile) missingFields.push('phone_mobile'); + if (!formData.crm_uf) missingFields.push('crm_uf'); + if (!formData.crm) missingFields.push('crm'); + + if (missingFields.length > 0) { + setEmptyFields(missingFields); + setShowRequiredModal(true); + + + setTimeout(() => { + if (missingFields.length > 0) { + scrollToEmptyField(missingFields[0]); + } + }, 500); return; } + const cpfLimpo = formData.cpf.replace(/\D/g, ''); if (cpfLimpo.length !== 11) { - setErrorModalMsg('CPF inválido. Por favor, verifique o número digitado.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF deve ter 11 dígitos'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } + + if (!validarCPF(formData.cpf)) { + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF inválido'); + setTimeout(() => scrollToEmptyField('cpf'), 500); + return; + } + + try { await onSave({ ...formData }); - setShowSuccessModal(true); + } catch (error) { - setErrorModalMsg('médico salvo com sucesso'); - setShowModal(true); + + throw error; } }; - const handleCloseSuccessModal = () => { - setShowSuccessModal(false); - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/medicos`); + const handleModalClose = () => { + setShowRequiredModal(false); }; return ( <> - {showModal && ( + + {showRequiredModal && (
Atenção
-

- {errorModalMsg} +

+ {cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'}

+
+ {cpfError ? ( +

{cpfError}

+ ) : ( + <> + {!formData.full_name &&

- Nome

} + {!formData.cpf &&

- CPF

} + {!formData.email &&

- Email

} + {!formData.phone_mobile &&

- Telefone

} + {!formData.crm_uf &&

- Estado do CRM

} + {!formData.crm &&

- CRM

} + + )} +
-
- - - )} - - {showSuccessModal && ( -
-
-
-
Sucesso
- -
- -
-

- Médico salvo com sucesso! -

-
- -
-
@@ -360,12 +434,30 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- + + {cpfError && ( +
+ {cpfError} +
+ )}
- @@ -398,7 +490,14 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- +
@@ -437,11 +536,25 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- +
- +
@@ -498,11 +611,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { - + + + +
diff --git a/src/components/patients/PatientForm.jsx b/src/components/patients/PatientForm.jsx index cd1eb2f..60cf51b 100644 --- a/src/components/patients/PatientForm.jsx +++ b/src/components/patients/PatientForm.jsx @@ -1,16 +1,12 @@ -import React, { useState, useEffect } from 'react'; -import { Link,useNavigate, useLocation } from 'react-router-dom'; +import React, { useState, useEffect, useRef } from 'react'; +import { Link } from 'react-router-dom'; import { FormatTelefones, FormatPeso, FormatCPF } from '../utils/Formatar/Format'; -function PatientForm({ onSave, formData, setFormData }) { - - - const [errorModalMsg, setErrorModalMsg] = useState(""); - const [showModal, setShowModal] = useState(false); - const [pacienteExistente, setPacienteExistente] = useState(null); - const [showSuccessModal, setShowSuccessModal] = useState(false); - +function PatientForm({ onSave, onCancel, formData, setFormData, isLoading }) { const [avatarUrl, setAvatarUrl] = useState(null); + const [showRequiredModal, setShowRequiredModal] = useState(false); + const [emptyFields, setEmptyFields] = useState([]); + const [cpfError, setCpfError] = useState(''); const [collapsedSections, setCollapsedSections] = useState({ dadosPessoais: true, infoMedicas: false, @@ -19,6 +15,41 @@ function PatientForm({ onSave, formData, setFormData }) { contato: false, }); + const nomeRef = useRef(null); + const cpfRef = useRef(null); + const emailRef = useRef(null); + const telefoneRef = useRef(null); + + + const validarCPF = (cpf) => { + const cpfLimpo = cpf.replace(/\D/g, ''); + + + if (cpfLimpo.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpfLimpo)) return false; + + + let soma = 0; + for (let i = 0; i < 9; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); + } + let resto = 11 - (soma % 11); + let digito1 = resto === 10 || resto === 11 ? 0 : resto; + + + soma = 0; + for (let i = 0; i < 10; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (11 - i); + } + resto = 11 - (soma % 11); + let digito2 = resto === 10 || resto === 11 ? 0 : resto; + + + return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + }; + const handleToggleCollapse = (section) => { setCollapsedSections(prev => ({ ...prev, @@ -40,6 +71,16 @@ function PatientForm({ onSave, formData, setFormData }) { const handleChange = (e) => { const { name, value, type, checked, files } = e.target; + + if (value && emptyFields.includes(name)) { + setEmptyFields(prev => prev.filter(field => field !== name)); + } + + + if (name === 'cpf' && cpfError) { + setCpfError(''); + } + if (type === 'file') { setFormData(prev => ({ ...prev, [name]: files[0] })); @@ -51,7 +92,17 @@ function PatientForm({ onSave, formData, setFormData }) { setAvatarUrl(null); } } else if (name === 'cpf') { - setFormData(prev => ({ ...prev, cpf: FormatCPF(value) })); + const cpfFormatado = FormatCPF(value); + setFormData(prev => ({ ...prev, cpf: cpfFormatado })); + + const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + if (cpfLimpo.length === 11) { + if (!validarCPF(cpfFormatado)) { + setCpfError('CPF inválido'); + } else { + setCpfError(''); + } + } } else if (name.includes('phone')) { setFormData(prev => ({ ...prev, [name]: FormatTelefones(value) })); } else if (name.includes('weight_kg') || name.includes('height_m')) { @@ -63,35 +114,114 @@ function PatientForm({ onSave, formData, setFormData }) { } }; + const scrollToEmptyField = (fieldName) => { + let fieldRef = null; + + switch (fieldName) { + case 'full_name': + fieldRef = nomeRef; + + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'cpf': + fieldRef = cpfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'email': + fieldRef = emailRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'phone_mobile': + fieldRef = telefoneRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + default: + return; + } + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + fieldRef.current.focus(); + + + fieldRef.current.style.border = '2px solid #dc3545'; + fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.style.border = ''; + fieldRef.current.style.boxShadow = ''; + } + }, 3000); + } + }, 300); + }; + const handleSubmit = async () => { - // ALTERADO: Nome, CPF, Email e Telefone - if (!formData.full_name || !formData.cpf || !formData.email || !formData.phone_mobile) { - setErrorModalMsg('Por favor, preencha Nome, CPF, Email e Telefone.'); - setShowModal(true); + + const missingFields = []; + if (!formData.full_name) missingFields.push('full_name'); + if (!formData.cpf) missingFields.push('cpf'); + if (!formData.email) missingFields.push('email'); + if (!formData.phone_mobile) missingFields.push('phone_mobile'); + + if (missingFields.length > 0) { + setEmptyFields(missingFields); + setShowRequiredModal(true); + + + setTimeout(() => { + if (missingFields.length > 0) { + scrollToEmptyField(missingFields[0]); + } + }, 500); + return; } + const cpfLimpo = formData.cpf.replace(/\D/g, ''); if (cpfLimpo.length !== 11) { - setErrorModalMsg('CPF inválido. Por favor, verifique o número digitado.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF deve ter 11 dígitos'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } - try { - await onSave({ ...formData, bmi: parseFloat(formData.bmi) || 0 }); - setShowSuccessModal(true); - } catch (error) { - setErrorModalMsg('Erro ao salvar paciente. Tente novamente.'); - setShowModal(true); + + if (!validarCPF(formData.cpf)) { + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF inválido'); + setTimeout(() => scrollToEmptyField('cpf'), 500); + return; } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(formData.email)) { + throw new Error('Email inválido. Por favor, verifique o email digitado.'); + } + + + await onSave({ ...formData, bmi: parseFloat(formData.bmi) || null }); + }; + + const handleModalClose = () => { + setShowRequiredModal(false); }; return (

MediConnect

- {/* DADOS PESSOAIS - MANTIDO O LAYOUT ORIGINAL */} + {/* DADOS PESSOAIS */}

handleToggleCollapse('dadosPessoais')} style={{ fontSize: '1.8rem' }}> Dados Pessoais @@ -141,10 +271,20 @@ function PatientForm({ onSave, formData, setFormData }) { {formData.foto && {formData.foto.name}}

- {/* CADASTRO - MANTIDO O LAYOUT ORIGINAL COM COLUNAS */} + + {/* CAMPOS OBRIGATÓRIOS */}
- +
@@ -152,11 +292,25 @@ function PatientForm({ onSave, formData, setFormData }) {
- +
- @@ -165,7 +319,21 @@ function PatientForm({ onSave, formData, setFormData }) {
- + + {cpfError && ( +
+ {cpfError} +
+ )}
@@ -254,7 +422,7 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* CAMPOS MOVIDOS */} + {/* CAMPOS ADICIONAIS */}
@@ -272,7 +440,7 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* INFORMAÇÕES MÉDICAS - MANTIDO O LAYOUT ORIGINAL */} + {/* INFORMAÇÕES MÉDICAS */}

handleToggleCollapse('infoMedicas')} style={{ fontSize: '1.8rem' }}> Informações Médicas @@ -313,7 +481,7 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* INFORMAÇÕES DE CONVÊNIO - MANTIDO O LAYOUT ORIGINAL */} + {/* INFORMAÇÕES DE CONVÊNIO */}

handleToggleCollapse('infoConvenio')} style={{ fontSize: '1.8rem' }}> Informações de convênio @@ -366,7 +534,7 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* ENDEREÇO - MANTIDO O LAYOUT ORIGINAL */} + {/* ENDEREÇO */}

handleToggleCollapse('endereco')} style={{ fontSize: '1.8rem' }}> Endereço @@ -408,7 +576,6 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* CONTATO - MANTIDO O LAYOUT ORIGINAL */}

handleToggleCollapse('contato')} style={{ fontSize: '1.8rem' }}> Contato @@ -420,11 +587,29 @@ function PatientForm({ onSave, formData, setFormData }) {
- +
- +
@@ -438,16 +623,8 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* Botões */} -
- - -
- - {/* Modal de erro - EXATAMENTE COMO NA IMAGEM */} - {showModal && ( + + {showRequiredModal && (
- {/* Header */} +
Atenção
- {/* Body */} +

- Por favor, preencha: + {cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'}

- {!formData.full_name &&

- Nome

} - {!formData.cpf &&

- CPF

} - {!formData.email &&

- Email

} - {!formData.phone_mobile &&

- Telefone

} + {cpfError ? ( +

{cpfError}

+ ) : ( + <> + {!formData.full_name &&

- Nome

} + {!formData.cpf &&

- CPF

} + {!formData.email &&

- Email

} + {!formData.phone_mobile &&

- Telefone

} + + )}
- {/* Footer */} +
)} - {/* Modal de sucesso */} - {showSuccessModal && ( -
-
- {/* Header */} -
-
Sucesso
- -
- {/* Body */} -
-

- O cadastro do paciente foi realizado com sucesso. -

-
- - {/* Footer */} -
- + + +
-
-)} -
); } diff --git a/src/pages/DoctorCadastroManager.jsx b/src/pages/DoctorCadastroManager.jsx index 2aeca70..450e7f7 100644 --- a/src/pages/DoctorCadastroManager.jsx +++ b/src/pages/DoctorCadastroManager.jsx @@ -5,88 +5,305 @@ import API_KEY from '../components/utils/apiKeys'; import { useNavigate, useLocation } from 'react-router-dom'; function DoctorCadastroManager() { - const [DoctorDict, setDoctorDict] = useState({}) + const [doctorData, setDoctorData] = useState({}); + const [isLoading, setIsLoading] = useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const navigate = useNavigate(); const location = useLocation(); const { getAuthorizationHeader, isAuthenticated } = useAuth(); const handleSaveDoctor = async (doctorData) => { + setIsLoading(true); const authHeader = getAuthorizationHeader(); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("apikey", API_KEY); - myHeaders.append("Authorization", authHeader); - - console.log(' Dados recebidos do Form:', doctorData); - - const cleanedData = { - full_name: doctorData.full_name, - cpf: doctorData.cpf ? doctorData.cpf.replace(/\D/g, '') : null, - birth_date: doctorData.birth_date || null, - email: doctorData.email, - phone_mobile: doctorData.phone_mobile ? doctorData.phone_mobile.replace(/\D/g, '') : null, - crm_uf: doctorData.crm_uf, - crm: doctorData.crm, - specialty: doctorData.specialty || null, - cep: doctorData.cep ? doctorData.cep.replace(/\D/g, '') : null, - street: doctorData.street || null, - neighborhood: doctorData.neighborhood || null, - city: doctorData.city || null, - state: doctorData.state || null, - number: doctorData.number || null, - complement: doctorData.complement || null, - phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, - }; - - console.log(' Dados limpos para envio:', cleanedData); - - var raw = JSON.stringify(cleanedData); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - try { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + + console.log('Dados recebidos do Form:', doctorData); + + const cleanedData = { + full_name: doctorData.full_name, + cpf: doctorData.cpf ? doctorData.cpf.replace(/\D/g, '') : null, + birth_date: doctorData.birth_date || null, + email: doctorData.email, + phone_mobile: doctorData.phone_mobile ? doctorData.phone_mobile.replace(/\D/g, '') : null, + crm_uf: doctorData.crm_uf, + crm: doctorData.crm, + specialty: doctorData.specialty || null, + cep: doctorData.cep ? doctorData.cep.replace(/\D/g, '') : null, + street: doctorData.street || null, + neighborhood: doctorData.neighborhood || null, + city: doctorData.city || null, + state: doctorData.state || null, + number: doctorData.number || null, + complement: doctorData.complement || null, + phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, + }; + + console.log('Dados limpos para envio:', cleanedData); + + var raw = JSON.stringify(cleanedData); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions); - console.log(" Status da resposta:", response.status); - console.log(" Response ok:", response.ok); + console.log("Status da resposta:", response.status); + console.log("Response ok:", response.ok); if (!response.ok) { - let errorMessage = `Erro HTTP: ${response.status}`; - try { - const errorData = await response.json(); - console.error(" Erro detalhado:", errorData); - errorMessage = errorData.message || errorData.details || errorMessage; - } catch (e) { - const errorText = await response.text(); - console.error(" Erro texto:", errorText); - errorMessage = errorText || errorMessage; + let errorMessage = `Erro ao salvar médico (${response.status})`; + + + const responseText = await response.text(); + console.log("Conteúdo da resposta:", responseText); + + if (responseText) { + try { + const errorData = JSON.parse(responseText); + console.error("Erro detalhado:", errorData); + errorMessage = errorData.message || errorData.details || errorMessage; + } catch (jsonError) { + + errorMessage = responseText || errorMessage; + } + } else { + errorMessage = `Resposta vazia do servidor (${response.status})`; } + throw new Error(errorMessage); } - const result = await response.json(); - console.log("Médico salvo no backend:", result); + const responseText = await response.text(); + let result = null; + + if (responseText) { + try { + result = JSON.parse(responseText); + console.log("Médico salvo no backend:", result); + } catch (jsonError) { + console.warn("Resposta não é JSON válido, mas request foi bem-sucedido"); + result = { success: true, status: response.status }; + } + } else { + console.log("Resposta vazia - assumindo sucesso"); + result = { success: true, status: response.status }; + } - // Redireciona para a lista de médicos do perfil atual - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/medicos`); - - return result; + + setShowSuccessModal(true); } catch (error) { - console.error(" Erro ao salvar Médico:", error); - throw error; + console.error("Erro ao salvar Médico:", error); + + let userFriendlyMessage = error.message; + + if (error.message.includes('doctors_cpf_key') || error.message.includes('duplicate key')) { + userFriendlyMessage = 'Já existe um médico cadastrado com este CPF. Verifique os dados ou edite o médico existente.'; + } else if (error.message.includes('Unexpected end of JSON input')) { + userFriendlyMessage = 'Erro de comunicação com o servidor. Tente novamente.'; + } + + setErrorMessage(userFriendlyMessage); + setShowErrorModal(true); + } finally { + setIsLoading(false); } }; + const handleCloseSuccessModal = () => { + setShowSuccessModal(false); + + const prefixo = location.pathname.split("/")[1]; + navigate(`/${prefixo}/medicos`); + }; + + const handleCloseErrorModal = () => { + setShowErrorModal(false); + }; + return ( <> + + {showSuccessModal && ( +
+
+
+
Sucesso
+ +
+ +
+

+ Médico cadastrado com sucesso! +

+
+ +
+ +
+
+
+ )} + + + {showErrorModal && ( +
+
+
+
Erro
+ +
+ +
+

+ {errorMessage} +

+
+ +
+ +
+
+
+ )} +

Cadastro de Médicos

@@ -95,9 +312,9 @@ function DoctorCadastroManager() {
diff --git a/src/pages/PatientCadastroManager.jsx b/src/pages/PatientCadastroManager.jsx index 21f6ad6..701e6b5 100644 --- a/src/pages/PatientCadastroManager.jsx +++ b/src/pages/PatientCadastroManager.jsx @@ -1,37 +1,98 @@ import {useState} from 'react'; import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import PatientForm from '../components/patients/PatientForm'; import API_KEY from '../components/utils/apiKeys'; import { useAuth } from '../components/utils/AuthProvider'; function PatientCadastroManager( {setCurrentPage} ) { - const navigate = useNavigate(); - const location = useLocation(); + const navigate = useNavigate() const [showModal, setShowModal] = useState(false); const [infosModal, setInfosModal] = useState({title:'', message:''}); + const [isLoading, setIsLoading] = useState(false); const { getAuthorizationHeader, isAuthenticated } = useAuth(); const [formData, setFormData] = useState({}) + + + const validarCPF = (cpf) => { + cpf = cpf.replace(/\D/g, ''); + + if (cpf.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpf)) return false; + + let soma = 0; + let resto; + + for (let i = 1; i <= 9; i++) { + soma = soma + parseInt(cpf.substring(i-1, i)) * (11 - i); + } + + resto = (soma * 10) % 11; + if ((resto === 10) || (resto === 11)) resto = 0; + if (resto !== parseInt(cpf.substring(9, 10))) return false; + + soma = 0; + for (let i = 1; i <= 10; i++) { + soma = soma + parseInt(cpf.substring(i-1, i)) * (12 - i); + } + + resto = (soma * 10) % 11; + if ((resto === 10) || (resto === 11)) resto = 0; + if (resto !== parseInt(cpf.substring(10, 11))) return false; + + return true; + } const handleSavePatient = async (patientData) => { - console.log('🔄 Iniciando salvamento do paciente:', patientData); + console.log(' Iniciando salvamento do paciente:', patientData); + setIsLoading(true); try { + + console.log(' Verificando autenticação...'); + if (!isAuthenticated) { + throw new Error('Usuário não autenticado'); + } + const authHeader = getAuthorizationHeader(); + console.log(' Header de autorização:', authHeader ? 'Presente' : 'Faltando'); + + if (!authHeader) { + throw new Error('Header de autorização não encontrado'); + } + + + const cpfLimpo = patientData.cpf.replace(/\D/g, ''); + if (!validarCPF(cpfLimpo)) { + throw new Error('CPF inválido. Por favor, verifique o número digitado.'); + } var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("apikey", API_KEY); myHeaders.append("Authorization", authHeader); + myHeaders.append("Prefer", "return=representation"); + + console.log(' Headers configurados:', { + 'Content-Type': 'application/json', + 'apikey': API_KEY ? 'Presente' : 'Faltando', + 'Authorization': authHeader ? 'Presente' : 'Faltando', + 'Prefer': 'return=representation' + }); const cleanedData = { full_name: patientData.full_name, - cpf: patientData.cpf.replace(/\D/g, ''), - birth_date: patientData.birth_date, - sex: patientData.sex, + cpf: cpfLimpo, email: patientData.email, phone_mobile: patientData.phone_mobile, + + birth_date: patientData.birth_date || null, + sex: patientData.sex === 'Masculino' ? 'M' : + patientData.sex === 'Feminino' ? 'F' : + patientData.sex || null, social_name: patientData.social_name || null, rg: patientData.rg || null, blood_type: patientData.blood_type || null, @@ -41,9 +102,14 @@ function PatientCadastroManager( {setCurrentPage} ) { notes: patientData.notes || null, }; - console.log('📤 Dados limpos para envio:', cleanedData); + console.log(' Dados limpos para envio:', cleanedData); + + if (!cleanedData.full_name || !cleanedData.cpf || !cleanedData.email || !cleanedData.phone_mobile) { + throw new Error('Dados obrigatórios faltando: nome, CPF, email e telefone são necessários'); + } var raw = JSON.stringify(cleanedData); + console.log(' Payload JSON:', raw); var requestOptions = { method: 'POST', @@ -52,39 +118,74 @@ function PatientCadastroManager( {setCurrentPage} ) { redirect: 'follow' }; + console.log(' Fazendo requisição para API...'); const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions); - console.log('📨 Status da resposta:', response.status); + console.log(' Status da resposta:', response.status); console.log(' Response ok:', response.ok); + let responseData; + try { + responseData = await response.json(); + console.log(' Corpo da resposta:', responseData); + } catch (jsonError) { + console.log(' Não foi possível parsear JSON da resposta:', jsonError); + responseData = { error: 'Resposta inválida da API' }; + } + + if (!response.ok) { - let errorMessage = `Erro HTTP: ${response.status}`; - try { - const errorData = await response.json(); - errorMessage = errorData.message || errorData.details || errorMessage; - } catch (e) { - const errorText = await response.text(); - errorMessage = errorText || errorMessage; + console.error(' Erro da API - Detalhes:', { + status: response.status, + statusText: response.statusText, + data: responseData + }); + + let errorMessage = 'Erro ao salvar paciente'; + + if (response.status === 401) { + errorMessage = 'Não autorizado. Verifique suas credenciais.'; + } else if (response.status === 403) { + errorMessage = 'Acesso proibido. Verifique suas permissões.'; + } else if (response.status === 409) { + errorMessage = 'Paciente com este CPF já existe.'; + } else if (response.status === 400) { + errorMessage = `Dados inválidos: ${responseData.details || responseData.message || 'Verifique os campos'}`; + } else if (response.status === 422) { + errorMessage = `Dados de entrada inválidos: ${responseData.details || 'Verifique o formato dos dados'}`; + } else if (response.status >= 500) { + errorMessage = 'Erro interno do servidor. Tente novamente mais tarde.'; + } else { + errorMessage = `Erro ${response.status}: ${responseData.message || responseData.error || 'Erro desconhecido'}`; } + throw new Error(errorMessage); } - const result = await response.json(); - console.log("Paciente salvo no backend:", result); - - // Redireciona para a lista de pacientes do perfil atual - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/pacientes`); - - return result; - - } catch (error) { - console.error(error); + console.log(' Paciente salvo com sucesso:', responseData); + + setInfosModal({ - title: 'Erro de conexão', - message: 'Não foi possível conectar ao servidor. Verifique sua internet e tente novamente.' + title: 'Sucesso', + message: 'O cadastro do paciente foi realizado com sucesso.' }); setShowModal(true); + + + setTimeout(() => { + setShowModal(false); + navigate('/secretaria/pacientes'); + }, 2000); + + } catch (error) { + console.error(' Erro completo ao salvar paciente:', error); + setInfosModal({ + title: 'Erro', + message: error.message || 'Não foi possível conectar ao servidor. Verifique sua internet e tente novamente.' + }); + setShowModal(true); + } finally { + setIsLoading(false); } }; @@ -92,44 +193,117 @@ function PatientCadastroManager( {setCurrentPage} ) { <>
{showModal &&( -
-
-
-
-
{infosModal.title}
- -
-
-

{infosModal.message}

-
-
- -
+
+
+ +
+
{infosModal.title}
+ +
+ + +
+

+ {infosModal.message} +

+ {infosModal.title === 'Erro' && ( +

+

+ )} +
+ + +
+
)}

Cadastro de Pacientes

+ {isLoading && ( +
+
+ Salvando paciente... +
+ )}
navigate('/secretaria/pacientes')} formData={formData} setFormData={setFormData} + isLoading={isLoading} />