diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx
index 36ac6f0..13add57 100644
--- a/src/PagesMedico/DoctorRelatorioManager.jsx
+++ b/src/PagesMedico/DoctorRelatorioManager.jsx
@@ -1,9 +1,11 @@
+// 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';
@@ -12,12 +14,11 @@ import './styleMedico/DoctorRelatorioManager.css';
const DoctorRelatorioManager = () => {
const navigate = useNavigate();
const { getAuthorizationHeader } = useAuth();
- let authHeader = getAuthorizationHeader();
-
+ const authHeader = getAuthorizationHeader();
+
const [relatoriosOriginais, setRelatoriosOriginais] = useState([]);
const [relatoriosFiltrados, setRelatoriosFiltrados] = useState([]);
const [relatoriosFinais, setRelatoriosFinais] = useState([]);
- const [pacientesData, setPacientesData] = useState({});
const [pacientesComRelatorios, setPacientesComRelatorios] = useState([]);
const [medicosComRelatorios, setMedicosComRelatorios] = useState([]);
const [showModal, setShowModal] = useState(false);
@@ -30,8 +31,7 @@ const DoctorRelatorioManager = () => {
const [paginaAtual, setPaginaAtual] = useState(1);
const [itensPorPagina, setItensPorPagina] = useState(10);
-
- const totalPaginas = Math.ceil(relatoriosFinais.length / itensPorPagina);
+ 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);
@@ -41,14 +41,76 @@ const DoctorRelatorioManager = () => {
const fetchReports = async () => {
try {
- var myHeaders = new Headers();
+ const myHeaders = new Headers();
myHeaders.append('apikey', API_KEY);
if (authHeader) myHeaders.append('Authorization', authHeader);
- var 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/reports?select=*", requestOptions);
- const data = await res.json();
+ // 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);
@@ -62,7 +124,7 @@ const DoctorRelatorioManager = () => {
setRelatoriosFinais(unique);
}
} catch (err) {
- console.error('Erro listar relatórios', err);
+ console.error('Erro listar relatórios (catch):', err);
if (mounted) {
setRelatoriosOriginais([]);
setRelatoriosFiltrados([]);
@@ -82,43 +144,57 @@ const DoctorRelatorioManager = () => {
};
}, [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 < relatoriosFiltrados.length; i++) {
- const rel = relatoriosFiltrados[i];
+ 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 {
- const doctorId = rel.created_by || rel.requested_by || null;
- if (doctorId) {
- const docRes = await GetDoctorByID(doctorId, authHeader);
+ 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: rel.requested_by || '' });
+ medicos.push({ full_name: '' });
}
} catch (err) {
+ // fallback para requested_by se houver
medicos.push({ full_name: rel.requested_by || '' });
}
}
setPacientesComRelatorios(pacientes);
setMedicosComRelatorios(medicos);
};
- if (relatoriosFiltrados.length > 0) fetchRelData();
+
+ if (relatoriosFinais.length > 0) fetchRelData();
else {
setPacientesComRelatorios([]);
setMedicosComRelatorios([]);
}
- }, [relatoriosFiltrados, authHeader]);
+ }, [relatoriosFinais, authHeader]);
- const abrirModal = (relatorio, index) => {
+ 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(index);
+ setModalIndex(indexToUse);
setShowModal(true);
};
@@ -127,6 +203,7 @@ const DoctorRelatorioManager = () => {
setTermoPesquisa('');
setFiltroExame('');
setRelatoriosFinais(relatoriosOriginais);
+ setPaginaAtual(1);
};
const BaixarPDFdoRelatorio = (nome_paciente, idx) => {
@@ -198,17 +275,17 @@ const DoctorRelatorioManager = () => {
Paciente: {pacientesComRelatorios[modalIndex]?.full_name}
Data de nascimento: {pacientesComRelatorios[modalIndex]?.birth_date || '—'}
-
Data do exame: {relatoriosFiltrados[modalIndex]?.due_at || '—'}
+
Data do exame: {relatoriosFinais[modalIndex]?.due_at || '—'}
Conteúdo do Relatório:
-
+
-
Dr {medicosComRelatorios[modalIndex]?.full_name || relatoriosFiltrados[modalIndex]?.requested_by}
-
Emitido em: {relatoriosFiltrados[modalIndex]?.created_at || '—'}
+
Dr {medicosComRelatorios[modalIndex]?.full_name || relatoriosFinais[modalIndex]?.requested_by}
+
Emitido em: {relatoriosFinais[modalIndex]?.created_at || '—'}
@@ -295,7 +372,8 @@ const DoctorRelatorioManager = () => {
{relatoriosPaginados.length > 0 ? (
relatoriosPaginados.map((relatorio, index) => {
- const paciente = pacientesData[relatorio.patient_id];
+ const globalIndex = relatoriosFinais.findIndex(r => r.id === relatorio.id);
+ const paciente = pacientesComRelatorios[globalIndex];
return (
| {paciente?.full_name || 'Carregando...'} |
@@ -381,4 +459,4 @@ const DoctorRelatorioManager = () => {
);
};
-export default DoctorRelatorioManager;
\ No newline at end of file
+export default DoctorRelatorioManager;
diff --git a/src/pages/LaudoManager.jsx b/src/pages/LaudoManager.jsx
index 8276a9c..e603dfa 100644
--- a/src/pages/LaudoManager.jsx
+++ b/src/pages/LaudoManager.jsx
@@ -1,337 +1,514 @@
// src/pages/LaudoManager.jsx
-import React, { useState, useEffect } from "react";
-import "./LaudoStyle.css"; // Importa o CSS externo
+import API_KEY from '../components/utils/apiKeys';
+import { Link } from 'react-router-dom';
+import React, { 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 '../PagesMedico/TiptapViewer'
+import '../PagesMedico/styleMedico/DoctorRelatorioManager.css';
-/* ===== Mock data (simula APIDOG) ===== */
-function mockFetchLaudos() {
- return [
- {
- id: "LAU-300551296",
- pedido: 300551296,
- data: "29/07/2025",
- paciente: { nome: "Sarah Mariana Oliveira", cpf: "616.869.070-**", nascimento: "1990-03-25", convenio: "Unimed" },
- solicitante: "Sandro Rangel Santos",
- exame: "US - Abdome Total",
- conteudo: "RELATÓRIO MÉDICO\n\nAchados: Imagens compatíveis com ...\nConclusão: Órgãos sem alterações significativas.",
- status: "rascunho"
- },
- {
- id: "LAU-300659170",
- pedido: 300659170,
- data: "29/07/2025",
- paciente: { nome: "Laissa Helena Marquetti", cpf: "950.684.57-**", nascimento: "1986-09-12", convenio: "Bradesco" },
- solicitante: "Sandro Rangel Santos",
- exame: "US - Mamária Bilateral",
- conteudo: "RELATÓRIO MÉDICO\n\nAchados: text...",
- status: "liberado"
- },
- {
- id: "LAU-300658301",
- pedido: 300658301,
- data: "28/07/2025",
- paciente: { nome: "Vera Lúcia Oliveira Santos", cpf: "928.005.**", nascimento: "1979-02-02", convenio: "Particular" },
- solicitante: "Dr. Fulano",
- exame: "US - Transvaginal",
- conteudo: "RELATÓRIO MÉDICO\n\nAchados: ...",
- status: "entregue"
- }
- ];
-}
+const LaudoManager = () => {
+ const navigate = useNavigate();
+ const { getAuthorizationHeader } = useAuth();
+ const authHeader = getAuthorizationHeader();
-function mockDeleteLaudo(id) {
- return new Promise((res) => setTimeout(() => res({ ok: true }), 500));
-}
+ 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 [modalIndex, setModalIndex] = useState(0);
-/* ===== Componente ===== */
-export default function LaudoManager() {
- const [laudos, setLaudos] = useState([]);
- const [openDropdownId, setOpenDropdownId] = useState(null);
+ const [showProtocolModal, setShowProtocolModal] = useState(false);
+ const [protocolForIndex, setProtocolForIndex] = useState(null);
- /* viewerLaudo é usado para mostrar o editor/leitura;
- previewLaudo é usado para a pré-visualização (sem bloquear) */
- const [viewerLaudo, setViewerLaudo] = useState(null);
- const [previewLaudo, setPreviewLaudo] = useState(null);
- const [showPreview, setShowPreview] = useState(false);
+ const [paginaAtual, setPaginaAtual] = useState(1);
+ const [itensPorPagina, setItensPorPagina] = useState(10);
- const [showConfirmDelete, setShowConfirmDelete] = useState(false);
- const [toDelete, setToDelete] = useState(null);
- const [loadingDelete, setLoadingDelete] = useState(false);
+ // agora guardamos a mensagem (null = sem aviso)
+ const [noPermissionText, setNoPermissionText] = useState(null);
- /* notificação simples (sem backdrop) para 'sem permissão' */
- const [showNoPermission, setShowNoPermission] = useState(false);
+ const isSecretary = true;
- /* pesquisa */
- const [query, setQuery] = useState("");
-
- /* Para simplificar: eu assumo aqui que estamos na visão da secretaria */
- const isSecretary = true; // permanece true (somente leitura)
+ 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(() => {
- // Importa os dados mock apenas
- const data = mockFetchLaudos();
- setLaudos(data);
- }, []);
+ let mounted = true;
- // Fecha dropdown ao clicar fora
- useEffect(() => {
- function onDocClick(e) {
- if (e.target.closest && e.target.closest('.action-btn')) return;
- if (e.target.closest && e.target.closest('.dropdown')) return;
- setOpenDropdownId(null);
- }
- document.addEventListener('click', onDocClick);
- return () => document.removeEventListener('click', onDocClick);
- }, []);
+ 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' };
- function toggleDropdown(id, e) {
- e.stopPropagation();
- setOpenDropdownId(prev => (prev === id ? null : id));
- }
+ const res = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", requestOptions);
+ const data = await res.json();
- /* (botao editar) */
- function handleOpenViewer(laudo) {
- setOpenDropdownId(null);
- if (isSecretary) {
- // (notificação sem bloquear)
- setShowNoPermission(true);
- return;
- }
- setViewerLaudo(laudo);
- }
+ 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));
- /* (botao imprimir) */
- function handlePrint(laudo) {
- // evitar bug: fechar viewer antes de abrir preview
- setViewerLaudo(null);
- setPreviewLaudo(laudo);
- setShowPreview(true);
- setOpenDropdownId(null);
- }
-
- /* (botao excluir) */
- function handleRequestDelete(laudo) {
- setToDelete(laudo);
- setOpenDropdownId(null);
- setShowConfirmDelete(true);
- }
-
- /* (funcionalidade do botao de excluir) */
- async function doConfirmDelete(confirm) {
- if (!toDelete) return;
- if (!confirm) {
- setShowConfirmDelete(false);
- setToDelete(null);
- return;
- }
- setLoadingDelete(true);
- try {
- const resp = await mockDeleteLaudo(toDelete.id);
- if (resp.ok || resp === true) {
- // removo o laudo da lista local
- setLaudos(curr => curr.filter(l => l.id !== toDelete.id));
- setShowConfirmDelete(false);
- setToDelete(null);
- alert("Laudo excluído com sucesso.");
- } else {
- alert("Erro ao excluir. Tente novamente.");
+ if (mounted) {
+ setRelatoriosOriginais(unique);
+ setRelatoriosFiltrados(unique);
+ setRelatoriosFinais(unique);
+ }
+ } catch (err) {
+ console.error('Erro listar relatórios', err);
+ if (mounted) {
+ setRelatoriosOriginais([]);
+ setRelatoriosFiltrados([]);
+ setRelatoriosFinais([]);
+ }
}
- } catch (err) {
- alert("Erro de rede ao excluir.");
- } finally {
- setLoadingDelete(false);
- }
- }
+ };
- /* filtro de pesquisa (por pedido ou nome do paciente) */
- const normalized = (s = "") => String(s).toLowerCase();
- const filteredLaudos = laudos.filter(l => {
- const q = normalized(query).trim();
- if (!q) return true;
- if (normalized(l.pedido).includes(q)) return true;
- if (normalized(l.paciente?.nome).includes(q)) return true;
- return false;
- });
+ fetchReports();
+ const refreshHandler = () => fetchReports();
+ window.addEventListener('reports:refresh', refreshHandler);
+ return () => {
+ mounted = false;
+ window.removeEventListener('reports:refresh', refreshHandler);
+ };
+ }, [authHeader]);
+
+ useEffect(() => {
+ const fetchRelData = async () => {
+ const pacientes = [];
+ const medicos = [];
+ for (let i = 0; i < relatoriosFiltrados.length; i++) {
+ const rel = relatoriosFiltrados[i];
+ try {
+ const pacienteRes = await GetPatientByID(rel.patient_id, authHeader);
+ pacientes.push(Array.isArray(pacienteRes) ? pacienteRes[0] : pacienteRes);
+ } catch (err) {
+ pacientes.push(null);
+ }
+ try {
+ const doctorId = rel.created_by || rel.requested_by || null;
+ if (doctorId) {
+ 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 abrirModal = (relatorio, index) => {
+ setRelatorioModal(relatorio);
+ setModalIndex(index);
+ setShowModal(true);
+ };
+
+ const limparFiltros = () => {
+ setTermoPesquisa('');
+ setFiltroExame('');
+ setRelatoriosFinais(relatoriosOriginais);
+ };
+
+ 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 handleEditClick = (relatorio) => {
+ if (isSecretary) {
+ setNoPermissionText('Sem permissão para editar/criar laudo.');
+ return;
+ }
+ navigate(`/medico/relatorios/${relatorio.id}/edit`);
+ };
+
+ const handleOpenProtocol = (relatorio, index) => {
+ setProtocolForIndex({ relatorio, index });
+ setShowProtocolModal(true);
+ };
+
+ const handleLiberarLaudo = async (relatorio) => {
+ if (isSecretary) {
+ // MUDANÇA: mostrar "Ainda não implementado"
+ setNoPermissionText('Ainda não implementado');
+ return;
+ }
+ // para médicos: implementação real já estava antes (mantive o bloco, caso queira)
+ try {
+ const myHeaders = new Headers();
+ myHeaders.append('apikey', API_KEY);
+ if (authHeader) myHeaders.append('Authorization', authHeader);
+ myHeaders.append('Content-Type', 'application/json');
+ myHeaders.append('Prefer', 'return=representation');
+
+ const body = JSON.stringify({ status: 'liberado' });
+
+ const res = await fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${relatorio.id}`, {
+ method: 'PATCH',
+ headers: myHeaders,
+ body
+ });
+
+ if (!res.ok) {
+ const txt = await res.text().catch(()=> '');
+ throw new Error('Erro ao liberar laudo: ' + res.status + ' ' + txt);
+ }
+
+ // refetch simples
+ const refreshed = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?select=*", {
+ method: 'GET',
+ headers: (() => { const h=new Headers(); h.append('apikey', API_KEY); if(authHeader) h.append('Authorization', authHeader); return h; })(),
+ });
+ const data = await refreshed.json();
+ setRelatoriosOriginais(Array.isArray(data)? data : []);
+ setRelatoriosFiltrados(Array.isArray(data)? data : []);
+ setRelatoriosFinais(Array.isArray(data)? data : []);
+ alert('Laudo liberado com sucesso.');
+ } catch (err) {
+ console.error(err);
+ alert('Erro ao liberar laudo. Veja console.');
+ }
+ };
+
+ useEffect(() => {
+ const q = (termoPesquisa || '').toLowerCase().trim();
+ const ex = (filtroExame || '').toLowerCase().trim();
+
+ let items = relatoriosOriginais || [];
+ if (q) {
+ items = items.filter(r => {
+ const patientName = (r.patient_name || r.patient_fullname || '').toString().toLowerCase();
+ const pedido = (r.id || r.request_id || r.request || '').toString().toLowerCase();
+ return patientName.includes(q) || pedido.includes(q) || (r.patient_id && r.patient_id.toString().includes(q));
+ });
+ }
+ if (ex) items = items.filter(r => (r.exam || r.exame || '').toLowerCase().includes(ex));
+
+ setRelatoriosFiltrados(items);
+ setRelatoriosFinais(items);
+ setPaginaAtual(1);
+ }, [termoPesquisa, filtroExame, relatoriosOriginais]);
+
+ 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 (
-
-
-
-
-
Gerenciamento de Laudo
- {/* removi a linha "Visualização: Secretaria" conforme pedido */}
-
-
-
-
- setQuery(e.target.value)}
- style={{ width:"100%", padding:12, borderRadius:8, border:"1px solid #e6eef8" }}
- />
-
-
- {filteredLaudos.length === 0 ? (
-
Nenhum laudo encontrado.
- ) : (
-
- {filteredLaudos.map((l) => (
-
-
-
{l.pedido}
-
{l.data}
+
+
Lista de Relatórios
+
+
+
+
+
+
Relatórios Cadastrados
+
+
-
-
{l.paciente.nome}
-
{l.paciente.cpf} • {l.paciente.convenio}
-
-
{l.exame}
-
{l.solicitante}
-
{l.status}
+
-
-
toggleDropdown(l.id, e)} title="Ações">
-
+
+
+
+ Filtros
+
+
+
+
+
+ setTermoPesquisa(e.target.value)}
+ />
+
+
+
+
+
+ setFiltroExame(e.target.value)}
+ />
+
+
+
+
+
+
+
+ {relatoriosFinais.length} DE {relatoriosOriginais.length} RELATÓRIOS ENCONTRADOS
+
+
+
- {openDropdownId === l.id && (
-
-
handleOpenViewer(l)}>Editar
-
handlePrint(l)}>Imprimir
-
{ alert("Protocolo de entrega: formulário (não implementado)."); setOpenDropdownId(null); }}>Protocolo de entrega
-
{ alert("Liberar laudo: requer permissão de médico. (não implementado)"); setOpenDropdownId(null); }}>Liberar laudo
-
handleRequestDelete(l)} style={{ color:"#c23b3b" }}>Excluir laudo
+
+
+
+
+ | Paciente |
+ CPF |
+ Exame |
+ |
+
+
+
+ {relatoriosPaginados.length > 0 ? (
+ relatoriosPaginados.map((relatorio, index) => {
+ const paciente = pacientesComRelatorios[index] || {};
+ return (
+
+ | {paciente?.full_name || relatorio.patient_name || 'Carregando...'} |
+ {paciente?.cpf || 'Carregando...'} |
+ {relatorio.exam || relatorio.exame || '—'} |
+
+
+
+
+
+
+ {/* Removido o botão "Imprimir" daqui (agora no Ver Detalhes) */}
+
+
+
+
+
+ |
+
+ );
+ })
+ ) : (
+ | Nenhum relatório encontrado. |
+ )}
+
+
+
+ {relatoriosFinais.length > 0 && (
+
+
+ Itens por página:
+
+
+
+
+
+ Página {paginaAtual} de {totalPaginas} •
+ Mostrando {indiceInicial + 1}-{Math.min(indiceFinal, relatoriosFinais.length)} de {relatoriosFinais.length} itens
+
+
+
+
)}
- ))}
+
- )}
+
- {/* Viewer modal (modo leitura) — só abre para quem tem permissão */}
- {viewerLaudo && !showPreview && !isSecretary && (
-
-
setViewerLaudo(null)} />
-
-
-
-
{viewerLaudo.paciente.nome}
-
- Nasc.: {viewerLaudo.paciente.nascimento} • {computeAge(viewerLaudo.paciente.nascimento)} anos • {viewerLaudo.paciente.cpf} • {viewerLaudo.paciente.convenio}
+ {/* Modal principal (detalhes) */}
+ {showModal && relatorioModal && (
+
setShowModal(false)}>
+
e.stopPropagation()}>
+
+
+
Relatório de {pacientesComRelatorios[modalIndex]?.full_name || relatorioModal.patient_name || 'Paciente'}
+
+
+
+
+
+
+
+
+
Paciente: {pacientesComRelatorios[modalIndex]?.full_name || relatorioModal.patient_name || '—'}
+
Data de nascimento: {pacientesComRelatorios[modalIndex]?.birth_date || '—'}
+
Data do exame: {relatorioModal?.due_at || relatorioModal?.date || '—'}
+
+
Conteúdo do Relatório:
+
+
+
+
+
+
+
Dr {medicosComRelatorios[modalIndex]?.full_name || relatorioModal?.requested_by || '—'}
+
Emitido em: {relatorioModal?.created_at || '—'}
+
-
-
-
-
-
-
-
-
B
-
I
-
U
-
Fonte
-
Tamanho
-
Lista
-
Campos
-
Modelos
-
Imagens
-
-
-
- {viewerLaudo.conteudo.split("\n").map((line, i) => (
-
{line}
- ))}
-
-
-
)}
- {/* Preview modal — agora não bloqueia a tela (sem backdrop escuro), botão imprimir é interativo */}
- {showPreview && previewLaudo && (
-
-
-
-
-
Pré-visualização - {previewLaudo.paciente.nome}
-
-
-
-
-
-
-
-
- RELATÓRIO MÉDICO
-
-
- {previewLaudo.paciente.nome} • Nasc.: {previewLaudo.paciente.nascimento} • CPF: {previewLaudo.paciente.cpf}
+ {/* Modal Protocolo */}
+ {showProtocolModal && protocolForIndex && (
+
setShowProtocolModal(false)}>
+
e.stopPropagation()}>
+
+
+
Protocolo de Entrega - {protocolForIndex.relatorio?.patient_name || 'Paciente'}
+
-
- {previewLaudo.conteudo}
+
+
+
Pedido: {protocolForIndex.relatorio?.id || protocolForIndex.relatorio?.pedido}
+
Paciente: {protocolForIndex.relatorio?.patient_name || '—'}
+
Data: {protocolForIndex.relatorio?.due_at || protocolForIndex.relatorio?.date || '—'}
+
+
Protocolo de entrega gerado automaticamente. (Substitua pelo endpoint real se houver)
+
+
+
+
+
+
)}
- {/* Notificação simples: Sem permissão (exibe sem backdrop escuro) - centralizada */}
- {showNoPermission && (
-
-
Sem permissão para editar
-
Você está na visualização da secretaria. Edição disponível somente para médicos autorizados.
-
-
-
-
- )}
-
- {/* Confirm delete modal (simples: Sim / Não) */}
- {showConfirmDelete && toDelete && (
-
-
-
Confirmar exclusão
-
Você tem certeza que quer excluir o laudo {toDelete.pedido} - {toDelete.paciente.nome} ? Esta ação é irreversível.
-
-
-
-
+ {/* Variável de aviso: mostra texto personalizado */}
+ {noPermissionText && (
+
setNoPermissionText(null)}>
+
e.stopPropagation()}>
+
+
+
{noPermissionText}
+
{/* opcional descrição aqui */}
+
+
+
+
)}
+
);
-}
+};
-/* ===== Helpers ===== */
-function computeAge(birth) {
- if (!birth) return "-";
- const [y,m,d] = birth.split("-").map(x => parseInt(x,10));
- if (!y) return "-";
- const today = new Date();
- let age = today.getFullYear() - y;
- const mm = today.getMonth() + 1;
- const dd = today.getDate();
- if (mm < m || (mm === m && dd < d)) age--;
- return age;
-}
+export default LaudoManager;
diff --git a/src/pages/LaudoStyle.css b/src/pages/LaudoStyle.css
index 31f1542..5a08f68 100644
--- a/src/pages/LaudoStyle.css
+++ b/src/pages/LaudoStyle.css
@@ -309,4 +309,39 @@ html[data-bs-theme="dark"] .notice-card {
background: #232323 !important;
color: #e0e0e0 !important;
box-shadow: 0 8px 30px rgba(10,20,40,0.32) !important;
-}
\ No newline at end of file
+}
+
+/* Botões coloridos para Protocolo e Liberar (combina com estilo dos outros botões) */
+.btn-protocolo {
+ background-color: #E6F2FF;
+ color: #004085;
+ border: 1px solid #d6e9ff;
+ padding: 8px 12px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+}
+.btn-protocolo:hover {
+ background-color: #cce5ff;
+}
+
+/* Liberar laudo - estilo parecido com o botão editar (amarelo claro) */
+.btn-liberar {
+ background-color: #FFF3CD;
+ color: #856404;
+ border: 1px solid #ffeaa7;
+ padding: 8px 12px;
+ border-radius: 8px;
+ font-weight: 600;
+ cursor: pointer;
+}
+.btn-liberar:hover {
+ background-color: #ffeaa7;
+}
+
+/* Ajuste visual (pequeno) para espaçamento horizontal dos botões da linha */
+.table-responsive .d-flex.gap-2 .btn {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+}