From efa723dbe3235f4e5b72ee8c175d6094112e592a Mon Sep 17 00:00:00 2001 From: Caio Miguel Lima Nunes Date: Tue, 7 Oct 2025 22:22:18 -0300 Subject: [PATCH 1/6] implementacao do sistema de logout com modal e API --- src/components/Sidebar.jsx | 373 +++++++++++++++++++++++++------------ 1 file changed, 253 insertions(+), 120 deletions(-) diff --git a/src/components/Sidebar.jsx b/src/components/Sidebar.jsx index e1cc85ad..b211975e 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Sidebar.jsx @@ -1,138 +1,271 @@ import React, { useState } from "react"; -import { Link } from "react-router-dom"; -import menuItems from "../data/sidebar-items-medico.json"; // Use "sidebar-items-secretaria.json" para secretaria e "sidebar-items-adm.json" para ADM -import TrocardePerfis from "./TrocardePerfis"; +import { Link, useNavigate } from "react-router-dom"; +import menuItems from "../data/sidebar-items-medico.json"; +import TrocardePerfis from "./TrocardePerfis"; +function Sidebar({ menuItems }) { + const [isActive, setIsActive] = useState(true); + const [openSubmenu, setOpenSubmenu] = useState(null); + const [showLogoutModal, setShowLogoutModal] = useState(false); + const navigate = useNavigate(); -// 1. Recebe 'menuItems' e 'onLogout' como props -function Sidebar({ menuItems, onLogout }) { - const [isActive, setIsActive] = useState(true); - const [openSubmenu, setOpenSubmenu] = useState(null); + const toggleSidebar = () => { + setIsActive(!isActive); + }; - const toggleSidebar = () => { - setIsActive(!isActive); - }; + const handleSubmenuClick = (submenuName) => { + setOpenSubmenu(openSubmenu === submenuName ? null : submenuName); + }; - const handleSubmenuClick = (submenuName) => { - setOpenSubmenu(openSubmenu === submenuName ? null : submenuName); - }; + const handleLogoutClick = () => { + setShowLogoutModal(true); + }; - const renderLink = (item) => { - // Links internos (rotas do React Router) - if (item.url && item.url.startsWith("/")) { - return ( - - {item.icon && } - {item.name} - - ); - } + const handleLogoutConfirm = async () => { + try { + const token = localStorage.getItem('token') || + localStorage.getItem('authToken') || + localStorage.getItem('userToken') || + localStorage.getItem('access_token') || + sessionStorage.getItem('token') || + sessionStorage.getItem('authToken'); - // Links externos - return ( - - {item.icon && } - {item.name} - - ); - }; + if (token) { + const response = await fetch('https://mock.apidog.com/m1/1053378-0-default/auth/v1/logout', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${token}` + } + }); - return ( - + + ); } export default Sidebar; \ No newline at end of file From 92c84e7f2dc18f15eddc3991efa65680a785b233 Mon Sep 17 00:00:00 2001 From: jp-lima Date: Wed, 8 Oct 2025 16:02:51 -0300 Subject: [PATCH 2/6] =?UTF-8?q?Cria=C3=A7=C3=A3o=20da=20p=C3=A1gina=20de?= =?UTF-8?q?=20relat=C3=B3rios=20e=20conex=C3=A3o=20com=20endpoint=20de=20g?= =?UTF-8?q?et=20e=20post?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 179 +++++++++++++++ package.json | 1 + src/PagesMedico/DoctorRelatorioManager.jsx | 164 ++++++++++++++ src/PagesMedico/FormNovoRelatorio.jsx | 208 ++++++++++++++++++ .../styleMedico/FormNovoRelatorio.css | 55 +++++ .../utils/Functions-Endpoints/Patient.js | 34 ++- src/data/sidebar-items-medico.json | 6 - src/pages/Details.jsx | 4 +- src/pages/EditPage.jsx | 4 +- src/pages/Inicio.jsx | 27 --- src/perfis/Perfil_medico/PerfilMedico.jsx | 11 +- .../perfil_financeiro/PerfilFinanceiro.jsx | 2 - 12 files changed, 649 insertions(+), 46 deletions(-) create mode 100644 src/PagesMedico/DoctorRelatorioManager.jsx create mode 100644 src/PagesMedico/FormNovoRelatorio.jsx create mode 100644 src/PagesMedico/styleMedico/FormNovoRelatorio.css diff --git a/package-lock.json b/package-lock.json index 9926bf5b..068a8fa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "bootstrap-icons": "^1.13.1", "dayjs": "^1.11.18", "flatpickr": "^4.6.13", + "html2pdf.js": "^0.12.1", "lucide-react": "^0.543.0", "perfect-scrollbar": "^1.5.6", "powershell": "^2.3.3", @@ -18118,6 +18119,12 @@ "@types/node": "*" } }, + "node_modules/@types/pako": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.4.tgz", + "integrity": "sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==", + "license": "MIT" + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -18163,6 +18170,13 @@ "integrity": "sha512-J5FBQt/pM2inLzg4hEWmzQx/8h8D0CiDxaG3vyp9rKrQRSDgBlhjdP5jQGgosEajXPSQouXGHOmVdgo7QmJuOg==", "license": "BSD-3-Clause" }, + "node_modules/@types/raf": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/@types/raf/-/raf-3.4.3.tgz", + "integrity": "sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==", + "license": "MIT", + "optional": true + }, "node_modules/@types/range-parser": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", @@ -19593,6 +19607,15 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", @@ -19965,6 +19988,26 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvg": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/canvg/-/canvg-3.0.11.tgz", + "integrity": "sha512-5ON+q7jCTgMp9cjpu4Jo6XbvfYwSB2Ow3kzHKfIyJfaCAOHLbdKPQqGKgfED/R5B+3TFFfe8pegYA+b423SRyA==", + "license": "MIT", + "optional": true, + "dependencies": { + "@babel/runtime": "^7.12.5", + "@types/raf": "^3.4.0", + "core-js": "^3.8.3", + "raf": "^3.4.1", + "regenerator-runtime": "^0.13.7", + "rgbcolor": "^1.0.1", + "stackblur-canvas": "^2.0.0", + "svg-pathdata": "^6.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, "node_modules/case-sensitive-paths-webpack-plugin": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz", @@ -21088,6 +21131,15 @@ "postcss": "^8.4" } }, + "node_modules/css-line-break": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/css-line-break/-/css-line-break-2.1.0.tgz", + "integrity": "sha512-FHcKFCZcAha3LwfVBhCQbW2nCNbkZXn7KVUJcsT5/P8YmfsVja0FMPJr0B903j/E69HUphKiV9iQArX8SDYA4w==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/css-loader": { "version": "6.11.0", "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-6.11.0.tgz", @@ -22014,6 +22066,16 @@ "url": "https://github.com/fb55/domhandler?sponsor=1" } }, + "node_modules/dompurify": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", + "license": "(MPL-2.0 OR Apache-2.0)", + "optional": true, + "optionalDependencies": { + "@types/trusted-types": "^2.0.7" + } + }, "node_modules/domutils": { "version": "2.8.0", "resolved": "https://registry.npmjs.org/domutils/-/domutils-2.8.0.tgz", @@ -23229,6 +23291,17 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "license": "MIT" }, + "node_modules/fast-png": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/fast-png/-/fast-png-6.4.0.tgz", + "integrity": "sha512-kAqZq1TlgBjZcLr5mcN6NP5Rv4V2f22z00c3g8vRrwkcqjerx7BEhPbOnWCPqaHUl2XWQBJQvOT/FQhdMT7X/Q==", + "license": "MIT", + "dependencies": { + "@types/pako": "^2.0.3", + "iobuffer": "^5.3.2", + "pako": "^2.1.0" + } + }, "node_modules/fast-uri": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", @@ -23275,6 +23348,12 @@ "bser": "2.1.1" } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", @@ -24525,6 +24604,29 @@ } } }, + "node_modules/html2canvas": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/html2canvas/-/html2canvas-1.4.1.tgz", + "integrity": "sha512-fPU6BHNpsyIhr8yyMpTLLxAbkaK8ArIBcmZIRiBLiDhjeqvXolaEmDGmELFuX9I4xDcaKKcJl+TKZLqruBbmWA==", + "license": "MIT", + "dependencies": { + "css-line-break": "^2.1.0", + "text-segmentation": "^1.0.3" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/html2pdf.js": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/html2pdf.js/-/html2pdf.js-0.12.1.tgz", + "integrity": "sha512-3rBWQ96H5oOU9jtoz3MnE/epGi27ig9h8aonBk4JTpvUERM3lMRxhIRckhJZEi4wE0YfRINoYOIDY0hLY0CHgQ==", + "license": "MIT", + "dependencies": { + "html2canvas": "^1.0.0", + "jspdf": "^3.0.0" + } + }, "node_modules/htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -24849,6 +24951,12 @@ "loose-envify": "^1.0.0" } }, + "node_modules/iobuffer": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/iobuffer/-/iobuffer-5.4.0.tgz", + "integrity": "sha512-DRebOWuqDvxunfkNJAlc3IzWIPD5xVxwUNbHr7xKB8E6aLJxIPfNX3CoMJghcFjpv6RWQsrcJbghtEwSPoJqMA==", + "license": "MIT" + }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -26609,6 +26717,23 @@ "node": ">=0.10.0" } }, + "node_modules/jspdf": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/jspdf/-/jspdf-3.0.3.tgz", + "integrity": "sha512-eURjAyz5iX1H8BOYAfzvdPfIKK53V7mCpBTe7Kb16PaM8JSXEcUQNBQaiWMI8wY5RvNOPj4GccMjTlfwRBd+oQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.9", + "fast-png": "^6.2.0", + "fflate": "^0.8.1" + }, + "optionalDependencies": { + "canvg": "^3.0.11", + "core-js": "^3.6.0", + "dompurify": "^3.2.4", + "html2canvas": "^1.0.0-rc.5" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -28473,6 +28598,12 @@ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==", "license": "BlueOak-1.0.0" }, + "node_modules/pako": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pako/-/pako-2.1.0.tgz", + "integrity": "sha512-w+eufiZ1WuJYgPXbV/PO3NCMEc3xqylkKHzp8bxp1uW4qaSNQUkwmLLEc3kKsfz8lpV1F8Ht3U1Cm+9Srog2ug==", + "license": "(MIT AND Zlib)" + }, "node_modules/param-case": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", @@ -31496,6 +31627,16 @@ "node": ">=0.10.0" } }, + "node_modules/rgbcolor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/rgbcolor/-/rgbcolor-1.0.1.tgz", + "integrity": "sha512-9aZLIrhRaD97sgVhtJOW6ckOEh6/GnvQtdVNfdZ6s67+3/XwLS9lBcQYzEEhYVeUowN7pRzMLsyGhK2i/xvWbw==", + "license": "MIT OR SEE LICENSE IN FEEL-FREE.md", + "optional": true, + "engines": { + "node": ">= 0.8.15" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -32375,6 +32516,16 @@ "node": ">=8" } }, + "node_modules/stackblur-canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/stackblur-canvas/-/stackblur-canvas-2.7.0.tgz", + "integrity": "sha512-yf7OENo23AGJhBriGx0QivY5JP6Y1HbrrDI6WLt6C5auYZXlQrheoY8hD4ibekFKz1HOfE48Ww8kMWMnJD/zcQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.1.14" + } + }, "node_modules/stackframe": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/stackframe/-/stackframe-1.3.4.tgz", @@ -32934,6 +33085,16 @@ "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", "license": "MIT" }, + "node_modules/svg-pathdata": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/svg-pathdata/-/svg-pathdata-6.0.3.tgz", + "integrity": "sha512-qsjeeq5YjBZ5eMdFuUa4ZosMLxgr5RZ+F+Y1OrDhuOCEInRMA3x74XdBtggJcj9kOeInz0WE+LgCPDkZFlBYJw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/svgo": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz", @@ -33255,6 +33416,15 @@ "node": ">=8" } }, + "node_modules/text-segmentation": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/text-segmentation/-/text-segmentation-1.0.3.tgz", + "integrity": "sha512-iOiPUo/BGnZ6+54OsWxZidGCsdU8YbE4PSpdPinp7DeMtUJNJBoJ/ouUSTJjHkh1KntHaltHl/gDs2FC4i5+Nw==", + "license": "MIT", + "dependencies": { + "utrie": "^1.0.2" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -33965,6 +34135,15 @@ "node": ">= 0.4.0" } }, + "node_modules/utrie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/utrie/-/utrie-1.0.2.tgz", + "integrity": "sha512-1MLa5ouZiOmQzUbjbu9VmjLzn1QLXBhwpUa7kdLUQK+KQ5KA9I1vk5U4YHe/X2Ch7PYnJfWuWT+VbuxbGwljhw==", + "license": "MIT", + "dependencies": { + "base64-arraybuffer": "^1.0.2" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", diff --git a/package.json b/package.json index 450cc45f..8f30168a 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bootstrap-icons": "^1.13.1", "dayjs": "^1.11.18", "flatpickr": "^4.6.13", + "html2pdf.js": "^0.12.1", "lucide-react": "^0.543.0", "perfect-scrollbar": "^1.5.6", "powershell": "^2.3.3", diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx new file mode 100644 index 00000000..334de6b6 --- /dev/null +++ b/src/PagesMedico/DoctorRelatorioManager.jsx @@ -0,0 +1,164 @@ +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'; +const DoctorRelatorioManager = () => { + const {getAuthorizationHeader} = useAuth(); + let authHeader = getAuthorizationHeader() + const [RelatoriosFiltrados, setRelatorios] = useState([]) + const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]) + + useEffect( () => { + let pacientesDosRelatorios =[] + + + const ListarPacientes = async () => { + for (let i = 0; i < RelatoriosFiltrados.length; i++) { + let relatorio = RelatoriosFiltrados[i]; + let paciente_id = relatorio.patient_id; + const paciente = await GetPatientByID(paciente_id, authHeader); + console.log(paciente) + if (paciente.length > 0) { + pacientesDosRelatorios.push(paciente[0]); + } + + } + setPacientesComRelatorios(pacientesDosRelatorios); + + } + + ListarPacientes() + console.log(PacientesComRelatorios, 'aqui') + + }, [RelatoriosFiltrados]); + + useEffect(() => { + var myHeaders = new Headers(); +myHeaders.append("apikey", API_KEY); +myHeaders.append("Authorization", authHeader); + +var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' +}; + +fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&status", requestOptions) + .then(response => response.json()) + .then(data => { setRelatorios(data); console.log(data) }) + .catch(error => console.log('error', error)); + }, []) + + + + return ( +
+
+

