From 687ea108b615c09e2843947f41b85708c9833dde Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Thu, 30 Oct 2025 11:30:03 -0300 Subject: [PATCH] branchcommit --- src/PagesMedico/DoctorRelatorioManager.jsx | 101 ++++++---- src/PagesMedico/EditPageRelatorio.jsx | 13 +- src/PagesMedico/FormNovoRelatorio.jsx | 55 ++++-- src/PagesMedico/TiptapViewer.jsx | 2 +- src/PagesMedico/relatorio.jsx | 2 +- .../styleMedico/FormNovoRelatorio.css | 173 ++++++++++++++---- 6 files changed, 248 insertions(+), 98 deletions(-) diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx index cf5d2c4..c15db0c 100644 --- a/src/PagesMedico/DoctorRelatorioManager.jsx +++ b/src/PagesMedico/DoctorRelatorioManager.jsx @@ -19,45 +19,59 @@ const DoctorRelatorioManager = () => { const [showModal, setShowModal] = useState(false); const [index, setIndex] = useState(); - // busca lista de relatórios useEffect(() => { + let mounted = true; + const fetchReports = async () => { try { var myHeaders = new Headers(); myHeaders.append('apikey', API_KEY); - myHeaders.append('Authorization', authHeader); + if (authHeader) myHeaders.append('Authorization', authHeader); var 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(); - setRelatorios(data || []); + + 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) setRelatorios(unique); } catch (err) { console.error('Erro listar relatórios', err); - setRelatorios([]); + if (mounted) setRelatorios([]); } }; + fetchReports(); + + const refreshHandler = () => fetchReports(); + window.addEventListener('reports:refresh', refreshHandler); + + return () => { + mounted = false; + window.removeEventListener('reports:refresh', refreshHandler); + }; }, [authHeader]); - // depois que RelatoriosFiltrados mudar, busca pacientes e médicos correspondentes useEffect(() => { const fetchRelData = async () => { const pacientes = []; const medicos = []; for (let i = 0; i < RelatoriosFiltrados.length; i++) { const rel = RelatoriosFiltrados[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: tenta created_by ou requested_by id se existir try { const doctorId = rel.created_by || rel.requested_by || null; if (doctorId) { - // se created_by é id (uuid) usamos GetDoctorByID, senão se requested_by for nome, guardamos nome const docRes = await GetDoctorByID(doctorId, authHeader); medicos.push(Array.isArray(docRes) ? docRes[0] : docRes); } else { @@ -77,54 +91,73 @@ const DoctorRelatorioManager = () => { } }, [RelatoriosFiltrados, authHeader]); - const BaixarPDFdoRelatorio = (nome_paciente) => { - const elemento = document.getElementById("folhaA4"); - const opt = { margin: 0, filename: `relatorio_${nome_paciente || "paciente"}.pdf`, html2canvas: { scale: 2 }, jsPDF: { unit: "mm", format: "a4", orientation: "portrait" } }; + 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(); }; return (
{showModal && ( -
-
+
setShowModal(false)}> + {/* aqui: classe modal-dialog-square para ficar quadrado */} +
e.stopPropagation()}>
-
-
Relatório de {PacientesComRelatorios[index]?.full_name}
- +
+
Relatório de {PacientesComRelatorios[index]?.full_name}
+
+
-
-
-

Clinica Rise up

-

Dr - CRM/SP 123456

-

Avenida - (79) 9 4444-4444

+
+
+

Clinica Rise up

+

Dr - CRM/SP 123456

+

Avenida - (79) 9 4444-4444

-
-

Paciente: {PacientesComRelatorios[index]?.full_name}

-

Data de nascimento: {PacientesComRelatorios[index]?.birth_date}

-

Data do exame: {RelatoriosFiltrados[index]?.due_at || ''}

- {/* Exibe conteúdo salvo (content_html) */} -

Conteúdo do Relatório:

- +
+

Paciente: {PacientesComRelatorios[index]?.full_name}

+

Data de nascimento: {PacientesComRelatorios[index]?.birth_date || '—'}

+

Data do exame: {RelatoriosFiltrados[index]?.due_at || '—'}

+ +

Conteúdo do Relatório:

+
+ +
-
+

Dr {MedicosComRelatorios[index]?.full_name || RelatoriosFiltrados[index]?.requested_by}

-

Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}

+

