From e4bf35eddd856b650f7043cd029f03d87190017f Mon Sep 17 00:00:00 2001 From: Caio Miguel Lima Nunes Date: Fri, 24 Oct 2025 11:53:36 -0300 Subject: [PATCH] atualizacoes --- src/PagesMedico/DoctorRelatorioManager.jsx | 377 +++++++++++++----- .../styleMedico/DoctorRelatorioManager.css | 31 ++ src/components/Header/Header.css | 287 ++++++++----- src/components/Header/Header.jsx | 66 ++- src/components/Sidebar.jsx | 10 - src/components/doctors/DoctorForm.jsx | 2 +- src/components/patients/PatientForm.jsx | 3 +- .../utils/Functions-Endpoints/Doctor.js | 64 +-- src/pages/DisponibilidadesDoctorPage.jsx | 4 +- src/pages/DoctorTable.jsx | 158 ++++++-- src/pages/ProfilePage.jsx | 292 +++++++++++--- src/pages/TablePaciente.jsx | 190 ++++++--- src/pages/style/ProfilePage.css | 295 ++++++++++---- src/pages/style/TableDoctor.css | 124 +++++- src/pages/style/TablePaciente.css | 122 +++++- 15 files changed, 1515 insertions(+), 510 deletions(-) create mode 100644 src/PagesMedico/styleMedico/DoctorRelatorioManager.css 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}
@@ -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

- - + +
)} -

Lista de Relatórios

+
+

Lista de Relatórios

+
@@ -133,14 +203,51 @@ const DoctorRelatorioManager = () => {

Relatórios Cadastrados

- +
-
Filtros
-
- +
+ Filtros +
+
+
+
+ + setTermoPesquisa(e.target.value)} + /> +
+
+
+
+ + setFiltroExame(e.target.value)} + /> +
+
+
+ +
+
+
+
+ {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 || '-'} - -
- - - -
- - - )) + {relatoriosPaginados.length > 0 ? ( + relatoriosPaginados.map((relatorio) => { + const paciente = pacientesData[relatorio.patient_id]; + return ( + + {paciente?.full_name || 'Carregando...'} + {paciente?.cpf || 'Carregando...'} + {relatorio.exam} + +
+ + +
+ + + ); + }) ) : ( - Nenhum paciente encontrado. + + +
+ +

Nenhum relatório encontrado com os filtros aplicados.

+ {(termoPesquisa || filtroExame) && ( + + )} +
+ + )} -
+ + {relatoriosFinais.length > 0 && ( +
+
+ Itens por página: + +
+ +
+ + Página {paginaAtual} de {totalPaginas} • + Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, relatoriosFinais.length)} de {relatoriosFinais.length} itens + + + +
+
+ )} +
@@ -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
- +
Telefone
(11) 3333-4444
- +
Chat Online
@@ -206,7 +230,7 @@ const Header = () => {

Chat de Suporte

- +
{mensagens.map((msg) => (
@@ -215,7 +239,7 @@ const Header = () => {
))}
- +
{
-
+ {avatarUrl ? ( + Foto do perfil + ) : ( +
+
👤
+
+ )}
{isDropdownOpen && (
- - + +
)}
diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index e05dbeb..e64a80f 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -260,16 +260,6 @@ function Sidebar({ menuItems }) { })} {/* Logout */} -
  • - -
  • diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index e7e7bea..68b625d 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -734,7 +734,7 @@ function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) {
    {/* BOTÕES DE AÇÃO */} -
    +
    @@ -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
    -
    - {/* Data de Cadastro */}
    )} + +
    +
    + {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) => ( @@ -410,13 +436,75 @@ function TableDoctor() { )) ) : ( - )}
    @@ -371,7 +398,6 @@ function TableDoctor() { )} -
    {medico.cpf}
    - Nenhum médico encontrado. + +
    + +

    Nenhum médico encontrado com os filtros aplicados.

    + {(search || filtroEspecialidade !== "Todos" || filtroAniversariante || + filtroCidade || filtroEstado || idadeMinima || idadeMaxima || dataInicial || dataFinal) && ( + + )} +
    + + {/* Paginação */} + {medicosFiltrados.length > 0 && ( +
    +
    + Itens por página: + +
    + +
    + + Página {paginaAtual} de {totalPaginas} • + Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, medicosFiltrados.length)} de {medicosFiltrados.length} médicos + + + +
    +
    + )}
    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 = () => {
    -
    -
    + + + + {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} +

    )}
    -

    - Email: {userEmail} -

    + {error && ( +
    + {error} +
    + )} -

    - Cargo: {userRole} -

    +
    +

    + Email: + {userEmail} +

    -
    -
    + +
    + {avatarUrl && ( + + )} +
    @@ -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 }) {
    )} + +
    +
    + {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) && ( + + )} +
    + + {/* Paginação */} + {pacientesFiltrados.length > 0 && ( +
    +
    + Itens por página: + +
    + +
    + + Página {paginaAtual} de {totalPaginas} • + Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, pacientesFiltrados.length)} de {pacientesFiltrados.length} pacientes + + + +
    +
    + )}
    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