diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx
index cf5d2c4..3c7a65a 100644
--- a/src/PagesMedico/DoctorRelatorioManager.jsx
+++ b/src/PagesMedico/DoctorRelatorioManager.jsx
@@ -1,96 +1,164 @@
-// src/PagesMedico/DoctorRelatorioManager.jsx
import API_KEY from '../components/utils/apiKeys';
import { Link } from 'react-router-dom';
import { useState, useEffect } from 'react';
import { useAuth } from '../components/utils/AuthProvider';
import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient';
-import { GetDoctorByID } from '../components/utils/Functions-Endpoints/Doctor';
import { useNavigate } from 'react-router-dom';
import html2pdf from 'html2pdf.js';
import TiptapViewer from './TiptapViewer';
+import './styleMedico/DoctorRelatorioManager.css';
const DoctorRelatorioManager = () => {
const navigate = useNavigate();
const { getAuthorizationHeader } = useAuth();
- const authHeader = getAuthorizationHeader();
- const [RelatoriosFiltrados, setRelatorios] = useState([]);
- const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]);
- const [MedicosComRelatorios, setMedicosComRelatorios] = useState([]);
+ let authHeader = getAuthorizationHeader();
+ const [relatoriosOriginais, setRelatoriosOriginais] = useState([]);
+ const [pacientesData, setPacientesData] = useState({});
const [showModal, setShowModal] = useState(false);
- const [index, setIndex] = useState();
+ const [relatorioModal, setRelatorioModal] = useState(null);
+ const [termoPesquisa, setTermoPesquisa] = useState('');
+ const [filtroExame, setFiltroExame] = useState('');
+ const [examesDisponiveis, setExamesDisponiveis] = useState([]);
+ const [relatoriosFinais, setRelatoriosFinais] = useState([]);
+
+
+ const [paginaAtual, setPaginaAtual] = useState(1);
+ const [itensPorPagina, setItensPorPagina] = useState(10);
- // busca lista de relatórios
useEffect(() => {
- const fetchReports = async () => {
- try {
- var myHeaders = new Headers();
- myHeaders.append('apikey', API_KEY);
- myHeaders.append('Authorization', authHeader);
- var requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
-
- const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
- const data = await res.json();
- setRelatorios(data || []);
- } catch (err) {
- console.error('Erro listar relatórios', err);
- setRelatorios([]);
+ const buscarPacientes = async () => {
+ const pacientesMap = {};
+ for (const relatorio of relatoriosOriginais) {
+ if (!pacientesMap[relatorio.patient_id]) {
+ try {
+ const paciente = await GetPatientByID(relatorio.patient_id, authHeader);
+ if (paciente && paciente.length > 0) {
+ pacientesMap[relatorio.patient_id] = paciente[0];
+ }
+ } catch (error) {
+ console.error('Erro ao buscar paciente:', error);
+ }
+ }
}
+ setPacientesData(pacientesMap);
};
- fetchReports();
+ if (relatoriosOriginais.length > 0) {
+ buscarPacientes();
+ }
+ }, [relatoriosOriginais, authHeader]);
+
+ useEffect(() => {
+ let resultados = relatoriosOriginais;
+ if (termoPesquisa.trim()) {
+ const termo = termoPesquisa.toLowerCase().trim();
+ resultados = resultados.filter(relatorio => {
+ const paciente = pacientesData[relatorio.patient_id];
+ if (!paciente) return false;
+ const nomeMatch = paciente.full_name?.toLowerCase().includes(termo);
+ const cpfMatch = paciente.cpf?.includes(termoPesquisa);
+ return nomeMatch || cpfMatch;
+ });
+ }
+ if (filtroExame.trim()) {
+ const termoExame = filtroExame.toLowerCase().trim();
+ resultados = resultados.filter(relatorio =>
+ relatorio.exam?.toLowerCase().includes(termoExame)
+ );
+ }
+ setRelatoriosFinais(resultados);
+ setPaginaAtual(1);
+ }, [termoPesquisa, filtroExame, relatoriosOriginais, pacientesData]);
+
+ useEffect(() => {
+ var myHeaders = new Headers();
+ myHeaders.append("apikey", API_KEY);
+ myHeaders.append("Authorization", authHeader);
+ var requestOptions = {
+ method: 'GET',
+ headers: myHeaders,
+ redirect: 'follow'
+ };
+ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions)
+ .then(response => response.json())
+ .then(data => {
+ setRelatoriosOriginais(data);
+ setRelatoriosFinais(data);
+ const examesUnicos = [...new Set(data.map(relatorio => relatorio.exam).filter(exam => exam))];
+ setExamesDisponiveis(examesUnicos);
+ })
+ .catch(error => console.log('error', error));
}, [authHeader]);
- // depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes
- useEffect(() => {
- const fetchRelData = async () => {
- const pacientes = [];
- const medicos = [];
- for (let i = 0; i < RelatoriosFiltrados.length; i++) {
- const rel = RelatoriosFiltrados[i];
- // paciente
- try {
- const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
- pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
- } catch (err) {
- pacientes.push(null);
- }
- // médico: tenta created_by ou requested_by id se existir
- try {
- const doctorId = rel.created_by || rel.requested_by || null;
- if (doctorId) {
- // se created_by é id (uuid) usamos GetDoctorByID, senão se requested_by for nome, guardamos nome
- const docRes = await GetDoctorByID(doctorId, authHeader);
- medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
- } else {
- medicos.push({ full_name: rel.requested_by || '' });
- }
- } catch (err) {
- medicos.push({ full_name: rel.requested_by || '' });
- }
- }
- setPacientesComRelatorios(pacientes);
- setMedicosComRelatorios(medicos);
- };
- if (RelatoriosFiltrados.length > 0) fetchRelData();
- else {
- setPacientesComRelatorios([]);
- setMedicosComRelatorios([]);
- }
- }, [RelatoriosFiltrados, authHeader]);
+
+ const totalPaginas = Math.ceil(relatoriosFinais.length / itensPorPagina);
+ const indiceInicial = (paginaAtual - 1) * itensPorPagina;
+ const indiceFinal = indiceInicial + itensPorPagina;
+ const relatoriosPaginados = relatoriosFinais.slice(indiceInicial, indiceFinal);
+
+ const limparFiltros = () => {
+ setTermoPesquisa('');
+ setFiltroExame('');
+ setPaginaAtual(1);
+ };
+
+ const abrirModal = (relatorio) => {
+ setRelatorioModal(relatorio);
+ setShowModal(true);
+ };
const BaixarPDFdoRelatorio = (nome_paciente) => {
const elemento = document.getElementById("folhaA4");
- const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } };
+ const opt = {
+ margin: 0,
+ filename: `relatorio_${nome_paciente || "paciente"}.pdf`,
+ html2canvas: { scale: 2 },
+ jsPDF: { unit: "mm", format: "a4", orientation: "portrait" },
+ };
html2pdf().set(opt).from(elemento).save();
};
+
+ const irParaPagina = (pagina) => {
+ setPaginaAtual(pagina);
+ };
+
+ const avancarPagina = () => {
+ if (paginaAtual < totalPaginas) {
+ setPaginaAtual(paginaAtual + 1);
+ }
+ };
+
+ const voltarPagina = () => {
+ if (paginaAtual > 1) {
+ setPaginaAtual(paginaAtual - 1);
+ }
+ };
+
+
+ const gerarNumerosPaginas = () => {
+ const paginas = [];
+ const paginasParaMostrar = 5;
+
+ let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
+ let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
+
+ inicio = Math.max(1, fim - paginasParaMostrar + 1);
+
+ for (let i = inicio; i <= fim; i++) {
+ paginas.push(i);
+ }
+
+ return paginas;
+ };
+
return (
- {showModal && (
-
+ {showModal && relatorioModal && (
+
-
Relatório de {PacientesComRelatorios[index]?.full_name}
+ Relatório de {pacientesData[relatorioModal.patient_id]?.full_name}
setShowModal(false)}>
@@ -100,32 +168,34 @@ const DoctorRelatorioManager = () => {
Dr - CRM/SP 123456
Avenida - (79) 9 4444-4444
-
-
Paciente: {PacientesComRelatorios[index]?.full_name}
-
Data de nascimento: {PacientesComRelatorios[index]?.birth_date}
-
Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}
- {/* Exibe conteúdo salvo (content_html) */}
+
Paciente: {pacientesData[relatorioModal.patient_id]?.full_name}
+
Data de nascimento: {pacientesData[relatorioModal.patient_id]?.birth_date}
+
Data do exame: { }
+
Exame: {relatorioModal.exam}
Conteúdo do Relatório:
-
+
-
-
Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}
-
Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}
+
Dr {relatorioModal.required_by}
+
Emitido em: 0
- BaixarPDFdoRelatorio(PacientesComRelatorios[index]?.full_name)}> baixar em pdf
- { setShowModal(false) }}>Fechar
+ BaixarPDFdoRelatorio(pacientesData[relatorioModal.patient_id]?.full_name)}>
+ baixar em pdf
+
+ setShowModal(false)}>Fechar
)}
-
Lista de Relatórios
+
+
Lista de Relatórios
+
@@ -133,14 +203,51 @@ const DoctorRelatorioManager = () => {
Relatórios Cadastrados
- Adicionar Relatório
+
+ Adicionar Relatório
+
-
Filtros
-
-
+
+ Filtros
+
+
+
+
+ Buscar por nome ou CPF do paciente
+ setTermoPesquisa(e.target.value)}
+ />
+
+
+
+
+ Filtrar por tipo de exame
+ setFiltroExame(e.target.value)}
+ />
+
+
+
+
+ Limpar
+
+
+
+
+
+ {relatoriosFinais.length} DE {relatoriosOriginais.length} RELATÓRIOS ENCONTRADOS
+
@@ -149,36 +256,104 @@ const DoctorRelatorioManager = () => {
Paciente
- Doutor
+ CPF
+ Exame
- {RelatoriosFiltrados.length > 0 ? (
- RelatoriosFiltrados.map((relatorio, idx) => (
-
- {PacientesComRelatorios[idx]?.full_name}
- {MedicosComRelatorios[idx]?.full_name || relatorio.requested_by || '-'}
-
-
- { setShowModal(true); setIndex(idx); }}>
- Ver Detalhes
-
-
- navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
- Editar
-
-
-
-
- ))
+ {relatoriosPaginados.length > 0 ? (
+ relatoriosPaginados.map((relatorio) => {
+ const paciente = pacientesData[relatorio.patient_id];
+ return (
+
+ {paciente?.full_name || 'Carregando...'}
+ {paciente?.cpf || 'Carregando...'}
+ {relatorio.exam}
+
+
+ abrirModal(relatorio)}>
+ Ver Detalhes
+
+ navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
+ Editar
+
+
+
+
+ );
+ })
) : (
- Nenhum paciente encontrado.
+
+
+
+
+
Nenhum relatório encontrado com os filtros aplicados.
+ {(termoPesquisa || filtroExame) && (
+
+ Limpar filtros
+
+ )}
+
+
+
)}
-
+
+ {relatoriosFinais.length > 0 && (
+
+
+ Itens por página:
+ {
+ setItensPorPagina(Number(e.target.value));
+ setPaginaAtual(1);
+ }}
+ >
+ 5
+ 10
+ 25
+ 50
+
+
+
+
+
+ Página {paginaAtual} de {totalPaginas} •
+ Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, relatoriosFinais.length)} de {relatoriosFinais.length} itens
+
+
+
+
+
+
+
+
+
+
+ {gerarNumerosPaginas().map(pagina => (
+
+ irParaPagina(pagina)}>
+ {pagina}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
+
@@ -188,4 +363,4 @@ const DoctorRelatorioManager = () => {
);
};
-export default DoctorRelatorioManager;
+export default DoctorRelatorioManager;
\ No newline at end of file
diff --git a/src/PagesMedico/styleMedico/DoctorRelatorioManager.css b/src/PagesMedico/styleMedico/DoctorRelatorioManager.css
new file mode 100644
index 0000000..374d049
--- /dev/null
+++ b/src/PagesMedico/styleMedico/DoctorRelatorioManager.css
@@ -0,0 +1,31 @@
+.contador-relatorios {
+ background-color: #1e3a8a;
+ color: white;
+ font-weight: bold;
+ font-size: 14px;
+ padding: 8px 12px;
+ border-radius: 4px;
+ display: inline-block;
+}
+
+.btn-ver-detalhes {
+ background-color: #E6F2FF;
+ color: #004085;
+ border: none;
+}
+
+.btn-ver-detalhes:hover {
+ background-color: #cce5ff;
+ color: #004085;
+}
+
+.btn-editar {
+ background-color: #FFF3CD;
+ color: #856404;
+ border: none;
+}
+
+.btn-editar:hover {
+ background-color: #ffeaa7;
+ color: #856404;
+}
\ No newline at end of file
diff --git a/src/components/Header/Header.css b/src/components/Header/Header.css
index 09ee387..f51e931 100644
--- a/src/components/Header/Header.css
+++ b/src/components/Header/Header.css
@@ -20,6 +20,11 @@
font-size: 24px;
cursor: pointer;
padding: 5px;
+ transition: transform 0.2s ease;
+}
+
+.phone-icon-container:hover {
+ transform: scale(1.1);
}
.phone-icon {
@@ -33,75 +38,173 @@
}
.profile-picture-container {
- width: 40px;
- height: 40px;
+ width: 45px;
+ height: 45px;
border-radius: 50%;
overflow: hidden;
cursor: pointer;
- border: 2px solid #ccc;
- box-shadow: 0 0 5px rgba(0, 0, 0, 0.2);
+ border: 2px solid #007bff;
+ box-shadow: 0 2px 8px rgba(0, 123, 255, 0.3);
+ transition: all 0.3s ease;
+}
+
+.profile-picture-container:hover {
+ transform: scale(1.05);
+ box-shadow: 0 4px 12px rgba(0, 123, 255, 0.4);
+}
+
+.profile-photo {
+ width: 100%;
+ height: 100%;
+ border-radius: 50%;
+ object-fit: cover;
+ display: block;
}
.profile-placeholder {
width: 100%;
height: 100%;
- background-color: #A9A9A9;
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 50%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
position: relative;
}
-.profile-placeholder::after {
- content: '';
- position: absolute;
- top: 50%;
- left: 50%;
- transform: translate(-50%, -50%);
- width: 60%;
- height: 60%;
- background-image: url('data:image/svg+xml;utf8, ');
- background-size: contain;
- background-repeat: no-repeat;
- opacity: 0.8;
+.placeholder-icon {
+ font-size: 20px;
+ color: white;
}
.profile-dropdown {
position: absolute;
- top: 50px;
+ top: 60px;
right: 0;
background-color: white;
- border: 1px solid #ddd;
- border-radius: 5px;
- box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+ border: 1px solid #e0e0e0;
+ border-radius: 12px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
z-index: 1000;
- min-width: 150px;
+ min-width: 180px;
overflow: hidden;
+ animation: dropdownFadeIn 0.2s ease-out;
+}
+
+@keyframes dropdownFadeIn {
+ from {
+ opacity: 0;
+ transform: translateY(-10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.dropdown-button {
background: none;
border: none;
- padding: 10px 15px;
+ padding: 12px 16px;
text-align: left;
cursor: pointer;
font-size: 14px;
color: #333;
transition: background-color 0.2s;
+ display: flex;
+ align-items: center;
+ gap: 8px;
}
.dropdown-button:hover {
- background-color: #f0f0f0;
+ background-color: #f8f9fa;
}
.logout-button {
- color: #cc0000;
+ color: #dc3545;
+ border-top: 1px solid #f0f0f0;
}
.logout-button:hover {
background-color: #ffe0e0;
}
+/* Modal de Logout */
+.logout-modal-overlay {
+ position: fixed;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(0, 0, 0, 0.5);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ z-index: 9999;
+}
+
+.logout-modal-content {
+ background-color: white;
+ padding: 2rem;
+ border-radius: 12px;
+ box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
+ max-width: 400px;
+ width: 90%;
+ text-align: center;
+}
+
+.logout-modal-content h3 {
+ margin-bottom: 1rem;
+ color: #333;
+ font-size: 1.25rem;
+}
+
+.logout-modal-content p {
+ margin-bottom: 2rem;
+ color: #666;
+ line-height: 1.4;
+}
+
+.logout-modal-buttons {
+ display: flex;
+ gap: 1rem;
+ justify-content: center;
+}
+
+.logout-cancel-button {
+ padding: 0.75rem 1.5rem;
+ border: 1px solid #ccc;
+ border-radius: 8px;
+ background-color: transparent;
+ color: #333;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: all 0.2s;
+}
+
+.logout-cancel-button:hover {
+ background-color: #f0f0f0;
+ border-color: #999;
+}
+
+.logout-confirm-button {
+ padding: 0.75rem 1.5rem;
+ border: none;
+ border-radius: 8px;
+ background-color: #dc3545;
+ color: white;
+ cursor: pointer;
+ font-size: 0.9rem;
+ transition: background-color 0.2s;
+}
+
+.logout-confirm-button:hover {
+ background-color: #c82333;
+}
+
+/* Suporte Card */
.suporte-card-overlay {
position: fixed;
top: 0;
@@ -187,6 +290,7 @@
margin-bottom: 0;
}
+/* Chat Online */
.chat-overlay {
position: fixed;
top: 0;
@@ -246,6 +350,7 @@
display: flex;
align-items: center;
justify-content: center;
+ transition: background-color 0.2s;
}
.fechar-chat:hover {
@@ -260,6 +365,7 @@
display: flex;
flex-direction: column;
gap: 1rem;
+ background-color: #fafafa;
}
.mensagem {
@@ -267,33 +373,53 @@
padding: 0.75rem;
border-radius: 12px;
position: relative;
+ animation: messageSlideIn 0.3s ease-out;
+}
+
+@keyframes messageSlideIn {
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
+ to {
+ opacity: 1;
+ transform: translateY(0);
+ }
}
.mensagem.usuario {
align-self: flex-end;
- background-color: #e3f2fd;
+ background-color: #007bff;
+ color: white;
border-bottom-right-radius: 4px;
}
.mensagem.suporte {
align-self: flex-start;
- background-color: #f5f5f5;
+ background-color: white;
+ border: 1px solid #e0e0e0;
border-bottom-left-radius: 4px;
}
.mensagem-texto {
margin-bottom: 0.25rem;
word-wrap: break-word;
+ line-height: 1.4;
}
.mensagem-hora {
font-size: 0.7rem;
- color: #666;
+ opacity: 0.8;
text-align: right;
}
+.mensagem.usuario .mensagem-hora {
+ color: rgba(255, 255, 255, 0.8);
+}
+
.mensagem.suporte .mensagem-hora {
text-align: left;
+ color: #666;
}
.chat-input {
@@ -313,93 +439,52 @@
outline: none;
font-size: 0.9rem;
background-color: white;
+ transition: border-color 0.2s;
}
.chat-campo:focus {
- border-color: #1e3a8a;
+ border-color: #007bff;
}
.chat-enviar {
- background-color: #1e3a8a;
+ background-color: #007bff;
color: white;
border: none;
- padding: 0.75rem 1rem;
+ padding: 0.75rem 1.5rem;
border-radius: 20px;
cursor: pointer;
font-size: 0.9rem;
+ transition: background-color 0.2s;
}
.chat-enviar:hover {
- background-color: #1e40af;
-}
-/* Modal de Logout */
-.logout-modal-overlay {
- position: fixed;
- top: 0;
- left: 0;
- right: 0;
- bottom: 0;
- background-color: rgba(0, 0, 0, 0.5);
- display: flex;
- justify-content: center;
- align-items: center;
- z-index: 9999;
+ background-color: #0056b3;
}
-.logout-modal-content {
- background-color: white;
- padding: 2rem;
- border-radius: 12px;
- box-shadow: 0 8px 24px rgba(0, 0, 0, 0.2);
- max-width: 400px;
- width: 90%;
- text-align: center;
-}
-
-.logout-modal-content h3 {
- margin-bottom: 1rem;
- color: #333;
- font-size: 1.25rem;
-}
-
-.logout-modal-content p {
- margin-bottom: 2rem;
- color: #666;
- line-height: 1.4;
-}
-
-.logout-modal-buttons {
- display: flex;
- gap: 1rem;
- justify-content: center;
-}
-
-.logout-cancel-button {
- padding: 0.75rem 1.5rem;
- border: 1px solid #ccc;
- border-radius: 8px;
- background-color: transparent;
- color: #333;
- cursor: pointer;
- font-size: 0.9rem;
- transition: background-color 0.2s;
-}
-
-.logout-cancel-button:hover {
- background-color: #f0f0f0;
-}
-
-.logout-confirm-button {
- padding: 0.75rem 1.5rem;
- border: none;
- border-radius: 8px;
- background-color: #dc3545;
- color: white;
- cursor: pointer;
- font-size: 0.9rem;
- transition: background-color 0.2s;
-}
-
-.logout-confirm-button:hover {
- background-color: #c82333;
+/* Responsividade */
+@media (max-width: 768px) {
+ .header-container {
+ padding: 10px 15px;
+ }
+
+ .right-corner-elements {
+ gap: 15px;
+ }
+
+ .profile-picture-container {
+ width: 40px;
+ height: 40px;
+ }
+
+ .suporte-card-container,
+ .chat-container {
+ margin-right: 10px;
+ margin-left: 10px;
+ }
+
+ .suporte-card,
+ .chat-online {
+ width: calc(100vw - 20px);
+ max-width: none;
+ }
}
\ No newline at end of file
diff --git a/src/components/Header/Header.jsx b/src/components/Header/Header.jsx
index 449a7aa..1767082 100644
--- a/src/components/Header/Header.jsx
+++ b/src/components/Header/Header.jsx
@@ -9,10 +9,31 @@ const Header = () => {
const [mensagem, setMensagem] = useState('');
const [mensagens, setMensagens] = useState([]);
const [showLogoutModal, setShowLogoutModal] = useState(false);
+ const [avatarUrl, setAvatarUrl] = useState(null);
const navigate = useNavigate();
const chatInputRef = useRef(null);
const mensagensContainerRef = useRef(null);
+
+ useEffect(() => {
+ const loadAvatar = () => {
+ const localAvatar = localStorage.getItem('user_avatar');
+ if (localAvatar) {
+ setAvatarUrl(localAvatar);
+ }
+ };
+
+ loadAvatar();
+
+
+ const handleStorageChange = () => {
+ loadAvatar();
+ };
+
+ window.addEventListener('storage', handleStorageChange);
+ return () => window.removeEventListener('storage', handleStorageChange);
+ }, []);
+
useEffect(() => {
if (isChatOpen && chatInputRef.current) {
chatInputRef.current.focus();
@@ -25,7 +46,12 @@ const Header = () => {
}
}, [mensagens]);
- // Funções de Logout (do seu código)
+
+ const handleLogoutCancel = () => {
+ setShowLogoutModal(false);
+ };
+
+
const handleLogoutClick = () => {
setShowLogoutModal(true);
setIsDropdownOpen(false);
@@ -77,7 +103,7 @@ const Header = () => {
};
const clearAuthData = () => {
- ["token","authToken","userToken","access_token","user","auth","userData"].forEach(key => {
+ ["token", "authToken", "userToken", "access_token", "user", "auth", "userData", "user_avatar"].forEach(key => {
localStorage.removeItem(key);
sessionStorage.removeItem(key);
});
@@ -91,8 +117,6 @@ const Header = () => {
}
};
- const handleLogoutCancel = () => setShowLogoutModal(false);
-
const handleProfileClick = () => {
setIsDropdownOpen(!isDropdownOpen);
if (isSuporteCardOpen) setIsSuporteCardOpen(false);
@@ -100,7 +124,7 @@ const Header = () => {
};
const handleViewProfile = () => {
- navigate('/perfil');
+ navigate('/perfil');
setIsDropdownOpen(false);
};
@@ -160,7 +184,7 @@ const Header = () => {
'Já encaminhei sua solicitação para nossa equipe técnica.',
'Vou ajudar você a resolver isso!'
];
-
+
const respostaSuporte = {
id: Date.now() + 1,
texto: respostas[Math.floor(Math.random() * respostas.length)],
@@ -176,21 +200,21 @@ const Header = () => {
Suporte
Entre em contato conosco através dos canais abaixo
-
+
Email
suporte@mediconnect.com
-
+
-
+
Chat Online
@@ -206,7 +230,7 @@ const Header = () => {
Chat de Suporte
×
-
+
{mensagens.map((msg) => (
@@ -215,7 +239,7 @@ const Header = () => {
))}
-
+
{/* BOTÕES DE AÇÃO */}
-
+
+
{isLoading ? 'Salvando...' : 'Salvar Paciente'}
@@ -642,6 +642,7 @@ function PatientForm({ onSave, onCancel, formData, setFormData, isLoading }) {
+
);
}
diff --git a/src/components/utils/Functions-Endpoints/Doctor.js b/src/components/utils/Functions-Endpoints/Doctor.js
index b4a39a4..688c3c3 100644
--- a/src/components/utils/Functions-Endpoints/Doctor.js
+++ b/src/components/utils/Functions-Endpoints/Doctor.js
@@ -1,15 +1,6 @@
-<<<<<<< HEAD
-import API_KEY from "../apiKeys";
-
-
-
-const GetDoctorByID = async (ID,authHeader) => {
-
-=======
import API_KEY from '../apiKeys';
const GetDoctorByID = async (ID, authHeader) => {
->>>>>>> perfillogin
var myHeaders = new Headers();
myHeaders.append('apikey', API_KEY);
if (authHeader) myHeaders.append('Authorization', authHeader);
@@ -25,48 +16,29 @@ const GetAllDoctors = async (authHeader) => {
myHeaders.append('apikey', API_KEY);
if (authHeader) myHeaders.append('Authorization', authHeader);
- const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
+ const requestOptions = {
+ method: 'GET',
+ headers: myHeaders,
+ redirect: 'follow'
+ };
+
const res = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors?select=id,full_name,crm&limit=500', requestOptions);
const DictMedicos = await res.json();
return DictMedicos;
};
-<<<<<<< HEAD
-}
-
-const GetAllDoctors = async (authHeader) => {
- var myHeaders = new Headers();
- myHeaders.append("apikey", API_KEY);
- myHeaders.append("Authorization", authHeader);
-
- var requestOptions = {
- method: 'GET',
- headers: myHeaders,
- redirect: 'follow'
- };
-
- const result = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
- const DictMedicos = await result.json()
- return DictMedicos
- }
-
-
const GetDoctorByName = async (nome, authHeader) => {
- const Medicos = await GetAllDoctors(authHeader)
+ const Medicos = await GetAllDoctors(authHeader);
- for (let i = 0; i < Medicos.length; i++) {
+ for (let i = 0; i < Medicos.length; i++) {
+ if (Medicos[i].full_name === nome) {
+ console.log('Medico encontrado:', Medicos[i]);
+ return Medicos[i];
+ }
+ }
+
+ console.log("Nenhum médico encontrado com o nome:", nome);
+ return null;
+};
- if (Medicos[i].full_name === nome) {
- console.log('Medico encontrado:', Medicos[i]);
- return Medicos[i];
- }
- else{console.log("nada encontrado")}
- }
-
-
-}
-
-export {GetDoctorByID, GetDoctorByName, GetAllDoctors}
-=======
-export { GetDoctorByID, GetAllDoctors };
->>>>>>> perfillogin
+export { GetDoctorByID, GetDoctorByName, GetAllDoctors };
\ No newline at end of file
diff --git a/src/pages/DisponibilidadesDoctorPage.jsx b/src/pages/DisponibilidadesDoctorPage.jsx
index a035dfe..af4d65c 100644
--- a/src/pages/DisponibilidadesDoctorPage.jsx
+++ b/src/pages/DisponibilidadesDoctorPage.jsx
@@ -101,7 +101,7 @@ const DisponibilidadesDoctorPage = () => {
Disponibilidades por Médico
-
{
}}
>
+ Gerenciar Disponibilidades
-
+ */}
diff --git a/src/pages/DoctorTable.jsx b/src/pages/DoctorTable.jsx
index 9b88010..cd21813 100644
--- a/src/pages/DoctorTable.jsx
+++ b/src/pages/DoctorTable.jsx
@@ -12,7 +12,6 @@ function TableDoctor() {
const [filtroEspecialidade, setFiltroEspecialidade] = useState("Todos");
const [filtroAniversariante, setFiltroAniversariante] = useState(false);
-
const [showFiltrosAvancados, setShowFiltrosAvancados] = useState(false);
const [filtroCidade, setFiltroCidade] = useState("");
const [filtroEstado, setFiltroEstado] = useState("");
@@ -22,6 +21,9 @@ function TableDoctor() {
const [dataFinal, setDataFinal] = useState("");
+ const [paginaAtual, setPaginaAtual] = useState(1);
+ const [itensPorPagina, setItensPorPagina] = useState(10);
+
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedDoctorId, setSelectedDoctorId] = useState(null);
@@ -36,9 +38,9 @@ function TableDoctor() {
setIdadeMaxima("");
setDataInicial("");
setDataFinal("");
+ setPaginaAtual(1);
};
-
const deleteDoctor = async (id) => {
const authHeader = getAuthorizationHeader()
console.log(id, 'teu id')
@@ -63,7 +65,6 @@ function TableDoctor() {
}
};
-
const ehAniversariante = (dataNascimento) => {
if (!dataNascimento) return false;
const hoje = new Date();
@@ -75,7 +76,6 @@ function TableDoctor() {
);
};
-
const calcularIdade = (dataNascimento) => {
if (!dataNascimento) return 0;
const hoje = new Date();
@@ -104,18 +104,16 @@ function TableDoctor() {
fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions)
.then(response => response.json())
- .then(result => {setMedicos(result); console.log(result)})
+ .then(result => setMedicos(result))
.catch(error => console.log('error', error));
}, [isAuthenticated, getAuthorizationHeader]);
-
const medicosFiltrados = Array.isArray(medicos) ? medicos.filter((medico) => {
const buscaNome = medico.full_name?.toLowerCase().includes(search.toLowerCase());
const buscaCPF = medico.cpf?.toLowerCase().includes(search.toLowerCase());
const buscaEmail = medico.email?.toLowerCase().includes(search.toLowerCase());
const passaBusca = search === "" || buscaNome || buscaCPF || buscaEmail;
-
const passaEspecialidade = filtroEspecialidade === "Todos" || medico.specialty === filtroEspecialidade;
const passaAniversario = filtroAniversariante
@@ -132,23 +130,62 @@ function TableDoctor() {
const passaIdadeMinima = idadeMinima ? idade >= parseInt(idadeMinima) : true;
const passaIdadeMaxima = idadeMaxima ? idade <= parseInt(idadeMaxima) : true;
-
const passaDataInicial = dataInicial ?
medico.created_at && new Date(medico.created_at) >= new Date(dataInicial) : true;
const passaDataFinal = dataFinal ?
medico.created_at && new Date(medico.created_at) <= new Date(dataFinal) : true;
-
const resultado = passaBusca && passaEspecialidade && passaAniversario &&
passaCidade && passaEstado && passaIdadeMinima && passaIdadeMaxima &&
passaDataInicial && passaDataFinal;
return resultado;
}) : [];
+
+
+ const totalPaginas = Math.ceil(medicosFiltrados.length / itensPorPagina);
+ const indiceInicial = (paginaAtual - 1) * itensPorPagina;
+ const indiceFinal = indiceInicial + itensPorPagina;
+ const medicosPaginados = medicosFiltrados.slice(indiceInicial, indiceFinal);
+
+
+ const irParaPagina = (pagina) => {
+ setPaginaAtual(pagina);
+ };
+
+ const avancarPagina = () => {
+ if (paginaAtual < totalPaginas) {
+ setPaginaAtual(paginaAtual + 1);
+ }
+ };
+
+ const voltarPagina = () => {
+ if (paginaAtual > 1) {
+ setPaginaAtual(paginaAtual - 1);
+ }
+ };
+
+
+ const gerarNumerosPaginas = () => {
+ const paginas = [];
+ const paginasParaMostrar = 5;
+
+ let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
+ let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
+
+ inicio = Math.max(1, fim - paginasParaMostrar + 1);
+
+ for (let i = inicio; i <= fim; i++) {
+ paginas.push(i);
+ }
+
+ return paginas;
+ };
+
useEffect(() => {
- console.log(` Médicos totais: ${medicos.length}, Filtrados: ${medicosFiltrados.length}`);
- }, [medicos, medicosFiltrados, search]);
+ setPaginaAtual(1);
+ }, [search, filtroEspecialidade, filtroAniversariante, filtroCidade, filtroEstado, idadeMinima, idadeMaxima, dataInicial, dataFinal]);
return (
<>
@@ -169,7 +206,6 @@ function TableDoctor() {
-
{" "}
@@ -180,16 +216,15 @@ function TableDoctor() {
setSearch(e.target.value)}
/>
- Digite o nome completo ou número do CPF
+ Digite o nome completo, CPF ou email
-
-
-
{showFiltrosAvancados && (
Filtros Avançados
-
Cidade
- {/* Data de Cadastro */}
Data inicial
)}
+
+
+
+ {medicosFiltrados.length} DE {medicos.length} MÉDICOS ENCONTRADOS
+
+
-
- {(search || filtroEspecialidade !== "Todos" || filtroAniversariante || // filtroVIP removido
+ {(search || filtroEspecialidade !== "Todos" || filtroAniversariante ||
filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
Filtros ativos:
{search && Busca: "{search}" }
{filtroEspecialidade !== "Todos" && Especialidade: {filtroEspecialidade} }
-
{filtroAniversariante && Aniversariantes }
{filtroCidade && Cidade: {filtroCidade} }
{filtroEstado && Estado: {filtroEstado} }
@@ -340,14 +375,6 @@ function TableDoctor() {
)}
-
-
-
- {medicosFiltrados.length} de {medicos.length} médicos encontrados
-
-
-
-
@@ -360,8 +387,8 @@ function TableDoctor() {
- {medicosFiltrados.length > 0 ? (
- medicosFiltrados.map((medico) => (
+ {medicosPaginados.length > 0 ? (
+ medicosPaginados.map((medico) => (
@@ -371,7 +398,6 @@ function TableDoctor() {
)}
-
{medico.cpf}
@@ -410,13 +436,75 @@ function TableDoctor() {
))
) : (
-
- Nenhum médico encontrado.
+
+
+
+
Nenhum médico encontrado com os filtros aplicados.
+ {(search || filtroEspecialidade !== "Todos" || filtroAniversariante ||
+ filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
+
+ Limpar filtros
+
+ )}
+
)}
+
+ {/* Paginação */}
+ {medicosFiltrados.length > 0 && (
+
+
+ Itens por página:
+ {
+ setItensPorPagina(Number(e.target.value));
+ setPaginaAtual(1);
+ }}
+ >
+ 5
+ 10
+ 25
+ 50
+
+
+
+
+
+ Página {paginaAtual} de {totalPaginas} •
+ Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, medicosFiltrados.length)} de {medicosFiltrados.length} médicos
+
+
+
+
+
+
+
+
+
+
+ {gerarNumerosPaginas().map(pagina => (
+
+ irParaPagina(pagina)}>
+ {pagina}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
diff --git a/src/pages/ProfilePage.jsx b/src/pages/ProfilePage.jsx
index 6d4f10b..ab47356 100644
--- a/src/pages/ProfilePage.jsx
+++ b/src/pages/ProfilePage.jsx
@@ -1,38 +1,186 @@
-// src/pages/ProfilePage.jsx
-import React, { useState } from "react";
+import React, { useState, useEffect, useCallback } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import "./style/ProfilePage.css";
-const simulatedUserData = {
- email: "admin@squad23.com",
- role: "Administrador",
+
+const MOCK_API_BASE_URL = "https://mock.apidog.com/m1/1053378-0-default";
+
+
+const getLocalAvatar = () => localStorage.getItem('user_avatar');
+const setLocalAvatar = (avatarData) => localStorage.setItem('user_avatar', avatarData);
+const clearLocalAvatar = () => localStorage.removeItem('user_avatar');
+
+const ROLES = {
+ ADMIN: "Administrador",
+ SECRETARY: "Secretária",
+ DOCTOR: "Médico",
+ FINANCIAL: "Financeiro"
};
const ProfilePage = () => {
const location = useLocation();
const navigate = useNavigate();
- const getRoleFromPath = () => {
+ const getRoleFromPath = useCallback(() => {
const path = location.pathname;
- if (path.includes("/admin")) return "Administrador";
- if (path.includes("/secretaria")) return "Secretária";
- if (path.includes("/medico")) return "Médico";
- if (path.includes("/financeiro")) return "Financeiro";
- return "Usuário Padrão";
- };
+ if (path.includes("/admin")) return ROLES.ADMIN;
+ if (path.includes("/secretaria")) return ROLES.SECRETARY;
+ if (path.includes("/medico")) return ROLES.DOCTOR;
+ if (path.includes("/financeiro")) return ROLES.FINANCIAL;
+ return "Usuário";
+ }, [location.pathname]);
- const userRole = simulatedUserData.role || getRoleFromPath();
- const userEmail = simulatedUserData.email || "email.nao.encontrado@example.com";
+ const userRole = getRoleFromPath();
const [userName, setUserName] = useState("Admin Padrão");
+ const [userEmail, setUserEmail] = useState("admin@squad23.com");
+ const [avatarUrl, setAvatarUrl] = useState(null);
const [isEditingName, setIsEditingName] = useState(false);
+ const [isUploading, setIsUploading] = useState(false);
+ const [error, setError] = useState(null);
- const handleNameKeyDown = (e) => {
- if (e.key === "Enter") setIsEditingName(false);
+
+ useEffect(() => {
+ const handleEscKey = (event) => {
+ if (event.keyCode === 27) handleClose();
+ };
+
+ document.addEventListener('keydown', handleEscKey);
+ return () => document.removeEventListener('keydown', handleEscKey);
+ }, []);
+
+
+ useEffect(() => {
+ const loadProfileData = () => {
+
+ const localAvatar = getLocalAvatar();
+ if (localAvatar) {
+ setAvatarUrl(localAvatar);
+ }
+ };
+
+ loadProfileData();
+ }, []);
+
+ const handleNameSave = () => {
+ if (userName.trim() === "") {
+ setError("Nome não pode estar vazio");
+ return;
+ }
+
+ setIsEditingName(false);
+ setError(null);
+ };
+
+ const handleNameKeyDown = (event) => {
+ if (event.key === "Enter") handleNameSave();
+ if (event.key === "Escape") {
+ setUserName("Admin Padrão");
+ setIsEditingName(false);
+ setError(null);
+ }
};
const handleClose = () => navigate(-1);
+
+ const handleAvatarUpload = async (event) => {
+ const file = event.target.files[0];
+ if (!file) return;
+
+ setError(null);
+
+
+ const MAX_FILE_SIZE = 5 * 1024 * 1024;
+ const ACCEPTED_TYPES = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
+
+ if (file.size > MAX_FILE_SIZE) {
+ setError("Arquivo muito grande. Máximo 5MB.");
+ return;
+ }
+
+ if (!ACCEPTED_TYPES.includes(file.type)) {
+ setError("Tipo de arquivo não suportado. Use JPEG, PNG, GIF ou WebP.");
+ return;
+ }
+
+ setIsUploading(true);
+
+ try {
+
+ try {
+ const result = await uploadAvatarToMockAPI(file);
+
+ const newAvatarUrl = result.url || result.avatarUrl;
+ if (newAvatarUrl) {
+ setAvatarUrl(newAvatarUrl);
+ setLocalAvatar(newAvatarUrl);
+ console.log('Avatar enviado para API com sucesso');
+ return;
+ }
+ } catch (apiError) {
+
+ console.log('API não disponível, salvando localmente...');
+ }
+
+
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const imageDataUrl = e.target.result;
+ setLocalAvatar(imageDataUrl);
+ setAvatarUrl(imageDataUrl);
+ console.log('Avatar salvo localmente');
+ };
+ reader.readAsDataURL(file);
+
+ } catch (error) {
+
+ console.error('Erro no processamento:', error);
+ const reader = new FileReader();
+ reader.onload = (e) => {
+ const imageDataUrl = e.target.result;
+ setLocalAvatar(imageDataUrl);
+ setAvatarUrl(imageDataUrl);
+ };
+ reader.readAsDataURL(file);
+ } finally {
+ setIsUploading(false);
+ event.target.value = '';
+ }
+ };
+
+
+ const uploadAvatarToMockAPI = async (file) => {
+ const formData = new FormData();
+ formData.append("avatar", file);
+
+ const response = await fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
+ method: "POST",
+ body: formData
+ });
+
+ if (!response.ok) {
+
+ return null;
+ }
+
+ return await response.json();
+ };
+
+
+ const clearAvatar = () => {
+
+ fetch(`${MOCK_API_BASE_URL}/storage/v1/object/avatars/[path]`, {
+ method: "DELETE"
+ }).catch(() => {
+
+ });
+
+
+ clearLocalAvatar();
+ setAvatarUrl(null);
+ };
+
return (
@@ -47,54 +195,108 @@ const ProfilePage = () => {
-
-
+ {avatarUrl ? (
+ {
+ setAvatarUrl(null);
+ clearLocalAvatar();
+ }}
+ />
+ ) : (
+
+ {userName.split(' ').map(n => n[0]).join('').toUpperCase()}
+
+ )}
+
+
+
- ✏️
-
+ {isUploading ? 'Enviando...' : 'Alterar Foto'}
+
+
+
+ {isUploading && (
+
+ Processando imagem...
+
+ )}
{isEditingName ? (
-
setUserName(e.target.value)}
- onBlur={() => setIsEditingName(false)}
- onKeyDown={handleNameKeyDown}
- autoFocus
- />
+
+
setUserName(e.target.value)}
+ onBlur={handleNameSave}
+ onKeyDown={handleNameKeyDown}
+ autoFocus
+ maxLength={50}
+ />
+
+ Pressione Enter para salvar, ESC para cancelar
+
+
) : (
-
{userName}
+
+ {userName}
+
)}
setIsEditingName(!isEditingName)}
- aria-label="Editar nome"
type="button"
+ aria-label={isEditingName ? 'Cancelar edição' : 'Editar nome'}
>
- ✏️
+ {isEditingName ? 'Cancelar' : 'Editar'}
-
- Email: {userEmail}
-
+ {error && (
+
+ {error}
+
+ )}
-
- Cargo: {userRole}
-
+
+
+ Email:
+ {userEmail}
+
-
-
- Fechar
+
+ Cargo:
+ {userRole}
+
+
+
+
+ {avatarUrl && (
+
+ Remover Avatar
+
+ )}
+
+ Fechar Perfil
@@ -104,4 +306,4 @@ const ProfilePage = () => {
);
};
-export default ProfilePage;
+export default ProfilePage;
\ No newline at end of file
diff --git a/src/pages/TablePaciente.jsx b/src/pages/TablePaciente.jsx
index bdcbff0..2ae27a7 100644
--- a/src/pages/TablePaciente.jsx
+++ b/src/pages/TablePaciente.jsx
@@ -3,10 +3,10 @@ import { Link } from "react-router-dom";
import API_KEY from "../components/utils/apiKeys";
import { useAuth } from "../components/utils/AuthProvider";
import "./style/TablePaciente.css";
-import ModalErro from "../components/utils/fetchErros/ModalErro";
+
function TablePaciente({ setCurrentPage, setPatientID }) {
- const { getAuthorizationHeader, isAuthenticated, RefreshingToken } = useAuth();
+ const { getAuthorizationHeader, isAuthenticated } = useAuth();
const [pacientes, setPacientes] = useState([]);
const [search, setSearch] = useState("");
@@ -21,13 +21,13 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
const [dataInicial, setDataInicial] = useState("");
const [dataFinal, setDataFinal] = useState("");
+
+ const [paginaAtual, setPaginaAtual] = useState(1);
+ const [itensPorPagina, setItensPorPagina] = useState(10);
+
const [showDeleteModal, setShowDeleteModal] = useState(false);
const [selectedPatientId, setSelectedPatientId] = useState(null);
- const [showModalError, setShowModalError] = useState(false);
-
- const [ ErrorInfo, setErrorInfo] = useState({})
-
const GetAnexos = async (id) => {
var myHeaders = new Headers();
myHeaders.append("Authorization", "Bearer
");
@@ -104,6 +104,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
};
useEffect(() => {
+
const authHeader = getAuthorizationHeader()
console.log(authHeader, 'aqui autorização')
@@ -117,47 +118,10 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
redirect: 'follow'
};
- fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
- .then(response => {
-
- // 1. VERIFICAÇÃO DO STATUS HTTP (Se não for 2xx)
- if (!response.ok) {
-
- return response.json().then(errorData => {
-
- const errorObject = {
- httpStatus: response.status,
- apiCode: errorData.code,
- message: errorData.message || response.statusText,
- details: errorData.details,
- hint: errorData.hint
- };
-
- console.error("ERRO DETALHADO:", errorObject);
- throw errorObject;
- });
- }
-
- // 3. Se a resposta for OK (2xx), processamos o JSON normalmente
- return response.json();
- })
- .then(result => {
- // 4. Bloco de SUCESSO
- setPacientes(result);
- console.log("Sucesso:", result);
-
- // IMPORTANTE: Se o modal estava aberto, feche-o no sucesso
- setShowModalError(false);
- })
- .catch(error => {
- // 5. Bloco de ERRO (Captura erros de rede ou o erro lançado pelo 'throw')
- //console.error('Falha na requisição:', error.message);
- if(error.httpStatus === 401){
- RefreshingToken()
- }
- setErrorInfo(error)
- setShowModalError(true);
- });
+ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions)
+ .then(response => response.json())
+ .then(result => setPacientes(result))
+ .catch(error => console.log('error', error));
}, [isAuthenticated, getAuthorizationHeader]);
const ehAniversariante = (dataNascimento) => {
@@ -195,6 +159,7 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
setIdadeMaxima("");
setDataInicial("");
setDataFinal("");
+ setPaginaAtual(1);
};
const pacientesFiltrados = Array.isArray(pacientes) ? pacientes.filter((paciente) => {
@@ -238,13 +203,53 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
return resultado;
}) : [];
+
+ const totalPaginas = Math.ceil(pacientesFiltrados.length / itensPorPagina);
+ const indiceInicial = (paginaAtual - 1) * itensPorPagina;
+ const indiceFinal = indiceInicial + itensPorPagina;
+ const pacientesPaginados = pacientesFiltrados.slice(indiceInicial, indiceFinal);
+
+
+ const irParaPagina = (pagina) => {
+ setPaginaAtual(pagina);
+ };
+
+ const avancarPagina = () => {
+ if (paginaAtual < totalPaginas) {
+ setPaginaAtual(paginaAtual + 1);
+ }
+ };
+
+ const voltarPagina = () => {
+ if (paginaAtual > 1) {
+ setPaginaAtual(paginaAtual - 1);
+ }
+ };
+
+
+ const gerarNumerosPaginas = () => {
+ const paginas = [];
+ const paginasParaMostrar = 5;
+
+ let inicio = Math.max(1, paginaAtual - Math.floor(paginasParaMostrar / 2));
+ let fim = Math.min(totalPaginas, inicio + paginasParaMostrar - 1);
+
+ inicio = Math.max(1, fim - paginasParaMostrar + 1);
+
+ for (let i = inicio; i <= fim; i++) {
+ paginas.push(i);
+ }
+
+ return paginas;
+ };
+
+
useEffect(() => {
- console.log(` Pacientes totais: ${pacientes?.length}, Filtrados: ${pacientesFiltrados?.length}`);
- }, [pacientes, pacientesFiltrados, search]);
+ setPaginaAtual(1);
+ }, [search, filtroConvenio, filtroVIP, filtroAniversariante, filtroCidade, filtroEstado, idadeMinima, idadeMaxima, dataInicial, dataFinal]);
return (
<>
-
Lista de Pacientes
@@ -295,7 +300,8 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
setFiltroVIP(!filtroVIP)}
style={{ padding: "0.25rem 0.5rem" }}
>
VIP
@@ -400,6 +406,12 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
)}
+
+
+
+ {pacientesFiltrados.length} DE {pacientes.length} PACIENTES ENCONTRADOS
+
+
{(search || filtroConvenio !== "Todos" || filtroVIP || filtroAniversariante ||
@@ -421,12 +433,6 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
)}
-
-
- {pacientesFiltrados?.length} de {pacientes?.length} pacientes encontrados
-
-
-
@@ -439,8 +445,8 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
- {pacientesFiltrados.length > 0 ? (
- pacientesFiltrados.map((paciente) => (
+ {pacientesPaginados.length > 0 ? (
+ pacientesPaginados.map((paciente) => (
@@ -495,13 +501,75 @@ function TablePaciente({ setCurrentPage, setPatientID }) {
))
) : (
-
- Nenhum paciente encontrado.
+
+
+
+
Nenhum paciente encontrado com os filtros aplicados.
+ {(search || filtroConvenio !== "Todos" || filtroVIP || filtroAniversariante ||
+ filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && (
+
+ Limpar filtros
+
+ )}
+
)}
+
+ {/* Paginação */}
+ {pacientesFiltrados.length > 0 && (
+
+
+ Itens por página:
+ {
+ setItensPorPagina(Number(e.target.value));
+ setPaginaAtual(1);
+ }}
+ >
+ 5
+ 10
+ 25
+ 50
+
+
+
+
+
+ Página {paginaAtual} de {totalPaginas} •
+ Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, pacientesFiltrados.length)} de {pacientesFiltrados.length} pacientes
+
+
+
+
+
+
+
+
+
+
+ {gerarNumerosPaginas().map(pagina => (
+
+ irParaPagina(pagina)}>
+ {pagina}
+
+
+ ))}
+
+
+
+
+
+
+
+
+
+
+ )}
diff --git a/src/pages/style/ProfilePage.css b/src/pages/style/ProfilePage.css
index c9215aa..b64cd17 100644
--- a/src/pages/style/ProfilePage.css
+++ b/src/pages/style/ProfilePage.css
@@ -1,6 +1,6 @@
/* src/pages/ProfilePage.css */
-/* Overlay que cobre toda a tela */
+/* Overlay */
.profile-overlay {
position: fixed;
inset: 0;
@@ -8,171 +8,318 @@
display: flex;
align-items: center;
justify-content: center;
- z-index: 20000; /* acima de header, vlibras, botões de acessibilidade */
+ z-index: 20000;
padding: 20px;
- box-sizing: border-box;
}
-/* Card central (estilo modal amplo parecido com a 4ª foto) */
+/* Modal */
.profile-modal {
background: #ffffff;
- border-radius: 10px;
- padding: 18px;
- width: min(1100px, 96%);
- max-width: 1100px;
+ border-radius: 12px;
+ padding: 20px;
+ width: min(600px, 96%);
+ max-width: 600px;
box-shadow: 0 18px 60px rgba(0, 0, 0, 0.5);
position: relative;
- box-sizing: border-box;
- overflow: visible;
}
-/* Botão fechar (X) no canto do card */
+/* Botão fechar */
.profile-close {
position: absolute;
- top: 14px;
- right: 14px;
+ top: 15px;
+ right: 15px;
background: none;
border: none;
- font-size: 26px;
+ font-size: 24px;
color: #666;
cursor: pointer;
- line-height: 1;
+ padding: 5px;
}
-/* Conteúdo dividido em 2 colunas: esquerda avatar / direita infos */
+.profile-close:hover {
+ color: #333;
+}
+
+/* Layout */
.profile-content {
display: flex;
- gap: 28px;
+ gap: 30px;
align-items: flex-start;
- padding: 22px 18px;
+ padding: 20px 10px;
}
-/* Coluna esquerda - avatar */
+/* Avatar */
.profile-left {
- width: 220px;
+ width: 160px;
display: flex;
justify-content: center;
}
-/* Avatar quadrado com sombra (estilo da foto 4) */
.avatar-wrapper {
position: relative;
- width: 180px;
- height: 180px;
+ width: 140px;
+ height: 140px;
}
.avatar-square {
width: 100%;
height: 100%;
border-radius: 8px;
- background-color: #d0d0d0;
- background-image: url("data:image/svg+xml;utf8, ");
- background-position: center;
- background-repeat: no-repeat;
- background-size: 55%;
- box-shadow: 0 8px 24px rgba(0,0,0,0.25);
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+ overflow: hidden;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-shadow: 0 4px 12px rgba(0,0,0,0.15);
+}
+
+.avatar-img {
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 8px;
+}
+
+.avatar-placeholder {
+ font-size: 2rem;
+ font-weight: bold;
+ color: white;
}
-/* Botão editar sobre o avatar — círculo pequeno */
.avatar-edit-btn {
position: absolute;
right: -8px;
bottom: -8px;
- transform: translate(0, 0);
border: none;
background: #ffffff;
- padding: 8px 9px;
+ padding: 8px;
border-radius: 50%;
- box-shadow: 0 6px 14px rgba(0,0,0,0.18);
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15);
cursor: pointer;
- font-size: 0.95rem;
- line-height: 1;
+ font-size: 0.9rem;
+ transition: all 0.2s ease;
}
-/* Coluna direita - informações */
+.avatar-edit-btn:hover {
+ background: #f0f0f0;
+ transform: scale(1.1);
+}
+
+.avatar-edit-btn.uploading {
+ background: #ffd700;
+}
+
+.upload-status {
+ position: absolute;
+ bottom: -25px;
+ left: 0;
+ right: 0;
+ text-align: center;
+ font-size: 0.8rem;
+ color: #666;
+}
+
+/* Informações */
.profile-right {
flex: 1;
- min-width: 280px;
- display: flex;
- flex-direction: column;
- justify-content: center;
+ min-width: 250px;
}
-/* Nome e botão de editar inline */
.profile-name-row {
display: flex;
align-items: center;
gap: 10px;
- margin-bottom: 10px;
+ margin-bottom: 15px;
}
.profile-username {
margin: 0;
- font-size: 1.9rem;
+ font-size: 1.8rem;
color: #222;
+ font-weight: 600;
}
.profile-edit-inline {
background: none;
border: none;
cursor: pointer;
- font-size: 1.05rem;
+ font-size: 1rem;
color: #444;
+ padding: 5px;
+ border-radius: 4px;
+}
+
+.profile-edit-inline:hover {
+ background: #f5f5f5;
+}
+
+/* Edição de nome */
+.name-edit-wrapper {
+ width: 100%;
}
-/* input de edição do nome */
.profile-name-input {
font-size: 1.6rem;
- padding: 6px 8px;
- border: 1px solid #e0e0e0;
+ padding: 5px 8px;
+ border: 2px solid #007bff;
border-radius: 6px;
+ width: 100%;
+ font-weight: 600;
+ color: #222;
+}
+
+.profile-name-input:focus {
+ outline: none;
+ border-color: #0056b3;
+}
+
+.name-edit-hint {
+ font-size: 0.75rem;
+ color: #666;
+ margin-top: 5px;
+}
+
+/* Informações do perfil */
+.profile-info {
+ margin: 20px 0;
}
-/* email/role */
.profile-email,
.profile-role {
- margin: 6px 0;
+ margin: 8px 0;
color: #555;
font-size: 1rem;
-}
-
-.profile-role {
- margin-top: 14px;
- padding-top: 12px;
- border-top: 1px solid #f1f1f1;
- color: #333;
-}
-
-/* ações (apenas fechar aqui) */
-.profile-actions-row {
display: flex;
- gap: 12px;
- margin-top: 18px;
+ gap: 8px;
+}
+
+.profile-email span,
+.profile-role span {
+ color: #777;
+ min-width: 50px;
+}
+
+/* Mensagem de erro */
+.error-message {
+ background-color: #fee;
+ color: #c33;
+ padding: 10px;
+ border-radius: 6px;
+ border: 1px solid #fcc;
+ margin: 15px 0;
+ font-size: 0.9rem;
+}
+
+/* Ações */
+.profile-actions {
+ display: flex;
+ gap: 10px;
+ margin-top: 20px;
}
-/* botões */
.btn {
- padding: 8px 14px;
- border-radius: 8px;
- border: 1px solid transparent;
+ padding: 10px 20px;
+ border-radius: 6px;
+ border: none;
cursor: pointer;
- font-size: 0.95rem;
+ font-size: 0.9rem;
+ font-weight: 500;
+ transition: all 0.2s ease;
}
.btn-close {
background: #f0f0f0;
color: #222;
- border: 1px solid #e6e6e6;
}
-/* responsividade */
-@media (max-width: 880px) {
+.btn-close:hover {
+ background: #e0e0e0;
+}
+
+.btn-clear {
+ background: #dc3545;
+ color: white;
+}
+
+.btn-clear:hover {
+ background: #c82333;
+}
+
+/* Responsividade */
+@media (max-width: 680px) {
.profile-content {
flex-direction: column;
- gap: 14px;
+ gap: 20px;
align-items: center;
+ text-align: center;
+ }
+
+ .profile-left {
+ width: 100%;
+ }
+
+ .avatar-wrapper {
+ margin: 0 auto;
+ }
+
+ .profile-email,
+ .profile-role {
+ justify-content: center;
+ }
+
+ .profile-actions {
+ justify-content: center;
}
- .profile-left { width: 100%; }
- .avatar-wrapper { width: 140px; height: 140px; }
- .profile-right { width: 100%; text-align: center; }
}
+
+@media (max-width: 480px) {
+ .profile-modal {
+ padding: 15px;
+ }
+
+ .profile-content {
+ padding: 10px 5px;
+ }
+
+ .profile-username {
+ font-size: 1.5rem;
+ }
+
+ .avatar-wrapper {
+ width: 120px;
+ height: 120px;
+ }
+}.avatar-edit-btn {
+ background: #007bff;
+ color: white;
+ border: none;
+ padding: 8px 16px;
+ border-radius: 6px;
+ cursor: pointer;
+ font-size: 14px;
+ margin-top: 10px;
+ transition: background-color 0.2s;
+}
+
+.avatar-edit-btn:hover {
+ background: #0056b3;
+}
+
+.avatar-edit-btn.uploading {
+ background: #6c757d;
+ cursor: not-allowed;
+}
+
+.profile-edit-inline {
+ background: #f8f9fa;
+ border: 1px solid #dee2e6;
+ padding: 6px 12px;
+ border-radius: 4px;
+ cursor: pointer;
+ font-size: 13px;
+ margin-left: 10px;
+}
+
+.profile-edit-inline:hover {
+ background: #e9ecef;
+}
\ No newline at end of file
diff --git a/src/pages/style/TableDoctor.css b/src/pages/style/TableDoctor.css
index d5b6233..6d6d277 100644
--- a/src/pages/style/TableDoctor.css
+++ b/src/pages/style/TableDoctor.css
@@ -1,4 +1,3 @@
-
.table-doctor-container {
line-height: 2.5;
}
@@ -58,8 +57,6 @@
font-weight: 500;
}
-
-
.results-badge {
background-color: #1e3a8a;
color: white;
@@ -75,7 +72,6 @@
font-size: 0.75em;
}
-
.btn-view {
background-color: #E6F2FF !important;
color: #004085 !important;
@@ -115,7 +111,6 @@
border-color: #ED969E;
}
-
.advanced-filters {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
@@ -132,7 +127,6 @@
font-size: 0.875rem;
}
-
.delete-modal .modal-header {
background-color: rgba(220, 53, 69, 0.1);
border-bottom: 1px solid rgba(220, 53, 69, 0.2);
@@ -143,7 +137,6 @@
font-weight: 600;
}
-
.filter-especialidade {
min-width: 180px !important;
max-width: 200px;
@@ -160,7 +153,6 @@
padding: 0.375rem 0.75rem;
}
-
.filtros-basicos {
display: flex;
flex-wrap: wrap;
@@ -168,7 +160,6 @@
gap: 0.75rem;
}
-
@media (max-width: 768px) {
.table-doctor-table {
font-size: 0.875rem;
@@ -207,7 +198,6 @@
}
}
-
.empty-state {
padding: 2rem;
text-align: center;
@@ -224,7 +214,6 @@
padding: 0.4em 0.65em;
}
-
.table-doctor-table tbody tr {
transition: background-color 0.15s ease-in-out;
}
@@ -233,4 +222,117 @@
.btn-edit,
.btn-delete {
transition: all 0.15s ease-in-out;
+}
+
+/* ===== ESTILOS PARA PAGINAÇÃO ===== */
+
+.contador-medicos {
+ background-color: #1e3a8a;
+ color: white;
+ padding: 0.5em 0.75em;
+ font-size: 0.875em;
+ font-weight: 500;
+ border-radius: 0.375rem;
+ text-align: center;
+ display: inline-block;
+}
+
+/* Estilos para a paginação */
+.pagination {
+ margin-bottom: 0;
+}
+
+.page-link {
+ color: #495057;
+ border: 1px solid #dee2e6;
+ padding: 0.375rem 0.75rem;
+ font-size: 0.875rem;
+}
+
+.page-link:hover {
+ color: #1e3a8a;
+ background-color: #e9ecef;
+ border-color: #dee2e6;
+}
+
+.page-item.active .page-link {
+ background-color: #1e3a8a;
+ border-color: #1e3a8a;
+ color: white;
+}
+
+.page-item.disabled .page-link {
+ color: #6c757d;
+ background-color: #f8f9fa;
+ border-color: #dee2e6;
+}
+
+/* Ajustes para a seção de paginação */
+.d-flex.justify-content-between.align-items-center {
+ border-top: 1px solid #dee2e6;
+ padding-top: 1rem;
+ margin-top: 1rem;
+}
+
+/* Estilos para empty state */
+.text-center.py-4 .text-muted {
+ padding: 2rem;
+}
+
+.text-center.py-4 .bi-search {
+ font-size: 3rem;
+ opacity: 0.5;
+}
+
+.text-center.py-4 p {
+ margin-bottom: 0.5rem;
+ font-size: 1.1rem;
+}
+
+.text-center.py-4 td {
+ border-bottom: none;
+ padding: 2rem !important;
+}
+
+/* Responsividade para paginação */
+@media (max-width: 768px) {
+ .d-flex.justify-content-between.align-items-center {
+ flex-direction: column;
+ gap: 1rem;
+ align-items: stretch !important;
+ }
+
+ .d-flex.justify-content-between.align-items-center > div {
+ justify-content: center !important;
+ }
+
+ .pagination {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .me-3.text-muted {
+ text-align: center;
+ margin-bottom: 0.5rem;
+ font-size: 0.8rem;
+ }
+
+ .contador-medicos {
+ font-size: 0.8rem;
+ padding: 0.4em 0.6em;
+ }
+}
+
+/* Ajuste para o select de itens por página */
+.form-select.form-select-sm.w-auto {
+ border: 1px solid #dee2e6;
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+}
+
+/* Melhorar a aparência dos badges de filtros ativos */
+.filters-active .badge {
+ font-size: 0.75em;
+ padding: 0.4em 0.65em;
+ margin-bottom: 0.25rem;
}
\ No newline at end of file
diff --git a/src/pages/style/TablePaciente.css b/src/pages/style/TablePaciente.css
index 71184e7..8e9a699 100644
--- a/src/pages/style/TablePaciente.css
+++ b/src/pages/style/TablePaciente.css
@@ -1,4 +1,3 @@
-
.table-paciente-container {
line-height: 2.5;
}
@@ -49,7 +48,6 @@
background-color: rgba(0, 0, 0, 0.025);
}
-
.insurance-badge {
background-color: #6c757d !important;
color: white !important;
@@ -81,7 +79,6 @@
font-size: 0.75em;
}
-
.btn-view {
background-color: #E6F2FF !important;
color: #004085 !important;
@@ -121,7 +118,6 @@
border-color: #ED969E;
}
-
.advanced-filters {
border: 1px solid #dee2e6;
border-radius: 0.375rem;
@@ -148,7 +144,6 @@
font-weight: 600;
}
-
.empty-state {
padding: 2rem;
text-align: center;
@@ -165,7 +160,6 @@
padding: 0.4em 0.65em;
}
-
.table-paciente-table tbody tr {
transition: background-color 0.15s ease-in-out;
}
@@ -176,7 +170,6 @@
transition: all 0.15s ease-in-out;
}
-
@media (max-width: 768px) {
.table-paciente-table {
font-size: 0.875rem;
@@ -213,6 +206,7 @@
margin-left: 0 !important;
}
}
+
.compact-select {
font-size: 1.0rem;
padding: 0.45rem 0.5rem;
@@ -227,8 +221,120 @@
white-space: nowrap;
}
-
.table-paciente-filters .d-flex {
align-items: center;
gap: 8px;
+}
+
+/* ===== ESTILOS PARA PAGINAÇÃO ===== */
+
+.contador-pacientes {
+ background-color: #1e3a8a;
+ color: white;
+ padding: 0.5em 0.75em;
+ font-size: 0.875em;
+ font-weight: 500;
+ border-radius: 0.375rem;
+ text-align: center;
+ display: inline-block;
+}
+
+/* Estilos para a paginação */
+.pagination {
+ margin-bottom: 0;
+}
+
+.page-link {
+ color: #495057;
+ border: 1px solid #dee2e6;
+ padding: 0.375rem 0.75rem;
+ font-size: 0.875rem;
+}
+
+.page-link:hover {
+ color: #1e3a8a;
+ background-color: #e9ecef;
+ border-color: #dee2e6;
+}
+
+.page-item.active .page-link {
+ background-color: #1e3a8a;
+ border-color: #1e3a8a;
+ color: white;
+}
+
+.page-item.disabled .page-link {
+ color: #6c757d;
+ background-color: #f8f9fa;
+ border-color: #dee2e6;
+}
+
+/* Ajustes para a seção de paginação */
+.d-flex.justify-content-between.align-items-center {
+ border-top: 1px solid #dee2e6;
+ padding-top: 1rem;
+ margin-top: 1rem;
+}
+
+/* Estilos para empty state */
+.text-center.py-4 .text-muted {
+ padding: 2rem;
+}
+
+.text-center.py-4 .bi-search {
+ font-size: 3rem;
+ opacity: 0.5;
+}
+
+.text-center.py-4 p {
+ margin-bottom: 0.5rem;
+ font-size: 1.1rem;
+}
+
+.text-center.py-4 td {
+ border-bottom: none;
+ padding: 2rem !important;
+}
+
+/* Responsividade para paginação */
+@media (max-width: 768px) {
+ .d-flex.justify-content-between.align-items-center {
+ flex-direction: column;
+ gap: 1rem;
+ align-items: stretch !important;
+ }
+
+ .d-flex.justify-content-between.align-items-center > div {
+ justify-content: center !important;
+ }
+
+ .pagination {
+ flex-wrap: wrap;
+ justify-content: center;
+ }
+
+ .me-3.text-muted {
+ text-align: center;
+ margin-bottom: 0.5rem;
+ font-size: 0.8rem;
+ }
+
+ .contador-pacientes {
+ font-size: 0.8rem;
+ padding: 0.4em 0.6em;
+ }
+}
+
+/* Ajuste para o select de itens por página */
+.form-select.form-select-sm.w-auto {
+ border: 1px solid #dee2e6;
+ border-radius: 0.375rem;
+ font-size: 0.875rem;
+}
+
+/* Melhorar a aparência dos badges de filtros ativos */
+.filters-active .badge {
+ font-size: 0.75em;
+ padding: 0.4em 0.65em;
+ margin-bottom: 0.25rem;
}
\ No newline at end of file