Emitido em: {RelatoriosFiltrados[index]?.created_at || '—'}

-
- - + +
+ +
)} + {/* restante da página (lista) permanece igual */}

Lista de Relatórios

@@ -173,7 +206,7 @@ const DoctorRelatorioManager = () => { )) ) : ( - Nenhum paciente encontrado. + Nenhum paciente encontrado. )} diff --git a/src/PagesMedico/EditPageRelatorio.jsx b/src/PagesMedico/EditPageRelatorio.jsx index 741da1e..ea99a6e 100644 --- a/src/PagesMedico/EditPageRelatorio.jsx +++ b/src/PagesMedico/EditPageRelatorio.jsx @@ -1,4 +1,4 @@ -// EditPageRelatorio.jsx +// src/PagesMedico/EditPageRelatorio.jsx import React, { useEffect, useState } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import API_KEY from '../components/utils/apiKeys'; @@ -52,7 +52,7 @@ const EditPageRelatorio = () => { try { const myHeaders = new Headers(); myHeaders.append("apikey", API_KEY); - myHeaders.append("Authorization", authHeader); + if (authHeader) myHeaders.append("Authorization", authHeader); const requestOptions = { method: 'GET', headers: myHeaders, redirect: 'follow' }; // Pega relatório por id (supabase geralmente retorna array para ?id=eq.X) @@ -101,8 +101,11 @@ const EditPageRelatorio = () => { try { const myHeaders = new Headers(); myHeaders.append('apikey', API_KEY); - myHeaders.append('Authorization', authHeader); + if (authHeader) myHeaders.append('Authorization', authHeader); myHeaders.append('Content-Type', 'application/json'); + myHeaders.append('Accept', 'application/json'); + // pedir que o Supabase retorne a representação do registro atualizado (opcional) + myHeaders.append('Prefer', 'return=representation'); const body = JSON.stringify({ content_html: html }); @@ -114,7 +117,8 @@ const EditPageRelatorio = () => { }); if (!res.ok) { - const txt = await res.text(); + let txt; + try { txt = await res.text(); } catch (e) { txt = 'erro lendo resposta'; } console.error('Erro PATCH', res.status, txt); throw new Error('Erro na API'); } @@ -150,4 +154,3 @@ const EditPageRelatorio = () => { }; export default EditPageRelatorio; - diff --git a/src/PagesMedico/FormNovoRelatorio.jsx b/src/PagesMedico/FormNovoRelatorio.jsx index 44bff89..b212a57 100644 --- a/src/PagesMedico/FormNovoRelatorio.jsx +++ b/src/PagesMedico/FormNovoRelatorio.jsx @@ -1,3 +1,4 @@ +// src/PagesMedico/FormNovoRelatorio.jsx import React, { useEffect, useState, useRef } from 'react'; import { useNavigate } from 'react-router-dom'; import API_KEY from '../components/utils/apiKeys'; @@ -39,7 +40,7 @@ const FormNovoRelatorio = () => { const doctorRef = useRef(); useEffect(() => { - // carregar pacientes + // carregar pacientes e médicos let mounted = true; const loadPatients = async () => { setLoadingPatients(true); @@ -109,7 +110,7 @@ const FormNovoRelatorio = () => { patient_id: patient.id, patient_name: patient.full_name || '', patient_birth: patient.birth_date || '', - contentHtml: generateTemplate(patient.full_name || '', patient.birth_date || '', form.doctor_name) + contentHtml: generateTemplate(patient.full_name || '', patient.birth_date || '', prev.doctor_name) })); setPatientQuery(''); setShowPatientDropdown(false); @@ -120,7 +121,7 @@ const FormNovoRelatorio = () => { ...prev, doctor_id: doctor.id, doctor_name: doctor.full_name || '', - contentHtml: generateTemplate(form.patient_name, form.patient_birth, doctor.full_name || '') + contentHtml: generateTemplate(prev.patient_name, prev.patient_birth, doctor.full_name || '') })); setDoctorQuery(''); setShowDoctorDropdown(false); @@ -137,7 +138,7 @@ const FormNovoRelatorio = () => { const handleEditorChange = (html) => setForm(prev => ({ ...prev, contentHtml: html })); - // salvar novo relatório + // salvar novo relatório (agora com Prefer: return=representation e dispatch para refresh) const handleSubmit = async (e) => { e.preventDefault(); if (!form.patient_id) return alert('Selecione o paciente (clicando no item) antes de salvar.'); @@ -146,35 +147,51 @@ const FormNovoRelatorio = () => { try { const myHeaders = new Headers(); myHeaders.append('apikey', API_KEY); - myHeaders.append('Authorization', authHeader); + if (authHeader) myHeaders.append('Authorization', authHeader); myHeaders.append('Content-Type', 'application/json'); + myHeaders.append('Accept', 'application/json'); + // pedir que o Supabase retorne a representação do registro criado + myHeaders.append('Prefer', 'return=representation'); - const body = JSON.stringify({ + // monta o payload apenas com campos válidos + const payload = { patient_id: form.patient_id, content: form.contentHtml, content_html: form.contentHtml, - requested_by: form.doctor_name || '', - created_by: form.doctor_id || null, - status: 'draft' - }); + requested_by: form.doctor_name || '' + }; + // só inclui created_by se tiver um id válido + if (form.doctor_id) payload.created_by = form.doctor_id; + payload.status = 'draft'; const res = await fetch('https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports', { method: 'POST', headers: myHeaders, - body, + body: JSON.stringify(payload), }); if (!res.ok) { - const txt = await res.text(); + // tenta ler JSON, se não for JSON lê o texto + let txt; + try { + txt = await res.json(); + } catch (err) { + txt = await res.text(); + } console.error('Erro POST criar relatório:', res.status, txt); - // mostra mensagem mais útil - return alert(`Erro ao criar relatório (ver console). Status ${res.status}`); + return alert(`Erro ao criar relatório (ver console). Status ${res.status}\nMensagem: ${JSON.stringify(txt)}`); } + const created = await res.json(); + console.log('Relatório criado:', created); + + // dispara refresh global para a lista (DoctorRelatorioManager está escutando) + window.dispatchEvent(new Event('reports:refresh')); + alert('Relatório criado com sucesso!'); navigate('/medico/relatorios'); } catch (err) { - console.error('Erro salvar relatório:', err); + console.error('Erro salvar relatório (catch):', err); alert('Erro ao salvar relatório. Veja console.'); } }; @@ -185,7 +202,7 @@ const FormNovoRelatorio = () => {
-
+
{ onFocus={() => setShowPatientDropdown(true)} /> {showPatientDropdown && patientQuery && ( -
    +
      {filteredPatients.length > 0 ? filteredPatients.map(p => (
    • choosePatient(p)}> {p.full_name} {p.cpf ? `- ${p.cpf}` : ''} @@ -206,7 +223,7 @@ const FormNovoRelatorio = () => {
      Clique no paciente desejado para selecioná-lo e preencher o template.
-
+
{ onFocus={() => setShowDoctorDropdown(true)} /> {showDoctorDropdown && doctorQuery && ( -
    +
      {filteredDoctors.length > 0 ? filteredDoctors.map(d => (
    • chooseDoctor(d)}> {d.full_name} {d.crm ? `- CRM ${d.crm}` : ''} diff --git a/src/PagesMedico/TiptapViewer.jsx b/src/PagesMedico/TiptapViewer.jsx index cf21c49..4e7e7f3 100644 --- a/src/PagesMedico/TiptapViewer.jsx +++ b/src/PagesMedico/TiptapViewer.jsx @@ -12,4 +12,4 @@ const TiptapViewer = ({ htmlContent }) => { ); }; -export default TiptapViewer; \ No newline at end of file +export default TiptapViewer; diff --git a/src/PagesMedico/relatorio.jsx b/src/PagesMedico/relatorio.jsx index bd90bb5..c0d38c0 100644 --- a/src/PagesMedico/relatorio.jsx +++ b/src/PagesMedico/relatorio.jsx @@ -112,4 +112,4 @@ function Relatorio() { ); } -export default Relatorio; \ No newline at end of file +export default Relatorio; diff --git a/src/PagesMedico/styleMedico/FormNovoRelatorio.css b/src/PagesMedico/styleMedico/FormNovoRelatorio.css index a8fafa3..ea170e8 100644 --- a/src/PagesMedico/styleMedico/FormNovoRelatorio.css +++ b/src/PagesMedico/styleMedico/FormNovoRelatorio.css @@ -1,55 +1,152 @@ -#folhaA4 { - width: 210mm; - min-height: 207mm; - padding: 20mm; - margin: 10mm auto; - border: 1px solid #ccc; - background: white; - +/* src/PagesMedico/styleMedico/FormNovoRelatorio.css */ + +/* --- Modal centralizada e quadrada (ajustada para ser mais larga e sem quadrado branco no botão fechar) --- */ + +/* backdrop + center */ +.modal.modal-centered { + position: fixed; + inset: 0; + display: flex; + align-items: center; + justify-content: center; + background: rgba(10, 20, 30, 0.45); + z-index: 2000; + padding: 20px; } -#primeiraLinha{ - display: flex; - flex-direction: row; - gap: 20px; - margin-bottom: 20px; +/* dialog box — maior horizontalmente e altura automática para scroll interno */ +.modal-dialog.modal-dialog-square { + width: 880px; /* largura aumentada */ + max-width: 96vw; + height: auto; /* deixa altura automática (melhor para conteúdo longo) */ + max-height: 92vh; + margin: 0; + display: flex; + align-items: center; + justify-content: center; + padding: 0; } -input,textarea,label{ - font-size: 1.1rem; +/* caixa branca que contém o conteúdo - ocupa 100% da dialog */ +.modal-dialog.modal-dialog-square .modal-content { + width: 100%; + height: auto; + border-radius: 12px; + box-shadow: 0 12px 30px rgba(11,22,35,0.18); + overflow: hidden; + display: flex; + flex-direction: column; + background: #fff; } -textarea{ - width: 100%; - height: 100px; - +/* header */ +.custom-modal-header { + position: relative; + background: linear-gradient(90deg, #203B75 0%, #274A8A 100%); + color: #fff; + padding: 14px 18px; + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; } -.submitButton{ - display: flex; - margin-left: auto; - height:50% ; - padding: 8px 20px; - - font-size: medium; +.custom-modal-header .modal-title { + margin: 0; + font-size: 1.05rem; + font-weight: 700; } -.bi-download{ - font-size: 1.2rem; - margin-right: 5px; - font-weight: bold; +/* botão fechar no header — sem quadrado branco por trás */ +.modal-close-btn { + background: transparent !important; + border: none; + width: 40px; + height: 40px; + border-radius: 4px; + display: inline-flex; + align-items: center; + justify-content: center; + cursor: pointer; + box-shadow: none; + outline: none; + position: relative; + z-index: 5; } -#infoPaciente{ - margin-top: 50px; - margin-bottom: 40px; +.modal-close-btn::after { + content: '✕'; + color: #fff; + font-weight: 700; + font-size: 16px; } -#header-relatorio{ - text-align: center; - margin-bottom: 30px; +/* body - faz scroll interno se for longo */ +.modal-body { + padding: 18px; + overflow: auto; + flex: 1 1 auto; } -.info-paciente{ - font-weight: bold; +/* footer */ +.custom-modal-footer { + display: flex; + justify-content: flex-end; + gap: 12px; + padding: 14px 18px; + border-top: 1px solid #eee; + background: #fafafa; +} + +/* folhaA4 dentro da modal — adapta para caber */ +.folhaA4 { + width: 100%; + box-sizing: border-box; + background: transparent; + padding: 0; +} + +/* melhor espaçamento e leitura do conteúdo */ +#header-relatorio p { + color: #374151; + margin: 6px 0; + text-align: center; +} + +#infoPaciente p { + margin: 10px 0; + color: #3d4650; +} + +/* tornar o viewer responsivo */ +.tiptap-viewer-wrapper { + border: 1px dashed #e7e7e7; + padding: 12px; + margin-top: 10px; + background: #fff; +} + +/* barra de scroll customizada (opcional) */ +.modal-body::-webkit-scrollbar { + width: 10px; +} + +.modal-body::-webkit-scrollbar-thumb { + background: rgba(100,100,100,0.18); + border-radius: 8px; +} + +.modal-body::-webkit-scrollbar-track { + background: rgba(0,0,0,0.02); +} + +/* responsividade para telas pequenas: mantém centralizado, ajusta proporção */ +@media (max-width: 680px) { + .modal-dialog.modal-dialog-square { + width: 92vw; + height: 86vh; + } + .modal-close-btn { width: 36px; height: 36px; } + .custom-modal-footer { padding: 10px; } + .modal-body { padding: 12px; } }