Lista de Relatórios

+
+
+
+
+
+
+

Relatórios Cadastrados

+ + + +
+ +
+
+
+ {" "} + Filtros +
+ +
+ +
+
+ +
+ + + + + + + + + + + + + + {RelatoriosFiltrados.length > 0 ? ( + RelatoriosFiltrados.map((relatorio, index) => ( + + + + + + + + + + + )) + ) : ( + + + + )} + +
PacienteCPFExameAções
{relatorio.order_number}{PacientesComRelatorios[index]?.full_name}{PacientesComRelatorios[index]?.cpf}{relatorio.exam}{relatorio.create_at} +
+ +
+
+ Nenhum paciente encontrado. +
+
+
+
+
+
+
+ +
+ + ) +} + +export default DoctorRelatorioManager \ No newline at end of file diff --git a/src/PagesMedico/FormNovoRelatorio.jsx b/src/PagesMedico/FormNovoRelatorio.jsx new file mode 100644 index 00000000..29018f79 --- /dev/null +++ b/src/PagesMedico/FormNovoRelatorio.jsx @@ -0,0 +1,208 @@ +import React from 'react' +import '../PagesMedico/styleMedico/FormNovoRelatorio.css' +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useAuth } from '../components/utils/AuthProvider' +import { GetPatientByCPF } from '../components/utils/Functions-Endpoints/Patient' +import { FormatCPF } from '../components/utils/Formatar/Format' +import API_KEY from '../components/utils/apiKeys' +import html2pdf from "html2pdf.js"; + + +const FormNovoRelatorio = () => { + const {getAuthorizationHeader} = useAuth() + let authHeader = getAuthorizationHeader() + const navigate= useNavigate() + const [DictInfo, setDictInfo] = useState({}) + const [showModal, setShowModal] = useState(false) + + const handleChange = (e) => { + const { name, value } = e.target; + console.log(name, value) + + + if(name === 'paciente_cpf') { + const formattedCPF = FormatCPF(value); + setDictInfo((prev) => ({ ...prev, [name]: formattedCPF })); + + const fetchPatient = async () => { + const patientData = await GetPatientByCPF(formattedCPF, authHeader); + if (patientData) { + setDictInfo((prev) => ({ + ...prev, + paciente_cpf:value, + paciente_nome: patientData.full_name, + paciente_id: patientData.id + })); + } + + }; + if(formattedCPF.length === 14){ + fetchPatient(); + } + }else{ + setDictInfo((prev) => ({ ...prev, [name]: value })); + } + } + + const handleSubmit = (e) => { + e.preventDefault(); + console.log(DictInfo) + setShowModal(true) + + var myHeaders = new Headers(); +myHeaders.append("apikey", API_KEY); +myHeaders.append("Authorization", authHeader); +myHeaders.append("Content-Type", "application/json"); + +var raw = JSON.stringify({ + "patient_id": DictInfo.paciente_id, + + "exam": DictInfo.exam, + "diagnosis": DictInfo.diagnosis, + "conclusion": DictInfo.conclusao, + "status": "draft", + "requested_by": DictInfo.requested_by, + + "hide_date": false, + "hide_signature": false, +}); + +var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' +}; + +fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports", requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + } + + return ( +
+ {showModal &&( +
+
+
+
+
Relatório criado com sucesso
+ +
+
+

Você também pode baixa-lo agora em pdf

+
+
+ + + +
+
+
+
+ )} +

Criar novo relatório

+ +
+ +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+ + +
+
+ + + +
+
+ +

Modelo do relatório

+
+ +
+

Clinica Rise up

+

Dr {DictInfo.requested_by} - CRM/SP 123456

+

Avenida - (79) 9 4444-4444

+
+ +
+

Paciente: {DictInfo?.paciente_nome}

+

Data de nascimento:

+ +

Data do exame: {DictInfo.data_exame}

+ +

Exame: {DictInfo.exame}

+ +

Diagnostico: {DictInfo.diagnostico}

+ +

Conclusão: {DictInfo.conclusao}

+ +
+ +
+

Dr {DictInfo.requested_by}

+

Emitido em: 0

+
+ +
+ +
+ ) +} + +export default FormNovoRelatorio \ No newline at end of file diff --git a/src/PagesMedico/styleMedico/FormNovoRelatorio.css b/src/PagesMedico/styleMedico/FormNovoRelatorio.css new file mode 100644 index 00000000..7d8a5ed9 --- /dev/null +++ b/src/PagesMedico/styleMedico/FormNovoRelatorio.css @@ -0,0 +1,55 @@ +#folhaA4 { + width: 210mm; + min-height: 207mm; + padding: 20mm; + margin: 10mm auto; + border: 1px solid #ccc; + background: white; + +} + +#primeiraLinha{ + display: flex; + flex-direction: row; + gap: 20px; + margin-bottom: 20px; +} + +input,textarea,label{ + font-size: 1.1rem; +} + +textarea{ + width: 100%; + height: 100px; + +} + +.submitButton{ + display: flex; + margin-left: auto; + height:50% ; + padding: 8px 20px; + + font-size: medium; +} + +.bi-download{ + font-size: 1.2rem; + margin-right: 5px; + font-weight: bold; +} + +#infoPaciente{ + margin-top: 50px; + margin-bottom: 40px; +} + +#header-relatorio{ + text-align: center; + margin-bottom: 30px; +} + +.info-paciente{ + font-weight: bold; +} \ No newline at end of file diff --git a/src/components/utils/Functions-Endpoints/Patient.js b/src/components/utils/Functions-Endpoints/Patient.js index dc943502..ad848545 100644 --- a/src/components/utils/Functions-Endpoints/Patient.js +++ b/src/components/utils/Functions-Endpoints/Patient.js @@ -2,7 +2,7 @@ import API_KEY from "../apiKeys"; -const GetByID = async (ID,authHeader) => { +const GetPatientByID = async (ID,authHeader) => { console.log(authHeader, 'mostrando autorização dentro da função') @@ -22,4 +22,34 @@ const DictPaciente = await result.json() return DictPaciente } -export {GetByID} \ No newline at end of file +const GetAllPatients = async (authHeader) => { + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + const result = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions) + const DictPacientes = await result.json() + return DictPacientes + } + + const GetPatientByCPF = async (cpf, authHeader) => { + const Pacientes = await GetAllPatients(authHeader) + + + for (let i = 0; i < Pacientes.length; i++) { + if (Pacientes[i].cpf === cpf) { + console.log('Paciente encontrado:', Pacientes[i]); + return Pacientes[i]; + } + else{console.log("nada encontrado")} + } + + } + +export {GetPatientByID, GetAllPatients, GetPatientByCPF} \ No newline at end of file diff --git a/src/data/sidebar-items-medico.json b/src/data/sidebar-items-medico.json index 0d2a4393..e31bf12f 100644 --- a/src/data/sidebar-items-medico.json +++ b/src/data/sidebar-items-medico.json @@ -9,12 +9,6 @@ "icon": "calendar-plus-fill", "url": "/medico/prontuario" }, - - { - "name": "Laudos", - "icon": "table", - "url": "/medico/laudo" - }, { "name": "Seus Agendamentos", "icon": "calendar", diff --git a/src/pages/Details.jsx b/src/pages/Details.jsx index 70cba433..4edc8780 100644 --- a/src/pages/Details.jsx +++ b/src/pages/Details.jsx @@ -2,7 +2,7 @@ import React, { useEffect, useState } from "react"; import avatarPlaceholder from '../assets/images/avatar_placeholder.png'; import { useParams, useNavigate, useLocation, Navigate } from "react-router-dom"; import API_KEY from "../components/utils/apiKeys"; -import {GetByID} from "../components/utils/Functions-Endpoints/Patient" +import {GetPatientByID} from "../components/utils/Functions-Endpoints/Patient" import { Link } from "react-router-dom"; import { useAuth } from "../components/utils/AuthProvider"; @@ -27,7 +27,7 @@ const Details = () => { console.log(patientID, 'teu id') const authHeader = getAuthorizationHeader() - GetByID(patientID, authHeader) + GetPatientByID(patientID, authHeader) .then((data) => { console.log(data, "paciente vindo da API"); setPaciente(data[0]); // supabase retorna array diff --git a/src/pages/EditPage.jsx b/src/pages/EditPage.jsx index 91091f3f..306a9703 100644 --- a/src/pages/EditPage.jsx +++ b/src/pages/EditPage.jsx @@ -3,7 +3,7 @@ import React from 'react' import PatientForm from '../components/patients/PatientForm' import {useEffect, useState} from 'react' -import { GetByID } from '../components/utils/Functions-Endpoints/Patient' +import { GetPatientByID } from '../components/utils/Functions-Endpoints/Patient' import API_KEY from '../components/utils/apiKeys' import {useNavigate, useParams } from 'react-router-dom' import { useAuth } from '../components/utils/AuthProvider' @@ -19,7 +19,7 @@ const PatientID = Parametros.id useEffect(() => { const authHeader = getAuthorizationHeader() - GetByID(PatientID, authHeader) + GetPatientByID(PatientID, authHeader) .then((data) => { console.log(data[0], "paciente vindo da API"); setPatientPUT(data[0]); // supabase retorna array diff --git a/src/pages/Inicio.jsx b/src/pages/Inicio.jsx index 8b6f5a3d..7bdbe786 100644 --- a/src/pages/Inicio.jsx +++ b/src/pages/Inicio.jsx @@ -11,33 +11,6 @@ function Inicio() { const [pacientes, setPacientes] = useState([]); const [agendamentos, setAgendamentos] = useState([]); - useEffect(() => { - - /*const fetchPacientes = async () => { - try { - const res = await fetch("https://mock.apidog.com/m1/1053378-0-default/pacientes"); - const data = await res.json(); - console.log(data) - //setPacientes(data.data); - } catch (error) { - console.error("Erro ao buscar pacientes:", error); - } - }; - - const fetchAgendamentos = async () => { - return; // <===serve para que nao cause erro - // try { - // const res = await fetch(); - // const data = await res.json(); - // setAgendamentos(data.data); - // } catch (error) { - // console.error("Erro ao buscar agendamentos:", error); - // } - }; - - fetchPacientes(); - fetchAgendamentos();*/ - }, []); const totalPacientes = pacientes.length; const novosEsseMes = pacientes.filter(p => p.createdAt && new Date(p.createdAt).getMonth() === new Date().getMonth()).length; diff --git a/src/perfis/Perfil_medico/PerfilMedico.jsx b/src/perfis/Perfil_medico/PerfilMedico.jsx index 77983104..f34b0b7f 100644 --- a/src/perfis/Perfil_medico/PerfilMedico.jsx +++ b/src/perfis/Perfil_medico/PerfilMedico.jsx @@ -1,12 +1,13 @@ import { Routes, Route } from "react-router-dom"; import Sidebar from "../../components/Sidebar"; -import LaudoManager from "../../pages/LaudoManager"; +import DoctorRelatorioManager from "../../PagesMedico/DoctorRelatorioManager"; import Prontuario from "../../PagesMedico/prontuario"; import Relatorio from "../../PagesMedico/relatorio"; import Agendamento from "../../PagesMedico/Agendamento"; import Chat from "../../PagesMedico/Chat"; import DoctorItems from "../../data/sidebar-items-medico.json"; +import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio"; // ...existing code... function PerfilMedico() { @@ -16,12 +17,12 @@ function PerfilMedico() {
- } /> - } /> + } /> + } /> } /> - } /> + } /> } /> - } /> {/* <-- nova rota */} + } />
diff --git a/src/perfis/perfil_financeiro/PerfilFinanceiro.jsx b/src/perfis/perfil_financeiro/PerfilFinanceiro.jsx index 36bb2f16..c5cf7957 100644 --- a/src/perfis/perfil_financeiro/PerfilFinanceiro.jsx +++ b/src/perfis/perfil_financeiro/PerfilFinanceiro.jsx @@ -12,9 +12,7 @@ function PerfilFinanceiro({ onLogout }) {
}/> - }/> - Página não encontrada} />
From 8deccda6b838f93f51aeb15cc12955c54f0c965c Mon Sep 17 00:00:00 2001 From: Caio Miguel Lima Nunes Date: Wed, 8 Oct 2025 22:13:39 -0300 Subject: [PATCH 3/6] Atualiza cadastros (pacientes e medicos) --- src/components/doctors/DoctorForm.jsx | 370 ++++++++++++++-------- src/components/patients/PatientForm.jsx | 387 +++++++++++++++--------- src/pages/DoctorCadastroManager.jsx | 339 +++++++++++++++++---- src/pages/PatientCadastroManager.jsx | 280 +++++++++++++---- 4 files changed, 998 insertions(+), 378 deletions(-) diff --git a/src/components/doctors/DoctorForm.jsx b/src/components/doctors/DoctorForm.jsx index 8b4ceb8a..45ec0bf2 100644 --- a/src/components/doctors/DoctorForm.jsx +++ b/src/components/doctors/DoctorForm.jsx @@ -1,11 +1,10 @@ -import React, { useState } from 'react'; -import { Link,useNavigate, useLocation } from 'react-router-dom'; +import React, { useState, useRef } from 'react'; +import { Link, useNavigate, useLocation } from 'react-router-dom'; -function DoctorForm({ onSave, onCancel, formData, setFormData }) { +function DoctorForm({ onSave, onCancel, formData, setFormData, isLoading }) { const navigate = useNavigate(); const location = useLocation(); - // Funções para formatar telefone e CPF const FormatTelefones = (valor) => { const digits = String(valor).replace(/\D/g, '').slice(0, 11); return digits @@ -23,20 +22,53 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { .replace(/(\d{3})(\d{1,2})$/, '$1-$2'); }; + + const validarCPF = (cpf) => { + const cpfLimpo = cpf.replace(/\D/g, ''); + + if (cpfLimpo.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpfLimpo)) return false; + + + let soma = 0; + for (let i = 0; i < 9; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); + } + let resto = 11 - (soma % 11); + let digito1 = resto === 10 || resto === 11 ? 0 : resto; + + + soma = 0; + for (let i = 0; i < 10; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (11 - i); + } + resto = 11 - (soma % 11); + let digito2 = resto === 10 || resto === 11 ? 0 : resto; + + + return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + }; + const [avatarUrl, setAvatarUrl] = useState(null); + const [showRequiredModal, setShowRequiredModal] = useState(false); + const [emptyFields, setEmptyFields] = useState([]); + const [cpfError, setCpfError] = useState(''); + + const nomeRef = useRef(null); + const cpfRef = useRef(null); + const emailRef = useRef(null); + const telefoneRef = useRef(null); + const crmUfRef = useRef(null); + const crmRef = useRef(null); const [collapsedSections, setCollapsedSections] = useState({ dadosPessoais: true, - infoMedicas: false, - infoConvenio: false, - endereco: false, contato: false, + endereco: false, }); - const [showModal, setShowModal] = useState(false); - const [showSuccessModal, setShowSuccessModal] = useState(false); - const [errorModalMsg, setErrorModalMsg] = useState(''); - const handleToggleCollapse = (section) => { setCollapsedSections(prevState => ({ ...prevState, @@ -47,6 +79,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { const handleChange = (e) => { const { name, value, type, checked, files } = e.target; + + if (value && emptyFields.includes(name)) { + setEmptyFields(prev => prev.filter(field => field !== name)); + } + + + if (name === 'cpf' && cpfError) { + setCpfError(''); + } + if (type === 'checkbox') { setFormData(prev => ({ ...prev, [name]: checked })); } else if (type === 'file') { @@ -65,6 +107,16 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { } else if (name.includes('cpf')) { let cpfFormatado = FormatCPF(value); setFormData(prev => ({ ...prev, [name]: cpfFormatado })); + + + const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + if (cpfLimpo.length === 11) { + if (!validarCPF(cpfFormatado)) { + setCpfError('CPF inválido'); + } else { + setCpfError(''); + } + } } else if (name.includes('phone')) { let telefoneFormatado = FormatTelefones(value); setFormData(prev => ({ ...prev, [name]: telefoneFormatado })); @@ -88,48 +140,133 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { state: data.uf || '' })); } else { - setErrorModalMsg('CEP não encontrado!'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cep']); } } catch (error) { - setErrorModalMsg('Erro ao buscar o CEP.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cep']); } } }; + + const scrollToEmptyField = (fieldName) => { + let fieldRef = null; + + switch (fieldName) { + case 'full_name': + fieldRef = nomeRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'cpf': + fieldRef = cpfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'email': + fieldRef = emailRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'phone_mobile': + fieldRef = telefoneRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'crm_uf': + fieldRef = crmUfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'crm': + fieldRef = crmRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + default: + return; + } + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + fieldRef.current.focus(); + + + fieldRef.current.style.border = '2px solid #dc3545'; + fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.style.border = ''; + fieldRef.current.style.boxShadow = ''; + } + }, 3000); + } + }, 300); + }; + const handleSubmit = async () => { - if (!formData.full_name || !formData.cpf || !formData.email || !formData.phone_mobile || !formData.crm_uf || !formData.crm) { - setErrorModalMsg('Por favor, preencha todos os campos obrigatórios.'); - setShowModal(true); + + const missingFields = []; + if (!formData.full_name) missingFields.push('full_name'); + if (!formData.cpf) missingFields.push('cpf'); + if (!formData.email) missingFields.push('email'); + if (!formData.phone_mobile) missingFields.push('phone_mobile'); + if (!formData.crm_uf) missingFields.push('crm_uf'); + if (!formData.crm) missingFields.push('crm'); + + if (missingFields.length > 0) { + setEmptyFields(missingFields); + setShowRequiredModal(true); + + + setTimeout(() => { + if (missingFields.length > 0) { + scrollToEmptyField(missingFields[0]); + } + }, 500); return; } + const cpfLimpo = formData.cpf.replace(/\D/g, ''); if (cpfLimpo.length !== 11) { - setErrorModalMsg('CPF inválido. Por favor, verifique o número digitado.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF deve ter 11 dígitos'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } + + if (!validarCPF(formData.cpf)) { + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF inválido'); + setTimeout(() => scrollToEmptyField('cpf'), 500); + return; + } + + try { await onSave({ ...formData }); - setShowSuccessModal(true); + } catch (error) { - setErrorModalMsg('médico salvo com sucesso'); - setShowModal(true); + + throw error; } }; - const handleCloseSuccessModal = () => { - setShowSuccessModal(false); - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/medicos`); + const handleModalClose = () => { + setShowRequiredModal(false); }; return ( <> - {showModal && ( + + {showRequiredModal && (
Atenção
-

- {errorModalMsg} +

+ {cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'}

+
+ {cpfError ? ( +

{cpfError}

+ ) : ( + <> + {!formData.full_name &&

- Nome

} + {!formData.cpf &&

- CPF

} + {!formData.email &&

- Email

} + {!formData.phone_mobile &&

- Telefone

} + {!formData.crm_uf &&

- Estado do CRM

} + {!formData.crm &&

- CRM

} + + )} +
-
- - - )} - - {showSuccessModal && ( -
-
-
-
Sucesso
- -
- -
-

- Médico salvo com sucesso! -

-
- -
-
@@ -360,12 +434,30 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- + + {cpfError && ( +
+ {cpfError} +
+ )}
- @@ -398,7 +490,14 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- +
@@ -437,11 +536,25 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) {
- +
- +
@@ -498,11 +611,20 @@ function DoctorForm({ onSave, onCancel, formData, setFormData }) { - + + + +
diff --git a/src/components/patients/PatientForm.jsx b/src/components/patients/PatientForm.jsx index cd1eb2f4..60cf51b5 100644 --- a/src/components/patients/PatientForm.jsx +++ b/src/components/patients/PatientForm.jsx @@ -1,16 +1,12 @@ -import React, { useState, useEffect } from 'react'; -import { Link,useNavigate, useLocation } from 'react-router-dom'; +import React, { useState, useEffect, useRef } from 'react'; +import { Link } from 'react-router-dom'; import { FormatTelefones, FormatPeso, FormatCPF } from '../utils/Formatar/Format'; -function PatientForm({ onSave, formData, setFormData }) { - - - const [errorModalMsg, setErrorModalMsg] = useState(""); - const [showModal, setShowModal] = useState(false); - const [pacienteExistente, setPacienteExistente] = useState(null); - const [showSuccessModal, setShowSuccessModal] = useState(false); - +function PatientForm({ onSave, onCancel, formData, setFormData, isLoading }) { const [avatarUrl, setAvatarUrl] = useState(null); + const [showRequiredModal, setShowRequiredModal] = useState(false); + const [emptyFields, setEmptyFields] = useState([]); + const [cpfError, setCpfError] = useState(''); const [collapsedSections, setCollapsedSections] = useState({ dadosPessoais: true, infoMedicas: false, @@ -19,6 +15,41 @@ function PatientForm({ onSave, formData, setFormData }) { contato: false, }); + const nomeRef = useRef(null); + const cpfRef = useRef(null); + const emailRef = useRef(null); + const telefoneRef = useRef(null); + + + const validarCPF = (cpf) => { + const cpfLimpo = cpf.replace(/\D/g, ''); + + + if (cpfLimpo.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpfLimpo)) return false; + + + let soma = 0; + for (let i = 0; i < 9; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (10 - i); + } + let resto = 11 - (soma % 11); + let digito1 = resto === 10 || resto === 11 ? 0 : resto; + + + soma = 0; + for (let i = 0; i < 10; i++) { + soma += parseInt(cpfLimpo.charAt(i)) * (11 - i); + } + resto = 11 - (soma % 11); + let digito2 = resto === 10 || resto === 11 ? 0 : resto; + + + return digito1 === parseInt(cpfLimpo.charAt(9)) && digito2 === parseInt(cpfLimpo.charAt(10)); + }; + const handleToggleCollapse = (section) => { setCollapsedSections(prev => ({ ...prev, @@ -40,6 +71,16 @@ function PatientForm({ onSave, formData, setFormData }) { const handleChange = (e) => { const { name, value, type, checked, files } = e.target; + + if (value && emptyFields.includes(name)) { + setEmptyFields(prev => prev.filter(field => field !== name)); + } + + + if (name === 'cpf' && cpfError) { + setCpfError(''); + } + if (type === 'file') { setFormData(prev => ({ ...prev, [name]: files[0] })); @@ -51,7 +92,17 @@ function PatientForm({ onSave, formData, setFormData }) { setAvatarUrl(null); } } else if (name === 'cpf') { - setFormData(prev => ({ ...prev, cpf: FormatCPF(value) })); + const cpfFormatado = FormatCPF(value); + setFormData(prev => ({ ...prev, cpf: cpfFormatado })); + + const cpfLimpo = cpfFormatado.replace(/\D/g, ''); + if (cpfLimpo.length === 11) { + if (!validarCPF(cpfFormatado)) { + setCpfError('CPF inválido'); + } else { + setCpfError(''); + } + } } else if (name.includes('phone')) { setFormData(prev => ({ ...prev, [name]: FormatTelefones(value) })); } else if (name.includes('weight_kg') || name.includes('height_m')) { @@ -63,35 +114,114 @@ function PatientForm({ onSave, formData, setFormData }) { } }; + const scrollToEmptyField = (fieldName) => { + let fieldRef = null; + + switch (fieldName) { + case 'full_name': + fieldRef = nomeRef; + + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'cpf': + fieldRef = cpfRef; + setCollapsedSections(prev => ({ ...prev, dadosPessoais: true })); + break; + case 'email': + fieldRef = emailRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + case 'phone_mobile': + fieldRef = telefoneRef; + setCollapsedSections(prev => ({ ...prev, contato: true })); + break; + default: + return; + } + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'center' + }); + fieldRef.current.focus(); + + + fieldRef.current.style.border = '2px solid #dc3545'; + fieldRef.current.style.boxShadow = '0 0 0 0.2rem rgba(220, 53, 69, 0.25)'; + + + setTimeout(() => { + if (fieldRef.current) { + fieldRef.current.style.border = ''; + fieldRef.current.style.boxShadow = ''; + } + }, 3000); + } + }, 300); + }; + const handleSubmit = async () => { - // ALTERADO: Nome, CPF, Email e Telefone - if (!formData.full_name || !formData.cpf || !formData.email || !formData.phone_mobile) { - setErrorModalMsg('Por favor, preencha Nome, CPF, Email e Telefone.'); - setShowModal(true); + + const missingFields = []; + if (!formData.full_name) missingFields.push('full_name'); + if (!formData.cpf) missingFields.push('cpf'); + if (!formData.email) missingFields.push('email'); + if (!formData.phone_mobile) missingFields.push('phone_mobile'); + + if (missingFields.length > 0) { + setEmptyFields(missingFields); + setShowRequiredModal(true); + + + setTimeout(() => { + if (missingFields.length > 0) { + scrollToEmptyField(missingFields[0]); + } + }, 500); + return; } + const cpfLimpo = formData.cpf.replace(/\D/g, ''); if (cpfLimpo.length !== 11) { - setErrorModalMsg('CPF inválido. Por favor, verifique o número digitado.'); - setShowModal(true); + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF deve ter 11 dígitos'); + setTimeout(() => scrollToEmptyField('cpf'), 500); return; } - try { - await onSave({ ...formData, bmi: parseFloat(formData.bmi) || 0 }); - setShowSuccessModal(true); - } catch (error) { - setErrorModalMsg('Erro ao salvar paciente. Tente novamente.'); - setShowModal(true); + + if (!validarCPF(formData.cpf)) { + setShowRequiredModal(true); + setEmptyFields(['cpf']); + setCpfError('CPF inválido'); + setTimeout(() => scrollToEmptyField('cpf'), 500); + return; } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(formData.email)) { + throw new Error('Email inválido. Por favor, verifique o email digitado.'); + } + + + await onSave({ ...formData, bmi: parseFloat(formData.bmi) || null }); + }; + + const handleModalClose = () => { + setShowRequiredModal(false); }; return (

MediConnect

- {/* DADOS PESSOAIS - MANTIDO O LAYOUT ORIGINAL */} + {/* DADOS PESSOAIS */}

handleToggleCollapse('dadosPessoais')} style={{ fontSize: '1.8rem' }}> Dados Pessoais @@ -141,10 +271,20 @@ function PatientForm({ onSave, formData, setFormData }) { {formData.foto && {formData.foto.name}}

- {/* CADASTRO - MANTIDO O LAYOUT ORIGINAL COM COLUNAS */} + + {/* CAMPOS OBRIGATÓRIOS */}
- +
@@ -152,11 +292,25 @@ function PatientForm({ onSave, formData, setFormData }) {
- +
- @@ -165,7 +319,21 @@ function PatientForm({ onSave, formData, setFormData }) {
- + + {cpfError && ( +
+ {cpfError} +
+ )}
@@ -254,7 +422,7 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* CAMPOS MOVIDOS */} + {/* CAMPOS ADICIONAIS */}
@@ -272,7 +440,7 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* INFORMAÇÕES MÉDICAS - MANTIDO O LAYOUT ORIGINAL */} + {/* INFORMAÇÕES MÉDICAS */}

handleToggleCollapse('infoMedicas')} style={{ fontSize: '1.8rem' }}> Informações Médicas @@ -313,7 +481,7 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* INFORMAÇÕES DE CONVÊNIO - MANTIDO O LAYOUT ORIGINAL */} + {/* INFORMAÇÕES DE CONVÊNIO */}

handleToggleCollapse('infoConvenio')} style={{ fontSize: '1.8rem' }}> Informações de convênio @@ -366,7 +534,7 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* ENDEREÇO - MANTIDO O LAYOUT ORIGINAL */} + {/* ENDEREÇO */}

handleToggleCollapse('endereco')} style={{ fontSize: '1.8rem' }}> Endereço @@ -408,7 +576,6 @@ function PatientForm({ onSave, formData, setFormData }) {

- {/* CONTATO - MANTIDO O LAYOUT ORIGINAL */}

handleToggleCollapse('contato')} style={{ fontSize: '1.8rem' }}> Contato @@ -420,11 +587,29 @@ function PatientForm({ onSave, formData, setFormData }) {
- +
- +
@@ -438,16 +623,8 @@ function PatientForm({ onSave, formData, setFormData }) {
- {/* Botões */} -
- - -
- - {/* Modal de erro - EXATAMENTE COMO NA IMAGEM */} - {showModal && ( + + {showRequiredModal && (
- {/* Header */} +
Atenção
- {/* Body */} +

- Por favor, preencha: + {cpfError ? 'Problema com o CPF:' : 'Por favor, preencha:'}

- {!formData.full_name &&

- Nome

} - {!formData.cpf &&

- CPF

} - {!formData.email &&

- Email

} - {!formData.phone_mobile &&

- Telefone

} + {cpfError ? ( +

{cpfError}

+ ) : ( + <> + {!formData.full_name &&

- Nome

} + {!formData.cpf &&

- CPF

} + {!formData.email &&

- Email

} + {!formData.phone_mobile &&

- Telefone

} + + )}
- {/* Footer */} +
)} - {/* Modal de sucesso */} - {showSuccessModal && ( -
-
- {/* Header */} -
-
Sucesso
- -
- {/* Body */} -
-

- O cadastro do paciente foi realizado com sucesso. -

-
- - {/* Footer */} -
- + + +
-
-)} -
); } diff --git a/src/pages/DoctorCadastroManager.jsx b/src/pages/DoctorCadastroManager.jsx index 2aeca708..450e7f75 100644 --- a/src/pages/DoctorCadastroManager.jsx +++ b/src/pages/DoctorCadastroManager.jsx @@ -5,88 +5,305 @@ import API_KEY from '../components/utils/apiKeys'; import { useNavigate, useLocation } from 'react-router-dom'; function DoctorCadastroManager() { - const [DoctorDict, setDoctorDict] = useState({}) + const [doctorData, setDoctorData] = useState({}); + const [isLoading, setIsLoading] = useState(false); + const [showSuccessModal, setShowSuccessModal] = useState(false); + const [showErrorModal, setShowErrorModal] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const navigate = useNavigate(); const location = useLocation(); const { getAuthorizationHeader, isAuthenticated } = useAuth(); const handleSaveDoctor = async (doctorData) => { + setIsLoading(true); const authHeader = getAuthorizationHeader(); - var myHeaders = new Headers(); - myHeaders.append("Content-Type", "application/json"); - myHeaders.append("apikey", API_KEY); - myHeaders.append("Authorization", authHeader); - - console.log(' Dados recebidos do Form:', doctorData); - - const cleanedData = { - full_name: doctorData.full_name, - cpf: doctorData.cpf ? doctorData.cpf.replace(/\D/g, '') : null, - birth_date: doctorData.birth_date || null, - email: doctorData.email, - phone_mobile: doctorData.phone_mobile ? doctorData.phone_mobile.replace(/\D/g, '') : null, - crm_uf: doctorData.crm_uf, - crm: doctorData.crm, - specialty: doctorData.specialty || null, - cep: doctorData.cep ? doctorData.cep.replace(/\D/g, '') : null, - street: doctorData.street || null, - neighborhood: doctorData.neighborhood || null, - city: doctorData.city || null, - state: doctorData.state || null, - number: doctorData.number || null, - complement: doctorData.complement || null, - phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, - }; - - console.log(' Dados limpos para envio:', cleanedData); - - var raw = JSON.stringify(cleanedData); - - var requestOptions = { - method: 'POST', - headers: myHeaders, - body: raw, - redirect: 'follow' - }; - try { + var myHeaders = new Headers(); + myHeaders.append("Content-Type", "application/json"); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + + console.log('Dados recebidos do Form:', doctorData); + + const cleanedData = { + full_name: doctorData.full_name, + cpf: doctorData.cpf ? doctorData.cpf.replace(/\D/g, '') : null, + birth_date: doctorData.birth_date || null, + email: doctorData.email, + phone_mobile: doctorData.phone_mobile ? doctorData.phone_mobile.replace(/\D/g, '') : null, + crm_uf: doctorData.crm_uf, + crm: doctorData.crm, + specialty: doctorData.specialty || null, + cep: doctorData.cep ? doctorData.cep.replace(/\D/g, '') : null, + street: doctorData.street || null, + neighborhood: doctorData.neighborhood || null, + city: doctorData.city || null, + state: doctorData.state || null, + number: doctorData.number || null, + complement: doctorData.complement || null, + phone2: doctorData.phone2 ? doctorData.phone2.replace(/\D/g, '') : null, + }; + + console.log('Dados limpos para envio:', cleanedData); + + var raw = JSON.stringify(cleanedData); + + var requestOptions = { + method: 'POST', + headers: myHeaders, + body: raw, + redirect: 'follow' + }; + const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/doctors", requestOptions); - console.log(" Status da resposta:", response.status); - console.log(" Response ok:", response.ok); + console.log("Status da resposta:", response.status); + console.log("Response ok:", response.ok); if (!response.ok) { - let errorMessage = `Erro HTTP: ${response.status}`; - try { - const errorData = await response.json(); - console.error(" Erro detalhado:", errorData); - errorMessage = errorData.message || errorData.details || errorMessage; - } catch (e) { - const errorText = await response.text(); - console.error(" Erro texto:", errorText); - errorMessage = errorText || errorMessage; + let errorMessage = `Erro ao salvar médico (${response.status})`; + + + const responseText = await response.text(); + console.log("Conteúdo da resposta:", responseText); + + if (responseText) { + try { + const errorData = JSON.parse(responseText); + console.error("Erro detalhado:", errorData); + errorMessage = errorData.message || errorData.details || errorMessage; + } catch (jsonError) { + + errorMessage = responseText || errorMessage; + } + } else { + errorMessage = `Resposta vazia do servidor (${response.status})`; } + throw new Error(errorMessage); } - const result = await response.json(); - console.log("Médico salvo no backend:", result); + const responseText = await response.text(); + let result = null; + + if (responseText) { + try { + result = JSON.parse(responseText); + console.log("Médico salvo no backend:", result); + } catch (jsonError) { + console.warn("Resposta não é JSON válido, mas request foi bem-sucedido"); + result = { success: true, status: response.status }; + } + } else { + console.log("Resposta vazia - assumindo sucesso"); + result = { success: true, status: response.status }; + } - // Redireciona para a lista de médicos do perfil atual - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/medicos`); - - return result; + + setShowSuccessModal(true); } catch (error) { - console.error(" Erro ao salvar Médico:", error); - throw error; + console.error("Erro ao salvar Médico:", error); + + let userFriendlyMessage = error.message; + + if (error.message.includes('doctors_cpf_key') || error.message.includes('duplicate key')) { + userFriendlyMessage = 'Já existe um médico cadastrado com este CPF. Verifique os dados ou edite o médico existente.'; + } else if (error.message.includes('Unexpected end of JSON input')) { + userFriendlyMessage = 'Erro de comunicação com o servidor. Tente novamente.'; + } + + setErrorMessage(userFriendlyMessage); + setShowErrorModal(true); + } finally { + setIsLoading(false); } }; + const handleCloseSuccessModal = () => { + setShowSuccessModal(false); + + const prefixo = location.pathname.split("/")[1]; + navigate(`/${prefixo}/medicos`); + }; + + const handleCloseErrorModal = () => { + setShowErrorModal(false); + }; + return ( <> + + {showSuccessModal && ( +
+
+
+
Sucesso
+ +
+ +
+

+ Médico cadastrado com sucesso! +

+
+ +
+ +
+
+
+ )} + + + {showErrorModal && ( +
+
+
+
Erro
+ +
+ +
+

+ {errorMessage} +

+
+ +
+ +
+
+
+ )} +

Cadastro de Médicos

@@ -95,9 +312,9 @@ function DoctorCadastroManager() {
diff --git a/src/pages/PatientCadastroManager.jsx b/src/pages/PatientCadastroManager.jsx index 21f6ad6f..701e6b5b 100644 --- a/src/pages/PatientCadastroManager.jsx +++ b/src/pages/PatientCadastroManager.jsx @@ -1,37 +1,98 @@ import {useState} from 'react'; import React from 'react'; -import { useNavigate, useLocation } from 'react-router-dom'; +import { useNavigate } from 'react-router-dom'; import PatientForm from '../components/patients/PatientForm'; import API_KEY from '../components/utils/apiKeys'; import { useAuth } from '../components/utils/AuthProvider'; function PatientCadastroManager( {setCurrentPage} ) { - const navigate = useNavigate(); - const location = useLocation(); + const navigate = useNavigate() const [showModal, setShowModal] = useState(false); const [infosModal, setInfosModal] = useState({title:'', message:''}); + const [isLoading, setIsLoading] = useState(false); const { getAuthorizationHeader, isAuthenticated } = useAuth(); const [formData, setFormData] = useState({}) + + + const validarCPF = (cpf) => { + cpf = cpf.replace(/\D/g, ''); + + if (cpf.length !== 11) return false; + + + if (/^(\d)\1+$/.test(cpf)) return false; + + let soma = 0; + let resto; + + for (let i = 1; i <= 9; i++) { + soma = soma + parseInt(cpf.substring(i-1, i)) * (11 - i); + } + + resto = (soma * 10) % 11; + if ((resto === 10) || (resto === 11)) resto = 0; + if (resto !== parseInt(cpf.substring(9, 10))) return false; + + soma = 0; + for (let i = 1; i <= 10; i++) { + soma = soma + parseInt(cpf.substring(i-1, i)) * (12 - i); + } + + resto = (soma * 10) % 11; + if ((resto === 10) || (resto === 11)) resto = 0; + if (resto !== parseInt(cpf.substring(10, 11))) return false; + + return true; + } const handleSavePatient = async (patientData) => { - console.log('🔄 Iniciando salvamento do paciente:', patientData); + console.log(' Iniciando salvamento do paciente:', patientData); + setIsLoading(true); try { + + console.log(' Verificando autenticação...'); + if (!isAuthenticated) { + throw new Error('Usuário não autenticado'); + } + const authHeader = getAuthorizationHeader(); + console.log(' Header de autorização:', authHeader ? 'Presente' : 'Faltando'); + + if (!authHeader) { + throw new Error('Header de autorização não encontrado'); + } + + + const cpfLimpo = patientData.cpf.replace(/\D/g, ''); + if (!validarCPF(cpfLimpo)) { + throw new Error('CPF inválido. Por favor, verifique o número digitado.'); + } var myHeaders = new Headers(); myHeaders.append("Content-Type", "application/json"); myHeaders.append("apikey", API_KEY); myHeaders.append("Authorization", authHeader); + myHeaders.append("Prefer", "return=representation"); + + console.log(' Headers configurados:', { + 'Content-Type': 'application/json', + 'apikey': API_KEY ? 'Presente' : 'Faltando', + 'Authorization': authHeader ? 'Presente' : 'Faltando', + 'Prefer': 'return=representation' + }); const cleanedData = { full_name: patientData.full_name, - cpf: patientData.cpf.replace(/\D/g, ''), - birth_date: patientData.birth_date, - sex: patientData.sex, + cpf: cpfLimpo, email: patientData.email, phone_mobile: patientData.phone_mobile, + + birth_date: patientData.birth_date || null, + sex: patientData.sex === 'Masculino' ? 'M' : + patientData.sex === 'Feminino' ? 'F' : + patientData.sex || null, social_name: patientData.social_name || null, rg: patientData.rg || null, blood_type: patientData.blood_type || null, @@ -41,9 +102,14 @@ function PatientCadastroManager( {setCurrentPage} ) { notes: patientData.notes || null, }; - console.log('📤 Dados limpos para envio:', cleanedData); + console.log(' Dados limpos para envio:', cleanedData); + + if (!cleanedData.full_name || !cleanedData.cpf || !cleanedData.email || !cleanedData.phone_mobile) { + throw new Error('Dados obrigatórios faltando: nome, CPF, email e telefone são necessários'); + } var raw = JSON.stringify(cleanedData); + console.log(' Payload JSON:', raw); var requestOptions = { method: 'POST', @@ -52,39 +118,74 @@ function PatientCadastroManager( {setCurrentPage} ) { redirect: 'follow' }; + console.log(' Fazendo requisição para API...'); const response = await fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/patients", requestOptions); - console.log('📨 Status da resposta:', response.status); + console.log(' Status da resposta:', response.status); console.log(' Response ok:', response.ok); + let responseData; + try { + responseData = await response.json(); + console.log(' Corpo da resposta:', responseData); + } catch (jsonError) { + console.log(' Não foi possível parsear JSON da resposta:', jsonError); + responseData = { error: 'Resposta inválida da API' }; + } + + if (!response.ok) { - let errorMessage = `Erro HTTP: ${response.status}`; - try { - const errorData = await response.json(); - errorMessage = errorData.message || errorData.details || errorMessage; - } catch (e) { - const errorText = await response.text(); - errorMessage = errorText || errorMessage; + console.error(' Erro da API - Detalhes:', { + status: response.status, + statusText: response.statusText, + data: responseData + }); + + let errorMessage = 'Erro ao salvar paciente'; + + if (response.status === 401) { + errorMessage = 'Não autorizado. Verifique suas credenciais.'; + } else if (response.status === 403) { + errorMessage = 'Acesso proibido. Verifique suas permissões.'; + } else if (response.status === 409) { + errorMessage = 'Paciente com este CPF já existe.'; + } else if (response.status === 400) { + errorMessage = `Dados inválidos: ${responseData.details || responseData.message || 'Verifique os campos'}`; + } else if (response.status === 422) { + errorMessage = `Dados de entrada inválidos: ${responseData.details || 'Verifique o formato dos dados'}`; + } else if (response.status >= 500) { + errorMessage = 'Erro interno do servidor. Tente novamente mais tarde.'; + } else { + errorMessage = `Erro ${response.status}: ${responseData.message || responseData.error || 'Erro desconhecido'}`; } + throw new Error(errorMessage); } - const result = await response.json(); - console.log("Paciente salvo no backend:", result); - - // Redireciona para a lista de pacientes do perfil atual - const prefixo = location.pathname.split("/")[1]; - navigate(`/${prefixo}/pacientes`); - - return result; - - } catch (error) { - console.error(error); + console.log(' Paciente salvo com sucesso:', responseData); + + setInfosModal({ - title: 'Erro de conexão', - message: 'Não foi possível conectar ao servidor. Verifique sua internet e tente novamente.' + title: 'Sucesso', + message: 'O cadastro do paciente foi realizado com sucesso.' }); setShowModal(true); + + + setTimeout(() => { + setShowModal(false); + navigate('/secretaria/pacientes'); + }, 2000); + + } catch (error) { + console.error(' Erro completo ao salvar paciente:', error); + setInfosModal({ + title: 'Erro', + message: error.message || 'Não foi possível conectar ao servidor. Verifique sua internet e tente novamente.' + }); + setShowModal(true); + } finally { + setIsLoading(false); } }; @@ -92,44 +193,117 @@ function PatientCadastroManager( {setCurrentPage} ) { <>
{showModal &&( -
-
-
-
-
{infosModal.title}
- -
-
-

{infosModal.message}

-
-
- -
+
+
+ +
+
{infosModal.title}
+ +
+ + +
+

+ {infosModal.message} +

+ {infosModal.title === 'Erro' && ( +

+

+ )} +
+ + +
+
)}

Cadastro de Pacientes

+ {isLoading && ( +
+
+ Salvando paciente... +
+ )}
navigate('/secretaria/pacientes')} formData={formData} setFormData={setFormData} + isLoading={isLoading} />
From dc24d363463099d5472f0b8117ea8318a8962f59 Mon Sep 17 00:00:00 2001 From: jp-lima Date: Thu, 9 Oct 2025 09:05:33 -0300 Subject: [PATCH 4/6] =?UTF-8?q?Adi=C3=A7=C3=A3o=20da=20funcionalidade=20de?= =?UTF-8?q?=20ver=20detalhes=20e=20editar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/PagesMedico/DoctorRelatorioManager.jsx | 132 ++++++++++-- src/PagesMedico/EditPageRelatorio.jsx | 65 ++++++ src/PagesMedico/FormNovoRelatorio.jsx | 200 ++--------------- .../styleMedico/FormNovoRelatorio.css | 2 +- src/PagesMedico/styleMedico/geral.css | 24 ++- src/components/FormRelatorio.jsx | 201 ++++++++++++++++++ src/perfis/Perfil_medico/PerfilMedico.jsx | 2 + 7 files changed, 422 insertions(+), 204 deletions(-) create mode 100644 src/PagesMedico/EditPageRelatorio.jsx create mode 100644 src/components/FormRelatorio.jsx diff --git a/src/PagesMedico/DoctorRelatorioManager.jsx b/src/PagesMedico/DoctorRelatorioManager.jsx index 334de6b6..ce246021 100644 --- a/src/PagesMedico/DoctorRelatorioManager.jsx +++ b/src/PagesMedico/DoctorRelatorioManager.jsx @@ -3,16 +3,20 @@ 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 { useNavigate } from 'react-router-dom'; +import html2pdf from 'html2pdf.js'; const DoctorRelatorioManager = () => { + const navigate = useNavigate() const {getAuthorizationHeader} = useAuth(); let authHeader = getAuthorizationHeader() const [RelatoriosFiltrados, setRelatorios] = useState([]) const [PacientesComRelatorios, setPacientesComRelatorios] = useState([]) + const [showModal, setShowModal] = useState(false) + const [index, setIndex] = useState() useEffect( () => { - let pacientesDosRelatorios =[] - - + let pacientesDosRelatorios = [] + const ListarPacientes = async () => { for (let i = 0; i < RelatoriosFiltrados.length; i++) { let relatorio = RelatoriosFiltrados[i]; @@ -50,10 +54,78 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu .catch(error => console.log('error', error)); }, []) - + const BaixarPDFdoRelatorio = (nome_paciente) => { + const elemento = document.getElementById("folhaA4"); // tua div do relatório + 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 && ( +
+
+
+
+
Relatório de {PacientesComRelatorios[index]?.full_name}
+ +
+
+
+ +
+

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: {}

+ +

Exame: {RelatoriosFiltrados[index]?.exam}

+ +

Diagnostico: {RelatoriosFiltrados[index]?.diagnosis}

+

Conclusão: {RelatoriosFiltrados[index]?.conclusion}

+
+ +
+

Dr {RelatoriosFiltrados[index]?.required_by}

+

Emitido em: 0

+
+ +
+
+
+ + + + +
+
+
+
+ )} + +

Lista de Relatórios

@@ -103,39 +175,59 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?patient_id&statu - + + - {RelatoriosFiltrados.length > 0 ? ( RelatoriosFiltrados.map((relatorio, index) => ( - + - + diff --git a/src/PagesMedico/EditPageRelatorio.jsx b/src/PagesMedico/EditPageRelatorio.jsx new file mode 100644 index 00000000..831d8ad0 --- /dev/null +++ b/src/PagesMedico/EditPageRelatorio.jsx @@ -0,0 +1,65 @@ +import React, { useEffect, useState } from 'react' +import FormRelatorio from '../components/FormRelatorio' +import { useParams } from 'react-router-dom' +import API_KEY from '../components/utils/apiKeys' +import { useAuth } from '../components/utils/AuthProvider' +const EditPageRelatorio = () => { + const params = useParams() + const {getAuthorizationHeader} = useAuth() + let authHeader = getAuthorizationHeader() + const [DictInfo, setDictInfo] = useState({}) + + let RelatorioID = params.id + + const handleSave = (RelatorioInfos) => { + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + myHeaders.append("Content-Type", "application/json"); + + const raw = JSON.stringify({...RelatorioInfos, order_number:'REL-2025-4386'}) + + console.log(RelatorioInfos) + + var requestOptions = { + method: 'PATCH', + headers: myHeaders, + body: raw, + redirect: 'follow' + +}; + + fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.log('error', error)); + + } + + useEffect(() => { + var myHeaders = new Headers(); + myHeaders.append("apikey", API_KEY); + myHeaders.append("Authorization", authHeader); + + var requestOptions = { + method: 'GET', + headers: myHeaders, + redirect: 'follow' + }; + + fetch(`https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports?id=eq.${RelatorioID}`, requestOptions) + .then(response => response.json()) + .then(result => setDictInfo(result[0])) + .catch(error => console.log('error', error)); + }, []) + + console.log(RelatorioID) + + return ( +
+ +
+ ) +} + +export default EditPageRelatorio \ No newline at end of file diff --git a/src/PagesMedico/FormNovoRelatorio.jsx b/src/PagesMedico/FormNovoRelatorio.jsx index 29018f79..36917935 100644 --- a/src/PagesMedico/FormNovoRelatorio.jsx +++ b/src/PagesMedico/FormNovoRelatorio.jsx @@ -1,72 +1,24 @@ -import React from 'react' + import '../PagesMedico/styleMedico/FormNovoRelatorio.css' -import { useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useAuth } from '../components/utils/AuthProvider' -import { GetPatientByCPF } from '../components/utils/Functions-Endpoints/Patient' -import { FormatCPF } from '../components/utils/Formatar/Format' import API_KEY from '../components/utils/apiKeys' -import html2pdf from "html2pdf.js"; - - +import FormRelatorio from '../components/FormRelatorio' +import { useState } from 'react' +import { useAuth } from '../components/utils/AuthProvider' const FormNovoRelatorio = () => { - const {getAuthorizationHeader} = useAuth() - let authHeader = getAuthorizationHeader() - const navigate= useNavigate() - const [DictInfo, setDictInfo] = useState({}) - const [showModal, setShowModal] = useState(false) + const [DictInfo, setDictInfo] = useState({}) - const handleChange = (e) => { - const { name, value } = e.target; - console.log(name, value) + const {getAuthorizationHeader} = useAuth() + let authHeader = getAuthorizationHeader() - - if(name === 'paciente_cpf') { - const formattedCPF = FormatCPF(value); - setDictInfo((prev) => ({ ...prev, [name]: formattedCPF })); - - const fetchPatient = async () => { - const patientData = await GetPatientByCPF(formattedCPF, authHeader); - if (patientData) { - setDictInfo((prev) => ({ - ...prev, - paciente_cpf:value, - paciente_nome: patientData.full_name, - paciente_id: patientData.id - })); - } - - }; - if(formattedCPF.length === 14){ - fetchPatient(); - } - }else{ - setDictInfo((prev) => ({ ...prev, [name]: value })); - } - } - - const handleSubmit = (e) => { - e.preventDefault(); - console.log(DictInfo) - setShowModal(true) - - var myHeaders = new Headers(); + const handleSave = (data) => { + console.log("Relatório salvo:", data); + + var myHeaders = new Headers(); myHeaders.append("apikey", API_KEY); myHeaders.append("Authorization", authHeader); myHeaders.append("Content-Type", "application/json"); -var raw = JSON.stringify({ - "patient_id": DictInfo.paciente_id, - - "exam": DictInfo.exam, - "diagnosis": DictInfo.diagnosis, - "conclusion": DictInfo.conclusao, - "status": "draft", - "requested_by": DictInfo.requested_by, - - "hide_date": false, - "hide_signature": false, -}); +var raw = JSON.stringify({...data}); var requestOptions = { method: 'POST', @@ -79,130 +31,14 @@ fetch("https://yuanqfswhberkoevtmfr.supabase.co/rest/v1/reports", requestOptions .then(response => response.text()) .then(result => console.log(result)) .catch(error => console.log('error', error)); - } + } - return ( -
- {showModal &&( -
-
-
-
-
Relatório criado com sucesso
- -
-
-

Você também pode baixa-lo agora em pdf

-
-
- - - -
-
-
-
- )} -

Criar novo relatório

- -
- -
-
- -
- - -
- -
- - -
- - -
- - -
- -
- - -
- - -
- - -
- + return ( +
+

Criar Novo Relatorio

+
- -
-
- - -
- -
- - -
-
- - - - -
- -

Modelo do relatório

-
- -
-

Clinica Rise up

-

Dr {DictInfo.requested_by} - CRM/SP 123456

-

Avenida - (79) 9 4444-4444

-
- -
-

Paciente: {DictInfo?.paciente_nome}

-

Data de nascimento:

- -

Data do exame: {DictInfo.data_exame}

- -

Exame: {DictInfo.exame}

- -

Diagnostico: {DictInfo.diagnostico}

- -

Conclusão: {DictInfo.conclusao}

- -
- -
-

Dr {DictInfo.requested_by}

-

Emitido em: 0

-
- -
- -
- ) + ) } export default FormNovoRelatorio \ No newline at end of file diff --git a/src/PagesMedico/styleMedico/FormNovoRelatorio.css b/src/PagesMedico/styleMedico/FormNovoRelatorio.css index 7d8a5ed9..a8fafa30 100644 --- a/src/PagesMedico/styleMedico/FormNovoRelatorio.css +++ b/src/PagesMedico/styleMedico/FormNovoRelatorio.css @@ -52,4 +52,4 @@ textarea{ .info-paciente{ font-weight: bold; -} \ No newline at end of file +} diff --git a/src/PagesMedico/styleMedico/geral.css b/src/PagesMedico/styleMedico/geral.css index 94918bd6..67785953 100644 --- a/src/PagesMedico/styleMedico/geral.css +++ b/src/PagesMedico/styleMedico/geral.css @@ -186,4 +186,26 @@ tbody tr:hover { td { padding: 12px; border-bottom: 1px solid var(--cor-borda); -} \ No newline at end of file +} + +.modal-tabela-relatorio{ + width: 70rem; +} + +.modal-dialog.modal-tabela-relatorio { + max-width: 900px; /* largura máxima da modal */ + width: 100%; /* ocupa até o limite */ + margin: 1.75rem auto; /* centraliza vertical e horizontalmente */ +} + +.modal-content { + height: auto; /* altura variável conforme o conteúdo */ + max-height: none; /* remove limite interno */ +} + +.modal-body { + max-height: 70vh; /* limite vertical — 80% da altura da tela */ + overflow-y: auto; /* ativa rolagem vertical */ + overflow-x: hidden; /* impede rolagem horizontal */ + padding: 20px; /* espaço interno mais agradável */ +} diff --git a/src/components/FormRelatorio.jsx b/src/components/FormRelatorio.jsx new file mode 100644 index 00000000..7c04a49c --- /dev/null +++ b/src/components/FormRelatorio.jsx @@ -0,0 +1,201 @@ +import React from 'react' +import '../PagesMedico/styleMedico/FormNovoRelatorio.css' +import { useState } from 'react' +import { useNavigate } from 'react-router-dom' +import { useAuth } from '../components/utils/AuthProvider' +import { GetPatientByCPF } from '../components/utils/Functions-Endpoints/Patient' +import { FormatCPF } from '../components/utils/Formatar/Format' +import html2pdf from 'html2pdf.js' + +const FormRelatorio = ({onSave, DictInfo, setDictInfo }) => { + const {getAuthorizationHeader} = useAuth() + let authHeader = getAuthorizationHeader() + const navigate= useNavigate() + + const [showModal, setShowModal] = useState(false) + + const BaixarPDFdoRelatorio = () => { + const elemento = document.getElementById("folhaA4"); // tua div do relatório + const opt = { + margin: 0, + filename: `relatorio_${DictInfo?.paciente_nome || "paciente"}.pdf`, + html2canvas: { scale: 2 }, + jsPDF: { unit: "mm", format: "a4", orientation: "portrait" }, + }; + + html2pdf().set(opt).from(elemento).save(); + } + + const handleChange = (e) => { + const { name, value } = e.target; + console.log(name, value) + if(name === 'paciente_cpf') { + const formattedCPF = FormatCPF(value); + setDictInfo((prev) => ({ ...prev, [name]: formattedCPF })); + + const fetchPatient = async () => { + const patientData = await GetPatientByCPF(formattedCPF, authHeader); + if (patientData) { + setDictInfo((prev) => ({ + ...prev, + paciente_cpf:value, + paciente_nome: patientData.full_name, + paciente_id: patientData.id + })); + } + + }; + if(formattedCPF.length === 14){ + fetchPatient(); + } + }else{ + setDictInfo((prev) => ({ ...prev, [name]: value })); + } + } + + const handleSubmit = (e) => { + e.preventDefault(); + console.log(DictInfo) + setShowModal(true) + + +onSave({ + "patient_id": DictInfo.paciente_id, + + "exam": DictInfo.exam, + "diagnosis": DictInfo.diagnosis, + "conclusion": DictInfo.conclusao, + "status": "draft", + "requested_by": DictInfo.requested_by, + + "hide_date": false, + "hide_signature": false, +}); + + } + + return ( +
+ {showModal &&( +
+
+
+
+
Relatório criado com sucesso
+ +
+
+

Você também pode baixa-lo agora em pdf

+
+
+ + + +
+
+
+
+ )} + + +
+ +
+
+ +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ + +
+ + +
+ + +
+ +
+ +
+
+ + +
+ +
+ + +
+
+ + + + +
+ +

Modelo do relatório

+
+ +
+

Clinica Rise up

+

Dr {DictInfo.requested_by} - CRM/SP 123456

+

Avenida - (79) 9 4444-4444

+
+ +
+

Paciente: {DictInfo?.paciente_nome}

+

Data de nascimento:

+ +

Data do exame: {DictInfo.data_exam}

+ +

Exame: {DictInfo.exam}

+ +

Diagnostico: {DictInfo.diagnostico}

+ +

Conclusão: {DictInfo.conclusao}

+ +
+ +
+

Dr {DictInfo.requested_by}

+

Emitido em: 0

+
+ +
+ +
+ ) +} + +export default FormRelatorio \ No newline at end of file diff --git a/src/perfis/Perfil_medico/PerfilMedico.jsx b/src/perfis/Perfil_medico/PerfilMedico.jsx index f34b0b7f..b1b95462 100644 --- a/src/perfis/Perfil_medico/PerfilMedico.jsx +++ b/src/perfis/Perfil_medico/PerfilMedico.jsx @@ -8,6 +8,7 @@ import Agendamento from "../../PagesMedico/Agendamento"; import Chat from "../../PagesMedico/Chat"; import DoctorItems from "../../data/sidebar-items-medico.json"; import FormNovoRelatorio from "../../PagesMedico/FormNovoRelatorio"; +import EditPageRelatorio from "../../PagesMedico/EditPageRelatorio"; // ...existing code... function PerfilMedico() { @@ -19,6 +20,7 @@ function PerfilMedico() { } /> } /> + } /> } /> } /> } /> From 14445b4e187b0ef639324da1d052ded9fe5de262 Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Thu, 9 Oct 2025 09:46:57 -0300 Subject: [PATCH 5/6] dropdownroutes --- src/components/TrocardePerfis.jsx | 116 ++++++++++++----------------- src/pages/style/TrocardePerfil.css | 62 +++++++++++++++ 2 files changed, 109 insertions(+), 69 deletions(-) create mode 100644 src/pages/style/TrocardePerfil.css diff --git a/src/components/TrocardePerfis.jsx b/src/components/TrocardePerfis.jsx index 815ad294..7c244d1d 100644 --- a/src/components/TrocardePerfis.jsx +++ b/src/components/TrocardePerfis.jsx @@ -1,82 +1,60 @@ -import {React, useState, useEffect} from 'react' -import { useNavigate, useLocation } from 'react-router-dom' -import { UserInfos } from './utils/Functions-Endpoints/General'; -import { useAuth } from './utils/AuthProvider'; -import './Estilo/TrocardePerfis.css' +import React, { useState, useEffect } from "react"; +import { useNavigate, useLocation } from "react-router-dom"; +import { UserInfos } from "./utils/Functions-Endpoints/General"; +import { useAuth } from "./utils/AuthProvider"; +import "./Estilo/TrocardePerfis.css"; const TrocardePerfis = () => { const location = useLocation(); - - const [selectedProfile, setSelectedProfile] = useState(''); + const navigate = useNavigate(); const { getAuthorizationHeader } = useAuth(); + + const [selectedProfile, setSelectedProfile] = useState(""); const [showProfiles, setShowProfiles] = useState([]); - const navigate = useNavigate(); - let authHeader = getAuthorizationHeader(); - console.log('AUTH HEADER', authHeader) + useEffect(() => { + const fetchData = async () => { + const authHeader = getAuthorizationHeader(); + setSelectedProfile(location.pathname || ""); + const userInfo = await UserInfos(authHeader); + setShowProfiles(userInfo?.roles || []); + }; + fetchData(); + }, [location.pathname, getAuthorizationHeader]); - -const handleProfileClick = (route) => { - - navigate(route); -}; - - -useEffect(() => { - const fetchData = async () => { - setSelectedProfile(location.pathname); - const userInfo = await UserInfos(authHeader); - setShowProfiles(userInfo?.roles || []); + const handleSelectChange = (e) => { + const route = e.target.value; + setSelectedProfile(route); + if (route) navigate(route); }; - fetchData(); -}, []); + const options = [ + { key: "secretaria", label: "Secretaria", route: "/secretaria" }, + { key: "medico", label: "Médico", route: "/medico" }, + { key: "financeiro", label: "Financeiro", route: "/financeiro" }, + { key: "admin", label: "Administração", route: "/admin" }, + ].filter( + (opt) => + showProfiles?.includes(opt.key) || showProfiles?.includes("admin") + ); return ( - -
- -

Acesso aos modulos:

-
- {(showProfiles?.includes('secretaria') || showProfiles?.includes('admin')) && ( - - )} - {(showProfiles?.includes('medico') || showProfiles?.includes('admin')) && ( - - )} -
- -
- {(showProfiles?.includes('financeiro') || showProfiles?.includes('admin')) && ( - - )} - {showProfiles?.includes('admin') && ( - - )} -
+
+

Acesso aos módulos:

+
- ) -} + ); +}; -export default TrocardePerfis \ No newline at end of file +export default TrocardePerfis; diff --git a/src/pages/style/TrocardePerfil.css b/src/pages/style/TrocardePerfil.css new file mode 100644 index 00000000..3a253c29 --- /dev/null +++ b/src/pages/style/TrocardePerfil.css @@ -0,0 +1,62 @@ +.container-perfis { + position: absolute; + top: 80px; + left: 30px; + width: calc(100% - 60px); + display: flex; + flex-direction: column; + align-items: flex-start; + z-index: 60; +} + +.acesso-text { + font-size: 15px; + font-weight: 600; + color: #1e2b57; + margin-bottom: 6px; +} + +/* estilo visual refinado do select */ +.perfil-select { + width: 100%; + padding: 10px 14px; + border-radius: 10px; + border: 1.8px solid #d0d5dd; + background-color: #f9fafc; + color: #1e2b57; + font-weight: 600; + font-size: 14px; + outline: none; + cursor: pointer; + transition: all 0.2s ease; + box-shadow: 0 1px 3px rgba(30, 43, 87, 0.08); +} + +.perfil-select:hover { + border-color: #7a85ff; + background-color: #ffffff; + box-shadow: 0 2px 6px rgba(30, 43, 87, 0.1); +} + +.perfil-select:focus { + border-color: #5a46ff; + box-shadow: 0 0 0 3px rgba(90, 70, 255, 0.2); +} + +.perfil-select option[value=""] { + color: #777; + font-weight: 500; +} + +/* responsivo */ +@media (max-width: 780px) { + .container-perfis { + top: 60px; + left: 20px; + width: calc(100% - 40px); + } + .perfil-select { + font-size: 13px; + padding: 8px 12px; + } +} From cc3d1a4a7e431f1cb108f198028afbc06ba71264 Mon Sep 17 00:00:00 2001 From: Jessica_Faro Date: Thu, 9 Oct 2025 11:57:47 -0300 Subject: [PATCH 6/6] =?UTF-8?q?mudan=C3=A7asestilo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/TrocardePerfis.jsx | 4 +- src/pages/style/TrocardePerfis.css | 83 ++++++++++++++++++++++++++++++ 2 files changed, 85 insertions(+), 2 deletions(-) create mode 100644 src/pages/style/TrocardePerfis.css diff --git a/src/components/TrocardePerfis.jsx b/src/components/TrocardePerfis.jsx index 7c244d1d..d340b7fc 100644 --- a/src/components/TrocardePerfis.jsx +++ b/src/components/TrocardePerfis.jsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import { useNavigate, useLocation } from "react-router-dom"; import { UserInfos } from "./utils/Functions-Endpoints/General"; import { useAuth } from "./utils/AuthProvider"; -import "./Estilo/TrocardePerfis.css"; +import "../pages/style/TrocardePerfis.css"; const TrocardePerfis = () => { const location = useLocation(); @@ -57,4 +57,4 @@ const TrocardePerfis = () => { ); }; -export default TrocardePerfis; +export default TrocardePerfis; \ No newline at end of file diff --git a/src/pages/style/TrocardePerfis.css b/src/pages/style/TrocardePerfis.css new file mode 100644 index 00000000..29900a4a --- /dev/null +++ b/src/pages/style/TrocardePerfis.css @@ -0,0 +1,83 @@ +/* TrocardePerfis.css */ +.container-perfis { + /* certifique-se de que este arquivo esteja sendo importado corretamente */ + position: relative; /* evita sobrescrever posição do layout pai */ + display: flex; + flex-direction: column; + align-items: flex-end; /* empurra o select para a direita */ + padding-right: 24px; + padding-left: 16px; + margin-top: 20px; + width: auto; /* evita que ocupe 100% */ + z-index: 60; +} + +/* Alinha o label à direita para combinar com o select */ +.container-perfis .acesso-text { + font-size: 15px; + font-weight: 600; + color: #1e2b57; + margin-bottom: 8px; + text-align: right; +} + +/* Estilo do select (mais específico para evitar overrides) */ +.container-perfis .perfil-select { + width: 220px; /* largura fixa similar ao exemplo */ + padding: 10px 14px; + border-radius: 8px; + border: 1px solid #d0d5dd; + background-color: #ffffff; + color: #344054; + font-weight: 600; + font-size: 14px; + outline: none; + cursor: pointer; + transition: all 0.16s ease-in-out; + box-shadow: 0 1px 2px rgba(16, 24, 40, 0.05); + margin-left: auto; /* garante alinhamento à direita */ + display: block; + appearance: none; /* remove aparência nativa (melhora visual) */ + -webkit-appearance: none; + -moz-appearance: none; + background-image: linear-gradient(45deg, transparent 50%, #344054 50%), + linear-gradient(135deg, #344054 50%, transparent 50%); /* seta */ + background-position: calc(100% - 18px) calc(50% - 3px), calc(100% - 13px) calc(50% - 3px); + background-size: 6px 6px, 6px 6px; + background-repeat: no-repeat; + padding-right: 36px; /* espaço para a "seta" */ +} + +/* remove seta do select no IE */ +.container-perfis .perfil-select::-ms-expand { + display: none; +} + +.container-perfis .perfil-select:hover { + border-color: #3366ff; + box-shadow: 0 4px 10px rgba(51, 102, 255, 0.06); +} + +.container-perfis .perfil-select:focus { + border-color: #3366ff; + box-shadow: 0 0 0 3px rgba(51, 102, 255, 0.12); +} + +/* placeholder option */ +.container-perfis .perfil-select option[value=""] { + color: #9ca3af; + font-weight: 500; +} + +/* responsivo */ +@media (max-width: 780px) { + .container-perfis { + align-items: flex-start; + padding-right: 8px; + } + .container-perfis .perfil-select { + width: 100%; + margin-left: 0; + padding-right: 14px; + } +}
Paciente CPF ExameAções
{relatorio.order_number}{PacientesComRelatorios[index]?.full_name} {PacientesComRelatorios[index]?.cpf} {relatorio.exam}{relatorio.create_at} -
- +