463 lines
19 KiB
JavaScript
463 lines
19 KiB
JavaScript
// 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 { UserInfos } from '../components/utils/Functions-Endpoints/General';
|
|
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 [relatoriosOriginais, setRelatoriosOriginais] = useState([]);
|
|
const [relatoriosFiltrados, setRelatoriosFiltrados] = useState([]);
|
|
const [relatoriosFinais, setRelatoriosFinais] = useState([]);
|
|
const [pacientesComRelatorios, setPacientesComRelatorios] = useState([]);
|
|
const [medicosComRelatorios, setMedicosComRelatorios] = useState([]);
|
|
const [showModal, setShowModal] = useState(false);
|
|
const [relatorioModal, setRelatorioModal] = useState(null);
|
|
const [termoPesquisa, setTermoPesquisa] = useState('');
|
|
const [filtroExame, setFiltroExame] = useState('');
|
|
const [examesDisponiveis, setExamesDisponiveis] = useState([]);
|
|
const [modalIndex, setModalIndex] = useState(0);
|
|
|
|
const [paginaAtual, setPaginaAtual] = useState(1);
|
|
const [itensPorPagina, setItensPorPagina] = useState(10);
|
|
|
|
const totalPaginas = Math.max(1, Math.ceil(relatoriosFinais.length / itensPorPagina));
|
|
const indiceInicial = (paginaAtual - 1) * itensPorPagina;
|
|
const indiceFinal = indiceInicial + itensPorPagina;
|
|
const relatoriosPaginados = relatoriosFinais.slice(indiceInicial, indiceFinal);
|
|
|
|
useEffect(() => {
|
|
let mounted = true;
|
|
|
|
const fetchReports = async () => {
|
|
try {
|
|
const myHeaders = new Headers();
|
|
myHeaders.append('apikey', API_KEY);
|
|
if (authHeader) myHeaders.append('Authorization', authHeader);
|
|
const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' };
|
|
|
|
// Tenta descobrir o ID do usuário logado para aplicar filtro por médico
|
|
let userId = null;
|
|
let userFullName = null;
|
|
try {
|
|
const token = authHeader ? authHeader.replace(/^Bearer\s+/i, '') : '';
|
|
if (token) {
|
|
const userInfo = await UserInfos(token);
|
|
userId = userInfo?.id || userInfo?.user?.id || userInfo?.sub || null;
|
|
userFullName = userInfo?.full_name || (userInfo?.user && userInfo.user.full_name) || null;
|
|
}
|
|
} catch (err) {
|
|
console.warn('Não foi possível obter UserInfos (pode não estar logado):', err);
|
|
}
|
|
|
|
// Monta a URL com possíveis filtros preferenciais
|
|
const baseUrl = "https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*";
|
|
let data = [];
|
|
|
|
if (userId) {
|
|
// 1) tenta por doctor_id
|
|
try {
|
|
const res = await fetch(`${baseUrl}&doctor_id=eq.${userId}`, requestOptions);
|
|
data = await res.json();
|
|
} catch (e) {
|
|
console.warn('Erro ao buscar por doctor_id:', e);
|
|
data = [];
|
|
}
|
|
|
|
// 2) fallback para created_by (se vazio)
|
|
if ((!Array.isArray(data) || data.length === 0) && userId) {
|
|
try {
|
|
const res2 = await fetch(`${baseUrl}&created_by=eq.${userId}`, requestOptions);
|
|
data = await res2.json();
|
|
} catch (e) {
|
|
console.warn('Erro ao buscar por created_by:', e);
|
|
data = [];
|
|
}
|
|
}
|
|
|
|
// 3) fallback para requested_by com nome completo (se ainda vazio)
|
|
if ((!Array.isArray(data) || data.length === 0) && userFullName) {
|
|
try {
|
|
// encode para evitar problemas com espaços/caracteres especiais
|
|
const encodedName = encodeURIComponent(userFullName);
|
|
const res3 = await fetch(`${baseUrl}&requested_by=eq.${encodedName}`, requestOptions);
|
|
data = await res3.json();
|
|
} catch (e) {
|
|
console.warn('Erro ao buscar por requested_by:', e);
|
|
data = [];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Se não obteve userId ou nenhuma das tentativas acima retornou algo, busca tudo (comportamento anterior)
|
|
if (!userId || (!Array.isArray(data) || data.length === 0)) {
|
|
try {
|
|
const resAll = await fetch(baseUrl, requestOptions);
|
|
data = await resAll.json();
|
|
} catch (e) {
|
|
console.error('Erro listar relatórios (busca completa):', e);
|
|
data = [];
|
|
}
|
|
}
|
|
|
|
// garante unicidade e ordenação por criação
|
|
const uniqueMap = new Map();
|
|
(Array.isArray(data) ? data : []).forEach(r => {
|
|
if (r && r.id) uniqueMap.set(r.id, r);
|
|
});
|
|
const unique = Array.from(uniqueMap.values())
|
|
.sort((a, b) => new Date(b.created_at || 0) - new Date(a.created_at || 0));
|
|
|
|
if (mounted) {
|
|
setRelatoriosOriginais(unique);
|
|
setRelatoriosFiltrados(unique);
|
|
setRelatoriosFinais(unique);
|
|
}
|
|
} catch (err) {
|
|
console.error('Erro listar relatórios (catch):', err);
|
|
if (mounted) {
|
|
setRelatoriosOriginais([]);
|
|
setRelatoriosFiltrados([]);
|
|
setRelatoriosFinais([]);
|
|
}
|
|
}
|
|
};
|
|
|
|
fetchReports();
|
|
|
|
const refreshHandler = () => fetchReports();
|
|
window.addEventListener('reports:refresh', refreshHandler);
|
|
|
|
return () => {
|
|
mounted = false;
|
|
window.removeEventListener('reports:refresh', refreshHandler);
|
|
};
|
|
}, [authHeader]);
|
|
|
|
// Busca dados de pacientes e médicos baseados na lista final que aparece na tela
|
|
useEffect(() => {
|
|
const fetchRelData = async () => {
|
|
const pacientes = [];
|
|
const medicos = [];
|
|
for (let i = 0; i < relatoriosFinais.length; i++) {
|
|
const rel = relatoriosFinais[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: prioriza campos com id (doctor_id ou created_by). Se tiver somente requested_by (nome), usa nome.
|
|
try {
|
|
if (rel.doctor_id) {
|
|
const docRes = await GetDoctorByID(rel.doctor_id, authHeader);
|
|
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
|
|
} else if (rel.created_by) {
|
|
// created_by costuma ser id
|
|
const docRes = await GetDoctorByID(rel.created_by, authHeader);
|
|
medicos.push(Array.isArray(docRes) ? docRes[0] : docRes);
|
|
} else if (rel.requested_by) {
|
|
medicos.push({ full_name: rel.requested_by });
|
|
} else {
|
|
medicos.push({ full_name: '' });
|
|
}
|
|
} catch (err) {
|
|
// fallback para requested_by se houver
|
|
medicos.push({ full_name: rel.requested_by || '' });
|
|
}
|
|
}
|
|
setPacientesComRelatorios(pacientes);
|
|
setMedicosComRelatorios(medicos);
|
|
};
|
|
|
|
if (relatoriosFinais.length > 0) fetchRelData();
|
|
else {
|
|
setPacientesComRelatorios([]);
|
|
setMedicosComRelatorios([]);
|
|
}
|
|
}, [relatoriosFinais, authHeader]);
|
|
|
|
const abrirModal = (relatorio, pageIndex) => {
|
|
// encontra índice global do relatório no array relatoriosFinais (para alinhar com pacientes/medicos)
|
|
const globalIndex = relatoriosFinais.findIndex(r => r.id === relatorio.id);
|
|
const indexToUse = globalIndex >= 0 ? globalIndex : (indiceInicial + pageIndex);
|
|
setRelatorioModal(relatorio);
|
|
setModalIndex(indexToUse);
|
|
setShowModal(true);
|
|
};
|
|
|
|
// Função para limpar filtros
|
|
const limparFiltros = () => {
|
|
setTermoPesquisa('');
|
|
setFiltroExame('');
|
|
setRelatoriosFinais(relatoriosOriginais);
|
|
setPaginaAtual(1);
|
|
};
|
|
|
|
const BaixarPDFdoRelatorio = (nome_paciente, idx) => {
|
|
const elemento = document.getElementById(`folhaA4-${idx}`);
|
|
if (!elemento) {
|
|
console.error('Elemento para gerar PDF não encontrado:', `folhaA4-${idx}`);
|
|
return;
|
|
}
|
|
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 (
|
|
<div>
|
|
{showModal && (
|
|
<div className="modal modal-centered" role="dialog" aria-modal="true" onClick={() => setShowModal(false)}>
|
|
<div className="modal-dialog modal-dialog-square" role="document" onClick={(e) => e.stopPropagation()}>
|
|
<div className="modal-content">
|
|
<div className="modal-header custom-modal-header">
|
|
<h5 className="modal-title">Relatório de {pacientesComRelatorios[modalIndex]?.full_name}</h5>
|
|
<button type="button" className="btn-close modal-close-btn" aria-label="Close" onClick={() => setShowModal(false)}></button>
|
|
</div>
|
|
|
|
<div className="modal-body">
|
|
<div id={`folhaA4-${modalIndex}`} className="folhaA4">
|
|
<div id='header-relatorio' style={{ textAlign: 'center', marginBottom: 24 }}>
|
|
<p style={{ margin: 0 }}>Clinica Rise up</p>
|
|
<p style={{ margin: 0 }}>Dr - CRM/SP 123456</p>
|
|
<p style={{ margin: 0 }}>Avenida - (79) 9 4444-4444</p>
|
|
</div>
|
|
|
|
<div id='infoPaciente' style={{ padding: '0 6px' }}>
|
|
<p><strong>Paciente:</strong> {pacientesComRelatorios[modalIndex]?.full_name}</p>
|
|
<p><strong>Data de nascimento:</strong> {pacientesComRelatorios[modalIndex]?.birth_date || '—'}</p>
|
|
<p><strong>Data do exame:</strong> {relatoriosFinais[modalIndex]?.due_at || '—'}</p>
|
|
|
|
<p style={{ marginTop: 12, fontWeight: '700' }}>Conteúdo do Relatório:</p>
|
|
<div className="tiptap-viewer-wrapper">
|
|
<TiptapViewer htmlContent={relatoriosFinais[modalIndex]?.content_html || relatoriosFinais[modalIndex]?.content || 'Relatório não preenchido.'} />
|
|
</div>
|
|
</div>
|
|
|
|
<div style={{ marginTop: 20, padding: '0 6px' }}>
|
|
<p>Dr {medicosComRelatorios[modalIndex]?.full_name || relatoriosFinais[modalIndex]?.requested_by}</p>
|
|
<p style={{ color: '#6c757d', fontSize: '0.95rem' }}>Emitido em: {relatoriosFinais[modalIndex]?.created_at || '—'}</p>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="modal-footer custom-modal-footer">
|
|
<button className="btn btn-primary" onClick={() => BaixarPDFdoRelatorio(pacientesComRelatorios[modalIndex]?.full_name, modalIndex)}>
|
|
<i className='bi bi-file-pdf-fill'></i> baixar em pdf
|
|
</button>
|
|
<button type="button" className="btn btn-outline-secondary" onClick={() => { setShowModal(false) }}>
|
|
Fechar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
|
|
<div className="page-heading"><h3>Lista de Relatórios</h3></div>
|
|
<div className="page-content">
|
|
<section className="row">
|
|
<div className="col-12">
|
|
<div className="card">
|
|
<div className="card-header d-flex justify-content-between align-items-center">
|
|
<h4 className="card-title mb-0">Relatórios Cadastrados</h4>
|
|
<Link to={'criar'}>
|
|
<button className="btn btn-primary">
|
|
<i className="bi bi-plus-circle"></i> Adicionar Relatório
|
|
</button>
|
|
</Link>
|
|
</div>
|
|
<div className="card-body">
|
|
<div className="card p-3 mb-3">
|
|
<h5 className="mb-3">
|
|
<i className="bi bi-funnel-fill me-2 text-primary"></i> Filtros
|
|
</h5>
|
|
<div className="row">
|
|
<div className="col-md-5">
|
|
<div className="mb-3">
|
|
<label className="form-label">Buscar por nome ou CPF do paciente</label>
|
|
<input
|
|
type="text"
|
|
className="form-control"
|
|
placeholder="Digite nome ou CPF do paciente..."
|
|
value={termoPesquisa}
|
|
onChange={(e) => setTermoPesquisa(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-5">
|
|
<div className="mb-3">
|
|
<label className="form-label">Filtrar por tipo de exame</label>
|
|
<input
|
|
type="text"
|
|
className="form-control"
|
|
placeholder="Digite o tipo de exame..."
|
|
value={filtroExame}
|
|
onChange={(e) => setFiltroExame(e.target.value)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
<div className="col-md-2 d-flex align-items-end">
|
|
<button className="btn btn-outline-secondary w-100" onClick={limparFiltros}>
|
|
<i className="bi bi-arrow-clockwise"></i> Limpar
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<div className="mt-2">
|
|
<div className="contador-relatorios">
|
|
{relatoriosFinais.length} DE {relatoriosOriginais.length} RELATÓRIOS ENCONTRADOS
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="table-responsive">
|
|
<table className="table table-striped table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>Paciente</th>
|
|
<th>CPF</th>
|
|
<th>Exame</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{relatoriosPaginados.length > 0 ? (
|
|
relatoriosPaginados.map((relatorio, index) => {
|
|
const globalIndex = relatoriosFinais.findIndex(r => r.id === relatorio.id);
|
|
const paciente = pacientesComRelatorios[globalIndex];
|
|
return (
|
|
<tr key={relatorio.id}>
|
|
<td>{paciente?.full_name || 'Carregando...'}</td>
|
|
<td>{paciente?.cpf || 'Carregando...'}</td>
|
|
<td>{relatorio.exam}</td>
|
|
<td>
|
|
<div className="d-flex gap-2">
|
|
<button className="btn btn-sm btn-ver-detalhes" onClick={() => abrirModal(relatorio, index)}>
|
|
<i className="bi bi-eye me-1"></i> Ver Detalhes
|
|
</button>
|
|
<button className="btn btn-sm btn-editar" onClick={() => navigate(`/medico/relatorios/${relatorio.id}/edit`)}>
|
|
<i className="bi bi-pencil me-1"></i> Editar
|
|
</button>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
);
|
|
})
|
|
) : (
|
|
<tr><td colSpan="4" className="text-center">Nenhum relatório encontrado.</td></tr>
|
|
)}
|
|
</tbody>
|
|
</table>
|
|
|
|
{relatoriosFinais.length > 0 && (
|
|
<div className="d-flex justify-content-between align-items-center mt-3">
|
|
<div className="d-flex align-items-center">
|
|
<span className="me-2 text-muted">Itens por página:</span>
|
|
<select
|
|
className="form-select form-select-sm w-auto"
|
|
value={itensPorPagina}
|
|
onChange={(e) => {
|
|
setItensPorPagina(Number(e.target.value));
|
|
setPaginaAtual(1);
|
|
}}
|
|
>
|
|
<option value={5}>5</option>
|
|
<option value={10}>10</option>
|
|
<option value={25}>25</option>
|
|
<option value={50}>50</option>
|
|
</select>
|
|
</div>
|
|
|
|
<div className="d-flex align-items-center">
|
|
<span className="me-3 text-muted">
|
|
Página {paginaAtual} de {totalPaginas} •
|
|
Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, relatoriosFinais.length)} de {relatoriosFinais.length} itens
|
|
</span>
|
|
|
|
<nav>
|
|
<ul className="pagination pagination-sm mb-0">
|
|
<li className={`page-item ${paginaAtual === 1 ? 'disabled' : ''}`}>
|
|
<button className="page-link" onClick={voltarPagina}>
|
|
<i className="bi bi-chevron-left"></i>
|
|
</button>
|
|
</li>
|
|
|
|
{gerarNumerosPaginas().map(pagina => (
|
|
<li key={pagina} className={`page-item ${pagina === paginaAtual ? 'active' : ''}`}>
|
|
<button className="page-link" onClick={() => irParaPagina(pagina)}>
|
|
{pagina}
|
|
</button>
|
|
</li>
|
|
))}
|
|
|
|
<li className={`page-item ${paginaAtual === totalPaginas ? 'disabled' : ''}`}>
|
|
<button className="page-link" onClick={avancarPagina}>
|
|
<i className="bi bi-chevron-right"></i>
|
|
</button>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default DoctorRelatorioManager;
|