From dd598a4ce3b19832089b7681ddb2588c4d4eb222 Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Wed, 12 Nov 2025 12:05:15 -0300 Subject: [PATCH] =?UTF-8?q?relatorio=20secretaria=20e=20vizualiza=C3=A7?= =?UTF-8?q?=C3=A3o=20dos=20relatorios=20medicos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/PagesMedico/DoctorRelatorioManager.jsx | 130 +++- src/pages/LaudoManager.jsx | 745 +++++++++++++-------- src/pages/LaudoStyle.css | 37 +- 3 files changed, 601 insertions(+), 311 deletions(-) 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
+
+ + + + + + + + + + + {relatoriosPaginados.length > 0 ? ( + relatoriosPaginados.map((relatorio, index) => { + const paciente = pacientesComRelatorios[index] || {}; + return ( + + + + + + + ); + }) + ) : ( + + )} + +
PacienteCPFExame
{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'}
+ +
+ +
+
+
+

Clinica Rise up

+

Dr - CRM/SP 123456

+

Avenida - (79) 9 4444-4444

+
+ +
+

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; +}