From f3e74702e197645cdc5aac199b498a03578e7aaf Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Sat, 6 Sep 2025 14:14:50 -0300 Subject: [PATCH 1/3] Laudo do Paciente --- package-lock.json | 34 ++++ package.json | 1 + src/App.js | 4 + src/data/sidebar-items.json | 8 +- src/pages/LaudoManager.jsx | 327 ++++++++++++++++++++++++++++++++++++ 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 src/pages/LaudoManager.jsx diff --git a/package-lock.json b/package-lock.json index 3a03d4c..668ad01 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "bootstrap-icons": "^1.13.1", "flatpickr": "^4.6.13", "perfect-scrollbar": "^1.5.6", + "powershell": "^2.3.3", "quill": "^2.0.3", "rater-js": "^1.0.1", "react": "^18.2.0", @@ -25314,6 +25315,11 @@ "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==", "license": "MIT" }, + "node_modules/is-undefined": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/is-undefined/-/is-undefined-1.0.12.tgz", + "integrity": "sha512-qaX2mymwUhMq+NQPnx5iR/u2PgqhL6jLzDunMmonOgVofqoFhxzd6kOmiL0DLYZUkN/RvNWYPenoANVn5phlaA==" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -25357,6 +25363,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-win": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/is-win/-/is-win-1.0.11.tgz", + "integrity": "sha512-+XpgpizPqNzohXiqme7pfhAhpoG0Eo+CtuSx/XYW4enarERuheDbNbFrm4+XYylpV1w/eI+si5itFA0RfCWjog==" + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -30018,6 +30029,16 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "license": "MIT" }, + "node_modules/powershell": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/powershell/-/powershell-2.3.3.tgz", + "integrity": "sha512-xLEFA2BWxlhrcp2wecH3rGVhG/z1kQDFvie1ynHZVjXdcYWaIaUrshCa8kep7Sj8c0EdNcNnyZU79oTbJRFDsQ==", + "dependencies": { + "is-undefined": "^1.0.0", + "is-win": "^1.0.2", + "spawno": "^1.0.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -30069,6 +30090,11 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "license": "MIT" }, + "node_modules/proc-output": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/proc-output/-/proc-output-1.0.9.tgz", + "integrity": "sha512-XARWwM2pPNU/U8V4OuQNQLyjFqvHk1FRB5sFd1CCyT2vLLfDlLRLE4f6njcvm4Kyek1VzvF8MQRAYK1uLOlZmw==" + }, "node_modules/process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", @@ -32146,6 +32172,14 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/spawno": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spawno/-/spawno-1.0.4.tgz", + "integrity": "sha512-euy9JLkCC2SvXNYZAi9WBTHDxbjSWNCaeLhLIH+BGW1Xb/3yKxoWOT2kanSS1a5wB0iukDYu79FJMNJsGW7azA==", + "dependencies": { + "proc-output": "^1.0.0" + } + }, "node_modules/spdy": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/spdy/-/spdy-4.0.2.tgz", diff --git a/package.json b/package.json index 2ee1efb..e3e4a86 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bootstrap-icons": "^1.13.1", "flatpickr": "^4.6.13", "perfect-scrollbar": "^1.5.6", + "powershell": "^2.3.3", "quill": "^2.0.3", "rater-js": "^1.0.1", "react": "^18.2.0", diff --git a/src/App.js b/src/App.js index 165f091..0c3d186 100644 --- a/src/App.js +++ b/src/App.js @@ -14,6 +14,7 @@ import Details from './pages/Details'; //import DoctorEditPage from './components/doctors/DoctorEditPage'; import DoctorTable from './pages/DoctorTable'; import DoctorFormLayout from './pages/DoctorFormLayout'; +import LaudoManager from "./pages/LaudoManager"; function App() { const [isSidebarActive, setIsSidebarActive] = useState(true); @@ -35,6 +36,9 @@ const renderPageContent = () => { else if(currentPage === 'doctor-form-layout'){ return } + else if (currentPage === 'laudo-manager') { + return ; +} else if (currentPage === 'table') { return ; } diff --git a/src/data/sidebar-items.json b/src/data/sidebar-items.json index 9d39584..1f18dee 100644 --- a/src/data/sidebar-items.json +++ b/src/data/sidebar-items.json @@ -26,6 +26,12 @@ "name": "Lista de Médico", "icon": "table", "url": "doctor-table" - } + }, + + { + "name": "Laudo do Paciente", + "icon": "table", + "url": "laudo-manager" + } ] \ No newline at end of file diff --git a/src/pages/LaudoManager.jsx b/src/pages/LaudoManager.jsx new file mode 100644 index 0000000..fb73c59 --- /dev/null +++ b/src/pages/LaudoManager.jsx @@ -0,0 +1,327 @@ +import React, { useState, useRef, useEffect } from "react"; + +/* ===== Estilos embutidos ===== */ +const styles = ` +.laudo-wrap { display:flex; gap:24px; padding:18px; font-family: Inter, Roboto, Arial, sans-serif; } +.left-col { width: 100%; max-width: 1160px; background:#f7fbff; border-radius:8px; padding:18px; box-shadow: 0 1px 0 rgba(0,0,0,0.03);} +.title-row { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; } +.page-title { font-size:20px; color:#2b4a78; font-weight:700; } +.laudo-table { width:100%; border-collapse:collapse; background:#fff; border-radius:8px; overflow:visible; } +.laudo-row { display:flex; padding:14px 12px; align-items:center; border-bottom:1px solid #eef3f8; position:relative; overflow:visible; } +.col { flex:1; padding:0 8px; font-size:14px; color:#2e3a4b; } +.col.small { flex:0 0 90px; text-align:right; } +.row-actions { position:relative; flex: 0 0 88px; display:flex; justify-content:flex-end; } +.action-btn { background:transparent; border:1px solid #d7e6fb; border-radius:8px; height:40px; width:40px; display:flex; align-items:center; justify-content:center; cursor:pointer; } +.dropdown { position:absolute; right:0; top:48px; background:white; border-radius:8px; box-shadow: 0 10px 30px rgba(20,30,50,0.12); min-width:220px; padding:8px 0; z-index:9999; } +.dropdown .item { padding:12px 18px; cursor:pointer; font-size:15px; color:#244056; } +.dropdown .item:hover { background:#f6fbff; } +.viewer-modal, .preview-modal, .confirm-modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; z-index:12000; } +.modal-backdrop { position:absolute; inset:0; background: rgba(9,20,40,0.45); } +.modal-card { position:relative; width:92%; max-width:1100px; background:white; border-radius:10px; padding:18px; box-shadow: 0 10px 60px rgba(10,20,40,0.25); max-height:88vh; overflow:auto; } +.viewer-header { display:flex; justify-content:space-between; align-items:flex-start; gap:10px; margin-bottom:12px; } +.patient-info { font-size:13px; color:#3a556b; } +.toolbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; } +.tool-btn { padding:8px 10px; border-radius:6px; border:1px solid #e6eef8; cursor:pointer; background:#fff; font-size:13px; } +.editor-area { border:1px solid #e6eef8; border-radius:8px; padding:14px; min-height:360px; background: #fff; color:#1f2d3d; font-size:15px; line-height:1.5; } +.footer-controls { display:flex; justify-content:space-between; align-items:center; margin-top:12px; } +.toggle { display:flex; align-items:center; gap:8px; } +.btn { padding:8px 12px; border-radius:8px; border:none; cursor:pointer; font-weight:600; } +.btn.secondary { background:#eef6ff; color:#2f63a6; border:1px solid #d6e9ff; } +.btn.primary { background:#2f63a6; color:white; } +.small-muted { color:#7f95a8; font-size:13px; } +.empty { padding:40px; text-align:center; color:#7d97b4; } +`; + +/* ===== 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: "rascunho" + }, + { + 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: "rascunho" + } + ]; +} + +function mockDeleteLaudo(id) { + return new Promise((res) => setTimeout(() => res({ ok: true }), 500)); +} + +/* ===== Componente ===== */ +export default function LaudoManager() { + const [laudos, setLaudos] = useState([]); + const [openDropdownId, setOpenDropdownId] = useState(null); + const [viewerLaudo, setViewerLaudo] = useState(null); + const [showPreview, setShowPreview] = useState(false); + const [showConfirmDelete, setShowConfirmDelete] = useState(false); + const [toDelete, setToDelete] = useState(null); + const [loadingDelete, setLoadingDelete] = useState(false); + + useEffect(() => { + const el = document.createElement("style"); + el.innerHTML = styles; + document.head.appendChild(el); + const data = mockFetchLaudos(); + setLaudos(data); + return () => document.head.removeChild(el); + }, []); + + // Fecha dropdown ao clicar fora + useEffect(() => { + function onDocClick(e) { + // se clicar em um botão de ação (ícone), não fecha + if (e.target.closest && e.target.closest('.action-btn')) return; + // se clicar dentro de um dropdown, não fecha + if (e.target.closest && e.target.closest('.dropdown')) return; + // caso contrário, fecha qualquer dropdown aberto + setOpenDropdownId(null); + } + document.addEventListener('click', onDocClick); + return () => document.removeEventListener('click', onDocClick); + }, []); + + function toggleDropdown(id, e) { + e.stopPropagation(); // evita que o document click feche imediatamente + setOpenDropdownId(prev => (prev === id ? null : id)); + } + + function handleOpenViewer(laudo) { + setViewerLaudo(laudo); + setOpenDropdownId(null); + } + + function handleRequestDelete(laudo) { + setToDelete(laudo); + setOpenDropdownId(null); + setShowConfirmDelete(true); + } + + async function confirmDelete(typed) { + if (!toDelete) return; + if (typed.trim().toUpperCase() !== "EXCLUIR") { + alert("Confirmação inválida. Digite EXCLUIR para confirmar."); + return; + } + setLoadingDelete(true); + try { + const resp = await mockDeleteLaudo(toDelete.id); + if (resp.ok || resp === true) { + 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."); + } + } catch (err) { + alert("Erro de rede ao excluir."); + } finally { + setLoadingDelete(false); + } + } + + function handlePrint(laudo) { + setViewerLaudo(laudo); + setShowPreview(true); + setOpenDropdownId(null); + } + + return ( +
+
+
+
Gerenciamento de Laudo
+
+ + {laudos.length === 0 ? ( +
Nenhum laudo encontrado.
+ ) : ( +
+ {laudos.map((l) => ( +
+
+
{l.pedido}
+
{l.data}
+
+
+
{l.paciente.nome}
+
{l.paciente.cpf} • {l.paciente.convenio}
+
+
{l.exame}
+
{l.solicitante}
+ +
+
toggleDropdown(l.id, e)} title="Ações"> + +
+ + {/* dropdown associado ao laudo (não será cortado) */} + {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
+
+ )} +
+
+ ))} +
+ )} +
+ + {/* Viewer modal (modo leitura) */} + {viewerLaudo && !showPreview && ( +
+
setViewerLaudo(null)} /> +
+
+
+
{viewerLaudo.paciente.nome}
+
+ Nasc.: {viewerLaudo.paciente.nascimento} • {computeAge(viewerLaudo.paciente.nascimento)} anos • {viewerLaudo.paciente.cpf} • {viewerLaudo.paciente.convenio} +
+
+ +
+ + +
+
+ +
+
B
+
I
+
U
+
Fonte
+
Tamanho
+
Lista
+
Campos
+
Modelos
+
Imagens
+
+ +
+ {viewerLaudo.conteudo.split("\n").map((line, i) => ( +

{line}

+ ))} +
+ +
+
+ + + +
+ +
+ + +
+
+
+
+ )} + + {/* Preview modal */} + {showPreview && viewerLaudo && ( +
+
setShowPreview(false)} /> +
+
+
Pré-visualização - {viewerLaudo.paciente.nome}
+
+ + +
+
+ +
+
+ RELATÓRIO MÉDICO +
+
+ {viewerLaudo.paciente.nome} • Nasc.: {viewerLaudo.paciente.nascimento} • CPF: {viewerLaudo.paciente.cpf} +
+ +
+ {viewerLaudo.conteudo} +
+
+
+
+ )} + + {/* Confirm delete modal */} + {showConfirmDelete && toDelete && ( +
+
{ if (!loadingDelete) setShowConfirmDelete(false); }} /> +
+
Excluir laudo
+
Você está prestes a excluir o laudo {toDelete.pedido} - {toDelete.paciente.nome}. Esta ação é irreversível.
+ +
+
Para confirmar, digite EXCLUIR abaixo e clique em Confirmar.
+ setShowConfirmDelete(false)} /> +
+
+
+ )} +
+ ); +} + +/* ===== 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; +} + +function ConfirmDeleteInput({ onConfirm, onCancel, loading }) { + const [txt, setTxt] = useState(""); + return ( +
+ setTxt(e.target.value)} placeholder="Digite EXCLUIR" style={{ width:"100%", padding:"10px", borderRadius:6, border:"1px solid #e1ecfb", marginBottom:8 }} /> +
+ + +
+
+ ); +} From 1af82689435a8a60764a3882173929af0023f93c Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Fri, 12 Sep 2025 15:04:17 -0300 Subject: [PATCH 2/3] =?UTF-8?q?Atualizac=C3=A3o=20do=20laudo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/LaudoManager.jsx | 156 ++++++++++++++++++++++--------------- 1 file changed, 93 insertions(+), 63 deletions(-) diff --git a/src/pages/LaudoManager.jsx b/src/pages/LaudoManager.jsx index fb73c59..d95de17 100644 --- a/src/pages/LaudoManager.jsx +++ b/src/pages/LaudoManager.jsx @@ -1,13 +1,14 @@ +// src/pages/LaudoManager.jsx import React, { useState, useRef, useEffect } from "react"; /* ===== Estilos embutidos ===== */ +/* Eu coloquei os estilos aqui para simplificar a edição. */ const styles = ` .laudo-wrap { display:flex; gap:24px; padding:18px; font-family: Inter, Roboto, Arial, sans-serif; } -.left-col { width: 100%; max-width: 1160px; background:#f7fbff; border-radius:8px; padding:18px; box-shadow: 0 1px 0 rgba(0,0,0,0.03);} +.left-col { width: 100%; max-width: 1160px; background:#ffffff; border-radius:8px; padding:18px; box-shadow: 0 1px 0 rgba(0,0,0,0.03); } /* <-- fundo branco */ .title-row { display:flex; justify-content:space-between; align-items:center; margin-bottom:12px; } .page-title { font-size:20px; color:#2b4a78; font-weight:700; } -.laudo-table { width:100%; border-collapse:collapse; background:#fff; border-radius:8px; overflow:visible; } -.laudo-row { display:flex; padding:14px 12px; align-items:center; border-bottom:1px solid #eef3f8; position:relative; overflow:visible; } +.laudo-row { display:flex; padding:14px 12px; align-items:center; border-bottom:1px solid #eef3f8; position:relative; overflow:visible; background: transparent; } .col { flex:1; padding:0 8px; font-size:14px; color:#2e3a4b; } .col.small { flex:0 0 90px; text-align:right; } .row-actions { position:relative; flex: 0 0 88px; display:flex; justify-content:flex-end; } @@ -15,9 +16,9 @@ const styles = ` .dropdown { position:absolute; right:0; top:48px; background:white; border-radius:8px; box-shadow: 0 10px 30px rgba(20,30,50,0.12); min-width:220px; padding:8px 0; z-index:9999; } .dropdown .item { padding:12px 18px; cursor:pointer; font-size:15px; color:#244056; } .dropdown .item:hover { background:#f6fbff; } -.viewer-modal, .preview-modal, .confirm-modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; z-index:12000; } -.modal-backdrop { position:absolute; inset:0; background: rgba(9,20,40,0.45); } -.modal-card { position:relative; width:92%; max-width:1100px; background:white; border-radius:10px; padding:18px; box-shadow: 0 10px 60px rgba(10,20,40,0.25); max-height:88vh; overflow:auto; } +.viewer-modal, .preview-modal, .confirm-modal { position:fixed; inset:0; display:flex; align-items:center; justify-content:center; z-index:12000; pointer-events:none; } /* deixar pointer-events none para que não bloqueie por padrão */ +.modal-backdrop { position:absolute; inset:0; background: rgba(9,20,40,0.45); pointer-events:auto; } /* usado apenas quando necessário */ +.modal-card { position:relative; width:92%; max-width:1100px; background:white; border-radius:10px; padding:18px; box-shadow: 0 10px 60px rgba(10,20,40,0.25); max-height:88vh; overflow:auto; pointer-events:auto; } .viewer-header { display:flex; justify-content:space-between; align-items:flex-start; gap:10px; margin-bottom:12px; } .patient-info { font-size:13px; color:#3a556b; } .toolbar { display:flex; gap:8px; align-items:center; flex-wrap:wrap; margin-bottom:12px; } @@ -30,6 +31,9 @@ const styles = ` .btn.primary { background:#2f63a6; color:white; } .small-muted { color:#7f95a8; font-size:13px; } .empty { padding:40px; text-align:center; color:#7d97b4; } + +/* notificação simples (sem backdrop escuro) */ +.notice-card { position:fixed; top:20vh; left:50%; transform:translateX(-50%); background:#fff; border-radius:8px; padding:14px 18px; box-shadow:0 8px 30px rgba(10,20,40,0.12); z-index:13000; pointer-events:auto; } `; /* ===== Mock data (simula APIDOG) ===== */ @@ -53,7 +57,7 @@ function mockFetchLaudos() { solicitante: "Sandro Rangel Santos", exame: "US - Mamária Bilateral", conteudo: "RELATÓRIO MÉDICO\n\nAchados: text...", - status: "rascunho" + status: "liberado" }, { id: "LAU-300658301", @@ -63,7 +67,7 @@ function mockFetchLaudos() { solicitante: "Dr. Fulano", exame: "US - Transvaginal", conteudo: "RELATÓRIO MÉDICO\n\nAchados: ...", - status: "rascunho" + status: "entregue" } ]; } @@ -76,12 +80,23 @@ function mockDeleteLaudo(id) { export default function LaudoManager() { const [laudos, setLaudos] = useState([]); const [openDropdownId, setOpenDropdownId] = 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 [showConfirmDelete, setShowConfirmDelete] = useState(false); const [toDelete, setToDelete] = useState(null); const [loadingDelete, setLoadingDelete] = useState(false); + /* notificação simples (sem backdrop) para 'sem permissão' */ + const [showNoPermission, setShowNoPermission] = useState(false); + + /* Para simplificar: eu assumo aqui que estamos na visão da secretaria */ + const isSecretary = true; // eu deixei true para forçar o comportamento "somente leitura" + useEffect(() => { const el = document.createElement("style"); el.innerHTML = styles; @@ -94,11 +109,8 @@ export default function LaudoManager() { // Fecha dropdown ao clicar fora useEffect(() => { function onDocClick(e) { - // se clicar em um botão de ação (ícone), não fecha if (e.target.closest && e.target.closest('.action-btn')) return; - // se clicar dentro de um dropdown, não fecha if (e.target.closest && e.target.closest('.dropdown')) return; - // caso contrário, fecha qualquer dropdown aberto setOpenDropdownId(null); } document.addEventListener('click', onDocClick); @@ -106,12 +118,30 @@ export default function LaudoManager() { }, []); function toggleDropdown(id, e) { - e.stopPropagation(); // evita que o document click feche imediatamente + e.stopPropagation(); setOpenDropdownId(prev => (prev === id ? null : id)); } + /* Quando clicar em Editar: + - se for secretaria eu mostro um aviso simples sem fundo escuro + - se for médico (isSecretary=false) eu abriria o editor (aqui eu deixei o mecanismo) */ function handleOpenViewer(laudo) { + setOpenDropdownId(null); + if (isSecretary) { + // eu mostro um aviso simples (sem modal que bloqueia) + setShowNoPermission(true); + return; + } setViewerLaudo(laudo); + } + + /* Ao pedir impressão: eu fecho qualquer viewer aberto e abro a preview + - a pré-visualização NÃO bloqueia a página (removi backdrop) */ + function handlePrint(laudo) { + // Evito o bug: fechar viewer antes de abrir preview + setViewerLaudo(null); + setPreviewLaudo(laudo); + setShowPreview(true); setOpenDropdownId(null); } @@ -121,16 +151,18 @@ export default function LaudoManager() { setShowConfirmDelete(true); } - async function confirmDelete(typed) { + async function doConfirmDelete(confirm) { if (!toDelete) return; - if (typed.trim().toUpperCase() !== "EXCLUIR") { - alert("Confirmação inválida. Digite EXCLUIR para confirmar."); + if (!confirm) { + setShowConfirmDelete(false); + setToDelete(null); return; } setLoadingDelete(true); try { const resp = await mockDeleteLaudo(toDelete.id); if (resp.ok || resp === true) { + // eu removo o laudo da lista local setLaudos(curr => curr.filter(l => l.id !== toDelete.id)); setShowConfirmDelete(false); setToDelete(null); @@ -145,17 +177,18 @@ export default function LaudoManager() { } } - function handlePrint(laudo) { - setViewerLaudo(laudo); - setShowPreview(true); - setOpenDropdownId(null); - } - return (
-
Gerenciamento de Laudo
+
+
Gerenciamento de Laudo
+
Visualização: Secretaria (Somente leitura)
+
+
+ +
+
{laudos.length === 0 ? ( @@ -174,13 +207,13 @@ export default function LaudoManager() {
{l.exame}
{l.solicitante}
+
{l.status}
toggleDropdown(l.id, e)} title="Ações">
- {/* dropdown associado ao laudo (não será cortado) */} {openDropdownId === l.id && (
handleOpenViewer(l)}>Editar
@@ -197,9 +230,9 @@ export default function LaudoManager() { )}
- {/* Viewer modal (modo leitura) */} - {viewerLaudo && !showPreview && ( -
+ {/* Viewer modal (modo leitura) — só abre para quem tem permissão */} + {viewerLaudo && !showPreview && !isSecretary && ( +
setViewerLaudo(null)} />
@@ -211,7 +244,7 @@ export default function LaudoManager() {
- +
@@ -250,16 +283,16 @@ export default function LaudoManager() {
)} - {/* Preview modal */} - {showPreview && viewerLaudo && ( -
-
setShowPreview(false)} /> -
+ {/* Preview modal — agora não bloqueia a tela (sem backdrop escuro), botão imprimir é interativo */} + {showPreview && previewLaudo && ( +
+
+
-
Pré-visualização - {viewerLaudo.paciente.nome}
+
Pré-visualização - {previewLaudo.paciente.nome}
- - + +
@@ -268,28 +301,40 @@ export default function LaudoManager() { RELATÓRIO MÉDICO
- {viewerLaudo.paciente.nome} • Nasc.: {viewerLaudo.paciente.nascimento} • CPF: {viewerLaudo.paciente.cpf} + {previewLaudo.paciente.nome} • Nasc.: {previewLaudo.paciente.nascimento} • CPF: {previewLaudo.paciente.cpf}
- {viewerLaudo.conteudo} + {previewLaudo.conteudo}
)} - {/* Confirm delete modal */} - {showConfirmDelete && toDelete && ( -
-
{ if (!loadingDelete) setShowConfirmDelete(false); }} /> -
-
Excluir laudo
-
Você está prestes a excluir o laudo {toDelete.pedido} - {toDelete.paciente.nome}. Esta ação é irreversível.
+ {/* Notificação simples: Sem permissão (exibe sem backdrop escuro) */} + {showNoPermission && ( +
+
Sem permissão para editar
+
Você está na visualização da secretaria. Edição disponível somente para médicos autorizados.
+
+ +
+
+ )} -
-
Para confirmar, digite EXCLUIR abaixo e clique em Confirmar.
- setShowConfirmDelete(false)} /> + {/* 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.
+ +
+ +
@@ -309,19 +354,4 @@ function computeAge(birth) { const dd = today.getDate(); if (mm < m || (mm === m && dd < d)) age--; return age; -} - -function ConfirmDeleteInput({ onConfirm, onCancel, loading }) { - const [txt, setTxt] = useState(""); - return ( -
- setTxt(e.target.value)} placeholder="Digite EXCLUIR" style={{ width:"100%", padding:"10px", borderRadius:6, border:"1px solid #e1ecfb", marginBottom:8 }} /> -
- - -
-
- ); -} +} \ No newline at end of file From d5d03b0dd02d68aea901f8d41752d4f85e85a18b Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Sat, 13 Sep 2025 15:44:47 -0300 Subject: [PATCH 3/3] =?UTF-8?q?atualiza=C3=A7=C3=A3o=20do=20laudo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/LaudoManager.jsx | 53 +++++++++++++++++++++++++------------- 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/src/pages/LaudoManager.jsx b/src/pages/LaudoManager.jsx index d95de17..83b0680 100644 --- a/src/pages/LaudoManager.jsx +++ b/src/pages/LaudoManager.jsx @@ -1,5 +1,5 @@ // src/pages/LaudoManager.jsx -import React, { useState, useRef, useEffect } from "react"; +import React, { useState, useEffect } from "react"; /* ===== Estilos embutidos ===== */ /* Eu coloquei os estilos aqui para simplificar a edição. */ @@ -32,8 +32,8 @@ const styles = ` .small-muted { color:#7f95a8; font-size:13px; } .empty { padding:40px; text-align:center; color:#7d97b4; } -/* notificação simples (sem backdrop escuro) */ -.notice-card { position:fixed; top:20vh; left:50%; transform:translateX(-50%); background:#fff; border-radius:8px; padding:14px 18px; box-shadow:0 8px 30px rgba(10,20,40,0.12); z-index:13000; pointer-events:auto; } +/* notificação simples (centralizada) */ +.notice-card { position:fixed; top:50%; left:50%; transform:translate(-50%,-50%); background:#fff; border-radius:8px; padding:14px 18px; box-shadow:0 8px 30px rgba(10,20,40,0.12); z-index:13000; pointer-events:auto; max-width:720px; } `; /* ===== Mock data (simula APIDOG) ===== */ @@ -94,8 +94,11 @@ export default function LaudoManager() { /* notificação simples (sem backdrop) para 'sem permissão' */ const [showNoPermission, setShowNoPermission] = useState(false); + /* pesquisa */ + const [query, setQuery] = useState(""); + /* Para simplificar: eu assumo aqui que estamos na visão da secretaria */ - const isSecretary = true; // eu deixei true para forçar o comportamento "somente leitura" + const isSecretary = true; // permanece true (somente leitura) useEffect(() => { const el = document.createElement("style"); @@ -122,35 +125,34 @@ export default function LaudoManager() { setOpenDropdownId(prev => (prev === id ? null : id)); } - /* Quando clicar em Editar: - - se for secretaria eu mostro um aviso simples sem fundo escuro - - se for médico (isSecretary=false) eu abriria o editor (aqui eu deixei o mecanismo) */ + /* (botao editar) */ function handleOpenViewer(laudo) { setOpenDropdownId(null); if (isSecretary) { - // eu mostro um aviso simples (sem modal que bloqueia) + // (notificação sem bloquear) setShowNoPermission(true); return; } setViewerLaudo(laudo); } - /* Ao pedir impressão: eu fecho qualquer viewer aberto e abro a preview - - a pré-visualização NÃO bloqueia a página (removi backdrop) */ + /* (botao imprimir) */ function handlePrint(laudo) { - // Evito o bug: fechar viewer antes de abrir preview + // 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) { @@ -162,7 +164,7 @@ export default function LaudoManager() { try { const resp = await mockDeleteLaudo(toDelete.id); if (resp.ok || resp === true) { - // eu removo o laudo da lista local + // removo o laudo da lista local setLaudos(curr => curr.filter(l => l.id !== toDelete.id)); setShowConfirmDelete(false); setToDelete(null); @@ -177,25 +179,40 @@ export default function LaudoManager() { } } + /* 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; + }); + return (
Gerenciamento de Laudo
-
Visualização: Secretaria (Somente leitura)
+ {/* removi a linha "Visualização: Secretaria" conforme pedido */}
- + setQuery(e.target.value)} + style={{ width:"100%", padding:12, borderRadius:8, border:"1px solid #e6eef8" }} + />
- {laudos.length === 0 ? ( + {filteredLaudos.length === 0 ? (
Nenhum laudo encontrado.
) : (
- {laudos.map((l) => ( + {filteredLaudos.map((l) => (
{l.pedido}
@@ -312,7 +329,7 @@ export default function LaudoManager() {
)} - {/* Notificação simples: Sem permissão (exibe sem backdrop escuro) */} + {/* Notificação simples: Sem permissão (exibe sem backdrop escuro) - centralizada */} {showNoPermission && (
Sem permissão para editar
@@ -354,4 +371,4 @@ function computeAge(birth) { const dd = today.getDate(); if (mm < m || (mm === m && dd < d)) age--; return age; -} \ No newline at end of file +}