riseup-squad23/src/PagesMedico/DoctorRelatorioManager.jsx

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;