feat: atualiza laudos, upload, histórico, botão coeso, preview

This commit is contained in:
pedrogomes5913 2025-09-30 21:45:32 -03:00
parent c56cd9ff63
commit 80aa1c3401
4 changed files with 903 additions and 108 deletions

2
.next/trace Normal file
View File

@ -0,0 +1,2 @@
[{"name":"next-dev","duration":947649,"timestamp":2817749789,"id":1,"tags":{},"startTime":1758747933659,"traceId":"bd9681dfac8820db"}]
[{"name":"next-dev","duration":880712,"timestamp":2915157337,"id":1,"tags":{},"startTime":1758748031067,"traceId":"64935a741399e8ce"}]

View File

@ -1,9 +1,9 @@
{ {
"dependencies": { "dependencies": {
"@headlessui/react": "^2.2.7", "@headlessui/react": "^2.2.9",
"@heroicons/react": "^2.2.0", "@heroicons/react": "^2.2.0",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"react-big-calendar": "^1.19.4", "react-big-calendar": "^1.19.4",
"react-signature-canvas": "^1.1.0-alpha.2" "react-signature-canvas": "1.1.0-alpha.2"
} }
} }

520
pnpm-lock.yaml generated Normal file
View File

@ -0,0 +1,520 @@
lockfileVersion: '9.0'
settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
importers:
.:
dependencies:
'@headlessui/react':
specifier: ^2.2.9
version: 2.2.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@heroicons/react':
specifier: ^2.2.0
version: 2.2.0(react@19.1.1)
date-fns:
specifier: ^4.1.0
version: 4.1.0
react-big-calendar:
specifier: ^1.19.4
version: 1.19.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react-signature-canvas:
specifier: 1.1.0-alpha.2
version: 1.1.0-alpha.2(@types/react@19.1.16)(prop-types@15.8.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
packages:
'@babel/runtime@7.28.4':
resolution: {integrity: sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==}
engines: {node: '>=6.9.0'}
'@floating-ui/core@1.7.3':
resolution: {integrity: sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==}
'@floating-ui/dom@1.7.4':
resolution: {integrity: sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==}
'@floating-ui/react-dom@2.1.6':
resolution: {integrity: sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/react@0.26.28':
resolution: {integrity: sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==}
peerDependencies:
react: '>=16.8.0'
react-dom: '>=16.8.0'
'@floating-ui/utils@0.2.10':
resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==}
'@headlessui/react@2.2.9':
resolution: {integrity: sha512-Mb+Un58gwBn0/yWZfyrCh0TJyurtT+dETj7YHleylHk5od3dv2XqETPGWMyQ5/7sYN7oWdyM1u9MvC0OC8UmzQ==}
engines: {node: '>=10'}
peerDependencies:
react: ^18 || ^19 || ^19.0.0-rc
react-dom: ^18 || ^19 || ^19.0.0-rc
'@heroicons/react@2.2.0':
resolution: {integrity: sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==}
peerDependencies:
react: '>= 16 || ^19.0.0-rc'
'@popperjs/core@2.11.8':
resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==}
'@react-aria/focus@3.21.1':
resolution: {integrity: sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/interactions@3.25.5':
resolution: {integrity: sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/ssr@3.9.10':
resolution: {integrity: sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==}
engines: {node: '>= 12'}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-aria/utils@3.30.1':
resolution: {integrity: sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
react-dom: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-stately/flags@3.1.2':
resolution: {integrity: sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==}
'@react-stately/utils@3.10.8':
resolution: {integrity: sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@react-types/shared@3.32.0':
resolution: {integrity: sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==}
peerDependencies:
react: ^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1
'@restart/hooks@0.4.16':
resolution: {integrity: sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==}
peerDependencies:
react: '>=16.8.0'
'@swc/helpers@0.5.17':
resolution: {integrity: sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==}
'@tanstack/react-virtual@3.13.12':
resolution: {integrity: sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
'@tanstack/virtual-core@3.13.12':
resolution: {integrity: sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==}
'@types/react@19.1.16':
resolution: {integrity: sha512-WBM/nDbEZmDUORKnh5i1bTnAz6vTohUf9b8esSMu+b24+srbaxa04UbJgWx78CVfNXA20sNu0odEIluZDFdCog==}
'@types/signature_pad@2.3.6':
resolution: {integrity: sha512-v3j92gCQJoxomHhd+yaG4Vsf8tRS/XbzWKqDv85UsqjMGy4zhokuwKe4b6vhbgncKkh+thF+gpz6+fypTtnFqQ==}
'@types/warning@3.0.3':
resolution: {integrity: sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==}
clsx@1.2.1:
resolution: {integrity: sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==}
engines: {node: '>=6'}
clsx@2.1.1:
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
engines: {node: '>=6'}
csstype@3.1.3:
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
date-arithmetic@4.1.0:
resolution: {integrity: sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==}
date-fns@4.1.0:
resolution: {integrity: sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==}
dayjs@1.11.18:
resolution: {integrity: sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==}
dequal@2.0.3:
resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==}
engines: {node: '>=6'}
dom-helpers@5.2.1:
resolution: {integrity: sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==}
globalize@0.1.1:
resolution: {integrity: sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA==}
invariant@2.2.4:
resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==}
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
lodash-es@4.17.21:
resolution: {integrity: sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==}
lodash@4.17.21:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
luxon@3.7.2:
resolution: {integrity: sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==}
engines: {node: '>=12'}
memoize-one@6.0.0:
resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==}
moment-timezone@0.5.48:
resolution: {integrity: sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==}
moment@2.30.1:
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
react-big-calendar@1.19.4:
resolution: {integrity: sha512-FrvbDx2LF6JAWFD96LU1jjloppC5OgIvMYUYIPzAw5Aq+ArYFPxAjLqXc4DyxfsQDN0TJTMuS/BIbcSB7Pg0YA==}
peerDependencies:
react: ^16.14.0 || ^17 || ^18 || ^19
react-dom: ^16.14.0 || ^17 || ^18 || ^19
react-dom@19.1.1:
resolution: {integrity: sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==}
peerDependencies:
react: ^19.1.1
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
react-lifecycles-compat@3.0.4:
resolution: {integrity: sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==}
react-overlays@5.2.1:
resolution: {integrity: sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==}
peerDependencies:
react: '>=16.3.0'
react-dom: '>=16.3.0'
react-signature-canvas@1.1.0-alpha.2:
resolution: {integrity: sha512-tKUNk3Gmh04Ug4K8p5g8Is08BFUKvbXxi0PyetQ/f8OgCBzcx4vqNf9+OArY/TdNdfHtswXQNRwZD6tyELjkjQ==}
peerDependencies:
'@types/prop-types': ^15.7.3
'@types/react': 0.14 - 19
prop-types: ^15.5.8
react: 0.14 - 19
react-dom: 0.14 - 19
peerDependenciesMeta:
'@types/prop-types':
optional: true
'@types/react':
optional: true
react@19.1.1:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
scheduler@0.26.0:
resolution: {integrity: sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==}
signature_pad@2.3.2:
resolution: {integrity: sha512-peYXLxOsIY6MES2TrRLDiNg2T++8gGbpP2yaC+6Ohtxr+a2dzoaqWosWDY9sWqTAAk6E/TyQO+LJw9zQwyu5kA==}
tabbable@6.2.0:
resolution: {integrity: sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==}
trim-canvas@0.1.2:
resolution: {integrity: sha512-nd4Ga3iLFV94mdhW9JFMLpQbHUyCQuhFOD71PEAt1NjtMD5wbZctzhX8c3agHNybMR5zXD1XTGoIEWk995E6pQ==}
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
uncontrollable@7.2.1:
resolution: {integrity: sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==}
peerDependencies:
react: '>=15.0.0'
use-sync-external-store@1.5.0:
resolution: {integrity: sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==}
peerDependencies:
react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
warning@4.0.3:
resolution: {integrity: sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==}
snapshots:
'@babel/runtime@7.28.4': {}
'@floating-ui/core@1.7.3':
dependencies:
'@floating-ui/utils': 0.2.10
'@floating-ui/dom@1.7.4':
dependencies:
'@floating-ui/core': 1.7.3
'@floating-ui/utils': 0.2.10
'@floating-ui/react-dom@2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@floating-ui/dom': 1.7.4
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@floating-ui/react@0.26.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@floating-ui/react-dom': 2.1.6(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@floating-ui/utils': 0.2.10
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
tabbable: 6.2.0
'@floating-ui/utils@0.2.10': {}
'@headlessui/react@2.2.9(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@floating-ui/react': 0.26.28(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-aria/focus': 3.21.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-aria/interactions': 3.25.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@tanstack/react-virtual': 3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
use-sync-external-store: 1.5.0(react@19.1.1)
'@heroicons/react@2.2.0(react@19.1.1)':
dependencies:
react: 19.1.1
'@popperjs/core@2.11.8': {}
'@react-aria/focus@3.21.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@react-aria/interactions': 3.25.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-aria/utils': 3.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-types/shared': 3.32.0(react@19.1.1)
'@swc/helpers': 0.5.17
clsx: 2.1.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@react-aria/interactions@3.25.5(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.1.1)
'@react-aria/utils': 3.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@react-stately/flags': 3.1.2
'@react-types/shared': 3.32.0(react@19.1.1)
'@swc/helpers': 0.5.17
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@react-aria/ssr@3.9.10(react@19.1.1)':
dependencies:
'@swc/helpers': 0.5.17
react: 19.1.1
'@react-aria/utils@3.30.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@react-aria/ssr': 3.9.10(react@19.1.1)
'@react-stately/flags': 3.1.2
'@react-stately/utils': 3.10.8(react@19.1.1)
'@react-types/shared': 3.32.0(react@19.1.1)
'@swc/helpers': 0.5.17
clsx: 2.1.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@react-stately/flags@3.1.2':
dependencies:
'@swc/helpers': 0.5.17
'@react-stately/utils@3.10.8(react@19.1.1)':
dependencies:
'@swc/helpers': 0.5.17
react: 19.1.1
'@react-types/shared@3.32.0(react@19.1.1)':
dependencies:
react: 19.1.1
'@restart/hooks@0.4.16(react@19.1.1)':
dependencies:
dequal: 2.0.3
react: 19.1.1
'@swc/helpers@0.5.17':
dependencies:
tslib: 2.8.1
'@tanstack/react-virtual@3.13.12(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@tanstack/virtual-core': 3.13.12
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
'@tanstack/virtual-core@3.13.12': {}
'@types/react@19.1.16':
dependencies:
csstype: 3.1.3
'@types/signature_pad@2.3.6': {}
'@types/warning@3.0.3': {}
clsx@1.2.1: {}
clsx@2.1.1: {}
csstype@3.1.3: {}
date-arithmetic@4.1.0: {}
date-fns@4.1.0: {}
dayjs@1.11.18: {}
dequal@2.0.3: {}
dom-helpers@5.2.1:
dependencies:
'@babel/runtime': 7.28.4
csstype: 3.1.3
globalize@0.1.1: {}
invariant@2.2.4:
dependencies:
loose-envify: 1.4.0
js-tokens@4.0.0: {}
lodash-es@4.17.21: {}
lodash@4.17.21: {}
loose-envify@1.4.0:
dependencies:
js-tokens: 4.0.0
luxon@3.7.2: {}
memoize-one@6.0.0: {}
moment-timezone@0.5.48:
dependencies:
moment: 2.30.1
moment@2.30.1: {}
object-assign@4.1.1: {}
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
object-assign: 4.1.1
react-is: 16.13.1
react-big-calendar@1.19.4(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.28.4
clsx: 1.2.1
date-arithmetic: 4.1.0
dayjs: 1.11.18
dom-helpers: 5.2.1
globalize: 0.1.1
invariant: 2.2.4
lodash: 4.17.21
lodash-es: 4.17.21
luxon: 3.7.2
memoize-one: 6.0.0
moment: 2.30.1
moment-timezone: 0.5.48
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
react-overlays: 5.2.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
uncontrollable: 7.2.1(react@19.1.1)
react-dom@19.1.1(react@19.1.1):
dependencies:
react: 19.1.1
scheduler: 0.26.0
react-is@16.13.1: {}
react-lifecycles-compat@3.0.4: {}
react-overlays@5.2.1(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.28.4
'@popperjs/core': 2.11.8
'@restart/hooks': 0.4.16(react@19.1.1)
'@types/warning': 3.0.3
dom-helpers: 5.2.1
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
uncontrollable: 7.2.1(react@19.1.1)
warning: 4.0.3
react-signature-canvas@1.1.0-alpha.2(@types/react@19.1.16)(prop-types@15.8.1)(react-dom@19.1.1(react@19.1.1))(react@19.1.1):
dependencies:
'@babel/runtime': 7.28.4
'@types/signature_pad': 2.3.6
prop-types: 15.8.1
react: 19.1.1
react-dom: 19.1.1(react@19.1.1)
signature_pad: 2.3.2
trim-canvas: 0.1.2
optionalDependencies:
'@types/react': 19.1.16
react@19.1.1: {}
scheduler@0.26.0: {}
signature_pad@2.3.2: {}
tabbable@6.2.0: {}
trim-canvas@0.1.2: {}
tslib@2.8.1: {}
uncontrollable@7.2.1(react@19.1.1):
dependencies:
'@babel/runtime': 7.28.4
'@types/react': 19.1.16
invariant: 2.2.4
react: 19.1.1
react-lifecycles-compat: 3.0.4
use-sync-external-store@1.5.0(react@19.1.1):
dependencies:
react: 19.1.1
warning@4.0.3:
dependencies:
loose-envify: 1.4.0

View File

@ -1,4 +1,118 @@
"use client"; "use client";
// Componente para editar rascunho
function EditableRascunhoItem(props: { laudo: any; idx: number; setLaudos: React.Dispatch<React.SetStateAction<any[]>> }) {
const { laudo, idx, setLaudos } = props;
const [editConteudo, setEditConteudo] = useState(laudo.conteudo);
const [editCid, setEditCid] = useState(laudo.cid);
const [editAssinatura, setEditAssinatura] = useState(laudo.assinatura);
const [isEditing, setIsEditing] = useState(false);
const sigCanvasRef = useRef<any>(null);
const handleSave = () => {
setLaudos((prev: any[]) => prev.map((l, i) => i === idx ? { ...l, conteudo: editConteudo, cid: editCid, assinatura: editAssinatura } : l));
setIsEditing(false);
};
const handleDelete = () => {
setLaudos((prev: any[]) => prev.filter((_, i) => i !== idx));
};
const handleSalvarLaudo = () => {
setLaudos((prev: any[]) => prev.map((l, i) => i === idx ? { ...l, conteudo: editConteudo, cid: editCid, status: 'finalizado' } : l));
setIsEditing(false);
};
return (
<li className="border rounded p-3 flex flex-col bg-yellow-50">
<span className="font-semibold">{laudo.paciente} - {laudo.cpf} - {laudo.idade} anos</span>
<span className="text-gray-500 text-sm">{laudo.data}</span>
{isEditing ? (
<>
<textarea
className="w-full p-2 border rounded mt-2"
value={editConteudo}
onChange={e => setEditConteudo(e.target.value)}
rows={4}
/>
<input
className="w-full p-2 border rounded mt-2"
value={editCid}
onChange={e => setEditCid(e.target.value)}
placeholder="CID"
/>
{/* Campo de assinatura do rascunho */}
<div className="mt-4">
<label className="block font-semibold mb-1">Assinatura:</label>
<canvas
ref={sigCanvasRef}
width={300}
height={100}
style={{ border: '1px solid #cbd5e1', borderRadius: 8, background: '#f8fafc', cursor: 'crosshair' }}
onMouseDown={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = true;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
}}
onMouseMove={e => {
const canvas = sigCanvasRef.current;
if (!canvas || !canvas.isDrawing) return;
const ctx = canvas.getContext('2d');
ctx.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
ctx.stroke();
}}
onMouseUp={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = false;
setEditAssinatura(canvas.toDataURL());
}}
onMouseLeave={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = false;
}}
/>
<button
className="px-3 py-1 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 text-sm mt-2"
type="button"
onClick={() => {
const canvas = sigCanvasRef.current;
if (canvas && canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
setEditAssinatura(null);
}}
>
Limpar Assinatura
</button>
{editAssinatura && (
<div className="mt-2">
<span className="text-xs text-gray-500">Assinatura salva.</span>
</div>
)}
</div>
<div className="flex gap-2 mt-4">
<button className="bg-green-600 text-white px-4 py-1 rounded hover:bg-green-700" onClick={handleSave}>Salvar Rascunho</button>
<button className="bg-blue-600 text-white px-4 py-1 rounded hover:bg-blue-700" onClick={handleSalvarLaudo}>Salvar como Laudo</button>
<button className="bg-red-600 text-white px-4 py-1 rounded hover:bg-red-700" onClick={handleDelete}>Excluir</button>
<button className="bg-gray-300 text-gray-700 px-4 py-1 rounded hover:bg-gray-400" onClick={() => setIsEditing(false)}>Cancelar</button>
</div>
</>
) : (
<>
<span className="mt-2">{laudo.conteudo}</span>
{laudo.cid && <span className="text-xs text-gray-500 mt-1">CID: {laudo.cid}</span>}
{laudo.assinatura && <img src={laudo.assinatura} alt="Assinatura" className="mt-2 h-8" />}
<div className="flex gap-2 mt-2 self-end">
<button className="bg-yellow-600 text-white px-3 py-1 rounded hover:bg-yellow-700 text-sm" onClick={() => setIsEditing(true)}>Editar</button>
<button className="bg-blue-600 text-white px-3 py-1 rounded hover:bg-blue-700 text-sm" onClick={handleSalvarLaudo}>Salvar como Laudo</button>
<button className="bg-red-600 text-white px-3 py-1 rounded hover:bg-red-700 text-sm" onClick={handleDelete}>Excluir</button>
</div>
</>
)}
</li>
);
}
import React, { useState, useRef } from "react"; import React, { useState, useRef } from "react";
import SignatureCanvas from "react-signature-canvas"; import SignatureCanvas from "react-signature-canvas";
@ -115,7 +229,7 @@ const ProfissionalPage = () => {
const [prescricoesMedicas, setPrescricoesMedicas] = useState<any[]>([]); const [prescricoesMedicas, setPrescricoesMedicas] = useState<any[]>([]);
const [examesSolicitados, setExamesSolicitados] = useState<any[]>([]); const [examesSolicitados, setExamesSolicitados] = useState<any[]>([]);
const [diagnosticos, setDiagnosticos] = useState<any[]>([]); const [diagnosticos, setDiagnosticos] = useState<any[]>([]);
const [evolucaoQuadro, setEvolucaoQuadro] = useState<any[]>([]); const [activeTab, setActiveTab] = useState<'laudos' | 'rascunhos'>('rascunhos');
const [anexos, setAnexos] = useState<any[]>([]); const [anexos, setAnexos] = useState<any[]>([]);
const [abaProntuarioAtiva, setAbaProntuarioAtiva] = useState('nova-consulta'); const [abaProntuarioAtiva, setAbaProntuarioAtiva] = useState('nova-consulta');
@ -1514,6 +1628,14 @@ const ProfissionalPage = () => {
); );
// --- LaudoEditor COMPONENT --- // --- LaudoEditor COMPONENT ---
function LaudoEditor() { function LaudoEditor() {
const [showHistorico, setShowHistorico] = useState(false);
const [file, setFile] = useState<File | null>(null);
function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
if (e.target.files && e.target.files[0]) {
setFile(e.target.files[0]);
}
}
const [conteudo, setConteudo] = useState(""); const [conteudo, setConteudo] = useState("");
const [paciente, setPaciente] = useState(""); const [paciente, setPaciente] = useState("");
const [cpf, setCpf] = useState(""); const [cpf, setCpf] = useState("");
@ -1526,6 +1648,14 @@ function LaudoEditor() {
const [laudos, setLaudos] = useState<any[]>([]); const [laudos, setLaudos] = useState<any[]>([]);
const [preview, setPreview] = useState(false); const [preview, setPreview] = useState(false);
// Função para selecionar paciente e preencher dados automaticamente
function handleSelectPaciente(p: { nome: string; cpf: string; idade: string; sexo?: string }) {
setPaciente(p.nome);
setCpf(p.cpf);
setIdade(p.idade);
setSexo(p.sexo || "");
}
const salvarLaudo = (status: string) => { const salvarLaudo = (status: string) => {
const novoLaudo = { const novoLaudo = {
paciente, paciente,
@ -1536,127 +1666,270 @@ function LaudoEditor() {
conteudo, conteudo,
imagem, imagem,
assinatura, assinatura,
arquivo: file,
data: new Date().toLocaleString(), data: new Date().toLocaleString(),
status status
}; };
setLaudos(prev => [novoLaudo, ...prev]); setLaudos(prev => [novoLaudo, ...prev]);
setPaciente(""); setCpf(""); setIdade(""); setSexo(""); setCid(""); setConteudo(""); setImagem(null); setAssinatura(null); setPaciente(""); setCpf(""); setIdade(""); setSexo(""); setCid(""); setConteudo(""); setImagem(null); setAssinatura(null); setFile(null);
if (sigCanvasRef.current) sigCanvasRef.current.clear(); if (sigCanvasRef.current && sigCanvasRef.current.getContext) {
const ctx = sigCanvasRef.current.getContext('2d');
ctx.clearRect(0, 0, sigCanvasRef.current.width, sigCanvasRef.current.height);
}
}; };
return ( return (
<div className="min-h-screen bg-muted p-6"> <div className="min-h-screen bg-muted p-6">
<div className="flex flex-col md:flex-row gap-6"> {/* Se ainda não selecionou paciente, mostra seleção */}
<div className="bg-white p-6 rounded-lg shadow-md w-full md:w-1/3 flex flex-col gap-4"> {!paciente ? (
<h2 className="text-2xl font-bold text-primary text-center mb-4">Informações do Paciente</h2> <div className="bg-white rounded-xl shadow p-8 flex flex-col items-center">
<input type="text" placeholder="Nome do paciente" value={paciente} onChange={e => setPaciente(e.target.value)} className="p-3 border rounded-md focus:ring-2 focus:ring-primary/50 focus:outline-none"/> <span className="text-6xl text-gray-400 mb-2">
<input type="text" placeholder="CPF" value={cpf} onChange={e => setCpf(e.target.value)} className="p-3 border rounded-md focus:ring-2 focus:ring-primary/50 focus:outline-none"/> <svg width="56" height="56" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4" stroke="currentColor" strokeWidth="2"/><path d="M4 20c0-2.5 3.5-4.5 8-4.5s8 2 8 4.5" stroke="currentColor" strokeWidth="2"/></svg>
<input type="number" placeholder="Idade" value={idade} onChange={e => setIdade(e.target.value)} className="p-3 border rounded-md focus:ring-2 focus:ring-primary/50 focus:outline-none"/> </span>
<select value={sexo} onChange={e => setSexo(e.target.value)} className="p-3 border rounded-md focus:ring-2 focus:ring-primary/50 focus:outline-none"> <h2 className="text-2xl font-bold text-center mb-2">Selecionar Paciente</h2>
<option value="">Sexo</option> <p className="text-gray-600 text-center mb-6">Escolha um paciente para visualizar o prontuário completo</p>
<option value="Masculino">Masculino</option> <div className="w-full max-w-xl mb-8">
<option value="Feminino">Feminino</option> <label htmlFor="select-paciente" className="block font-semibold mb-1">Escolha o paciente:</label>
<option value="Outro">Outro</option> <select
id="select-paciente"
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-200 focus:border-blue-300 focus:outline-none bg-white text-base text-gray-900 shadow-sm transition-all appearance-none"
onChange={e => {
const p = pacientes.find(p => p.cpf === e.target.value);
if (p) handleSelectPaciente({ ...p, idade: String(p.idade) });
}}
defaultValue=""
>
<option value="" className="text-gray-400">Selecione um paciente...</option>
{pacientes.map(p => (
<option key={p.cpf} value={p.cpf}>{p.nome} - {p.cpf}</option>
))}
</select> </select>
<input type="text" placeholder="CID" value={cid} onChange={e => setCid(e.target.value)} className="p-3 border rounded-md focus:ring-2 focus:ring-primary/50 focus:outline-none"/>
<div>
<label className="block mb-1 font-medium text-primary">Imagem (opcional)</label>
<input type="file" accept="image/*" onChange={e => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => setImagem(reader.result as string);
reader.readAsDataURL(file);
} else {
setImagem(null);
}
}} className="block w-full text-sm text-muted-foreground file:mr-4 file:py-2 file:px-4 file:rounded-md file:border-0 file:text-sm file:font-semibold file:bg-primary/10 file:text-primary hover:file:bg-primary/20" />
{imagem && (
<img src={imagem} alt="Pré-visualização" className="mt-2 rounded-md max-h-32 border" />
)}
</div> </div>
<div> <div className="w-full max-w-5xl">
<label className="block mb-1 font-medium text-primary">Assinatura Digital</label> <h3 className="text-xl font-bold mb-4">Ou selecione rapidamente:</h3>
<div className="bg-muted rounded-md border p-2 flex flex-col items-center"> <div className="flex flex-col md:flex-row gap-4">
<SignatureCanvas {pacientes.map((p, idx) => (
ref={sigCanvasRef} <div key={p.cpf} className="flex-1 bg-white border rounded-xl shadow p-6 flex flex-col items-start gap-2 hover:border-primary cursor-pointer transition-all"
penColor="#0f172a" onClick={() => handleSelectPaciente({ ...p, idade: String(p.idade) })}
backgroundColor="#fff" style={{ minWidth: 220 }}
canvasProps={{ width: 300, height: 100, className: "rounded border bg-white" }} >
onEnd={() => setAssinatura(sigCanvasRef.current?.isEmpty() ? null : sigCanvasRef.current?.toDataURL())} <span className="text-4xl text-blue-400 mb-2">
<svg width="32" height="32" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4" stroke="currentColor" strokeWidth="2"/><path d="M4 20c0-2.5 3.5-4.5 8-4.5s8 2 8 4.5" stroke="currentColor" strokeWidth="2"/></svg>
</span>
<span className="font-bold text-lg">{p.nome}</span>
<span className="text-gray-600 text-base">CPF: {p.cpf}</span>
<span className="text-gray-500 text-base">{p.idade} anos</span>
</div>
))}
</div>
</div>
</div>
) : (
// Após selecionar paciente, mostra o editor de laudo completo
<div className="bg-white rounded-xl shadow p-8 flex flex-col items-center w-full max-w-4xl mx-auto">
<div className="w-full mb-6 flex flex-col items-center">
<span className="text-4xl text-blue-400 mb-2">
<svg width="32" height="32" fill="none" viewBox="0 0 24 24"><circle cx="12" cy="8" r="4" stroke="currentColor" strokeWidth="2"/><path d="M4 20c0-2.5 3.5-4.5 8-4.5s8 2 8 4.5" stroke="currentColor" strokeWidth="2"/></svg>
</span>
<span className="font-bold text-lg">{paciente}</span>
<span className="text-gray-600 text-base">CPF: {cpf}</span>
<span className="text-gray-500 text-base">{idade} anos</span>
{sexo && <span className="text-gray-500 text-base">Sexo: {sexo}</span>}
<button
className="mt-3 px-4 py-2 bg-gray-100 text-blue-700 border border-blue-200 rounded-lg font-medium hover:bg-blue-50 hover:text-blue-900 transition-all shadow-sm"
onClick={() => { setPaciente(""); setCpf(""); setIdade(""); setSexo(""); }}
>
Trocar paciente
</button>
</div>
{/* Campo de CID */}
<div className="w-full mb-4">
<label className="block font-semibold mb-1">CID:</label>
<input
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-200 focus:border-blue-300 focus:outline-none bg-white text-base text-gray-900 shadow-sm transition-all appearance-none"
value={cid}
onChange={e => setCid(e.target.value)}
placeholder="Ex: I10, E11, etc."
/> />
<div className="flex gap-2 mt-2"> </div>
<button type="button" onClick={() => { sigCanvasRef.current?.clear(); setAssinatura(null); }} className="px-3 py-1 text-xs rounded bg-muted-foreground text-white hover:bg-muted">Limpar</button> {/* Editor de laudo */}
<button type="button" onClick={() => setAssinatura(sigCanvasRef.current?.toDataURL())} className="px-3 py-1 text-xs rounded bg-primary text-primary-foreground hover:bg-primary/90">Salvar Assinatura</button> <div className="w-full mb-4">
<label className="block font-semibold mb-1">Conteúdo do Laudo:</label>
<textarea
className="w-full p-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-200 focus:border-blue-300 focus:outline-none bg-white text-base text-gray-900 shadow-sm transition-all appearance-none"
rows={6}
value={conteudo}
onChange={e => setConteudo(e.target.value)}
placeholder="Digite o conteúdo do laudo aqui..."
/>
</div>
{/* Campo de assinatura */}
<div className="w-full mb-6">
<label className="block font-semibold mb-1">Assinatura do Profissional:</label>
<div className="flex items-center gap-4">
<canvas
ref={sigCanvasRef}
width={300}
height={100}
style={{ border: '1px solid #cbd5e1', borderRadius: 8, background: '#f8fafc', cursor: 'crosshair' }}
onMouseDown={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = true;
const ctx = canvas.getContext('2d');
ctx.beginPath();
ctx.moveTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
}}
onMouseMove={e => {
const canvas = sigCanvasRef.current;
if (!canvas || !canvas.isDrawing) return;
const ctx = canvas.getContext('2d');
ctx.lineTo(e.nativeEvent.offsetX, e.nativeEvent.offsetY);
ctx.stroke();
}}
onMouseUp={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = false;
// Salva a assinatura como imagem base64
setAssinatura(canvas.toDataURL());
}}
onMouseLeave={e => {
const canvas = sigCanvasRef.current;
if (!canvas) return;
canvas.isDrawing = false;
}}
/>
<button
className="px-3 py-1 bg-gray-200 text-gray-700 rounded hover:bg-gray-300 text-sm"
type="button"
onClick={() => {
const canvas = sigCanvasRef.current;
if (canvas && canvas.getContext) {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
setAssinatura(null);
}}
>
Limpar
</button>
</div> </div>
{assinatura && ( {assinatura && (
<img src={assinatura} alt="Assinatura" className="mt-2 max-h-16 border rounded bg-white" /> <div className="mt-2">
)} <span className="text-xs text-gray-500">Assinatura salva.</span>
</div>
</div>
<div className="flex gap-2 mt-4">
<button type="button" onClick={() => salvarLaudo("Rascunho")} className="w-1/2 bg-muted-foreground text-white py-2 rounded-md hover:bg-muted">Salvar Rascunho</button>
<button type="button" onClick={() => salvarLaudo("Entregue")} className="w-1/2 bg-primary text-primary-foreground py-2 rounded-md hover:bg-primary/90">Liberar Laudo</button>
</div>
<button type="button" onClick={() => setPreview(!preview)} className="mt-2 w-full bg-primary text-primary-foreground py-2 rounded-md hover:bg-primary/90">Pré-visualizar Laudo</button>
</div>
<div className="bg-white p-6 rounded-lg shadow-md w-full md:w-2/3 flex flex-col gap-4">
<h2 className="text-2xl font-bold text-primary text-center mb-4">Editor de Laudo</h2>
{!preview ? (
<ReactQuill value={conteudo} onChange={setConteudo} modules={{
toolbar: [
['bold', 'italic', 'underline'],
[{'list': 'ordered'}, {'list': 'bullet'}],
[{'align': []}],
[{'size': ['small', false, 'large', 'huge']}],
['clean']
]
}} className="h-64"/>
) : (
<div className="border p-4 rounded-md bg-muted overflow-auto">
<h3 className="text-lg font-semibold mb-2">Pré-visualização:</h3>
<div dangerouslySetInnerHTML={{__html: conteudo}} />
</div> </div>
)} )}
<div className="mt-6"> </div>
<h3 className="text-xl font-bold text-primary mb-4 text-center">Histórico de Laudos</h3> {/* Botão para salvar laudo */}
{/* Upload de arquivos */}
<div className="w-full mb-4 flex flex-col gap-2">
<label className="block font-semibold mb-1">Anexar Arquivo:</label>
<div className="flex gap-2 items-center">
<input
id="upload-arquivo"
type="file"
onChange={handleFileChange}
className="hidden"
/>
<label htmlFor="upload-arquivo">
<button
type="button"
className="flex items-center gap-2 bg-blue-600 text-white px-6 py-2 rounded-lg font-semibold hover:bg-blue-700 transition-all shadow"
>
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 16v2a2 2 0 002 2h12a2 2 0 002-2v-2M7 10l5 5m0 0l5-5m-5 5V4" /></svg>
Selecionar Arquivo
</button>
</label>
{file && <span className="ml-2 text-sm text-gray-600">{file.name}</span>}
</div>
{/* Pré-visualização do laudo */}
<div className="w-full my-6 p-4 bg-gray-50 border rounded-lg shadow">
<h3 className="text-lg font-bold mb-2 text-blue-700">Pré-visualização do Laudo</h3>
<div className="mb-1"><span className="font-semibold">Paciente:</span> {paciente}</div>
<div className="mb-1"><span className="font-semibold">CPF:</span> {cpf}</div>
<div className="mb-1"><span className="font-semibold">Idade:</span> {idade}</div>
<div className="mb-1"><span className="font-semibold">Sexo:</span> {sexo}</div>
<div className="mb-1"><span className="font-semibold">CID:</span> {cid}</div>
<div className="mb-1"><span className="font-semibold">Conteúdo:</span> <span className="whitespace-pre-line">{conteudo}</span></div>
{assinatura && <div className="mb-1"><span className="font-semibold">Assinatura:</span> <img src={assinatura} alt="Assinatura" className="inline-block border rounded h-12 align-middle ml-2" /></div>}
{file && <div className="mb-1"><span className="font-semibold">Arquivo:</span> {file.name}</div>}
</div>
</div>
{/* Botão para salvar laudo */}
<div className="w-full flex justify-end gap-4">
<button
className="flex items-center gap-2 bg-blue-600 text-white px-6 py-2 rounded-lg font-semibold hover:bg-blue-700 transition-all shadow"
onClick={() => salvarLaudo("finalizado")}
type="button"
>
Salvar Laudo
</button>
<button
className="flex items-center gap-2 bg-yellow-500 text-white px-6 py-2 rounded-lg font-semibold hover:bg-yellow-600 transition-all shadow"
onClick={() => setShowHistorico(true)}
type="button"
>
Histórico de Laudos
</button>
</div>
{/* Modal de histórico de laudos */}
{showHistorico && (
<div className="fixed inset-0 bg-black bg-opacity-40 flex justify-center items-center z-50">
<div className="bg-white p-8 rounded-xl shadow-2xl w-full max-w-lg border border-gray-200">
<h2 className="text-2xl font-bold mb-6 text-blue-700 flex items-center gap-2">
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-blue-500" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M8 17l4 4 4-4m-4-5v9" /></svg>
Histórico de Laudos
</h2>
{laudos.length === 0 ? ( {laudos.length === 0 ? (
<p className="text-muted-foreground text-center">Nenhum laudo registrado.</p> <p className="text-gray-500">Nenhum laudo salvo.</p>
) : ( ) : (
laudos.map((laudo: any, idx: number) => ( <ul className="space-y-4 max-h-80 overflow-y-auto">
<div key={idx} className="border border-primary/20 rounded-lg p-4 mb-4 bg-muted shadow-sm"> {laudos.map((laudo, idx) => (
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2"> <li key={idx} className="border rounded-lg p-4 bg-gray-50 shadow flex flex-col gap-2">
<p className="font-semibold text-primary-foreground">{laudo.paciente}</p> <div className="flex justify-between items-center">
<p className="text-muted-foreground">CPF: {laudo.cpf}</p> <span className="font-bold text-lg text-gray-800">{laudo.paciente} - {laudo.cpf}</span>
<p className="text-muted-foreground">Idade: {laudo.idade}</p> <span className="text-xs text-gray-500">{laudo.data || new Date().toLocaleDateString()}</span>
<p className="text-muted-foreground">Sexo: {laudo.sexo}</p>
<p className="text-muted-foreground">CID: {laudo.cid}</p>
<p className="text-muted-foreground">Status: {laudo.status}</p>
<p className="text-muted-foreground">Data: {laudo.data}</p>
</div> </div>
{laudo.imagem && ( <div className="text-sm text-gray-600 mb-1">CID: <span className="font-semibold">{laudo.cid}</span></div>
<div className="mb-2"> <div className="text-base text-gray-700 whitespace-pre-line">{laudo.conteudo}</div>
<p className="font-semibold text-primary">Imagem:</p> {laudo.arquivo && (
<img src={laudo.imagem} alt="Imagem do laudo" className="rounded-md max-h-32 border mb-2" /> <div className="flex items-center gap-2 mt-2">
<span className="text-xs text-blue-600">Arquivo: {laudo.arquivo.name}</span>
<button
className="px-2 py-1 bg-green-600 text-white rounded text-xs hover:bg-green-700"
onClick={() => {
const url = URL.createObjectURL(laudo.arquivo);
const link = document.createElement('a');
link.href = url;
link.download = laudo.arquivo.name;
link.click();
setTimeout(() => URL.revokeObjectURL(url), 1000);
}}
>
Baixar PDF
</button>
</div> </div>
)} )}
{laudo.assinatura && ( </li>
<div className="mb-2"> ))}
<p className="font-semibold text-primary">Assinatura Digital:</p> </ul>
<img src={laudo.assinatura} alt="Assinatura digital" className="rounded-md max-h-16 border bg-white" /> )}
<button
className="mt-6 px-6 py-2 bg-gray-300 rounded-lg hover:bg-gray-400 text-lg font-semibold"
onClick={() => setShowHistorico(false)}
>
Fechar
</button>
</div>
</div> </div>
)} )}
<div className="mb-2">
<p className="font-semibold text-primary">Conteúdo:</p>
<div dangerouslySetInnerHTML={{__html: laudo.conteudo}}/>
</div> </div>
</div>
))
)} )}
</div> </div>
</div>
</div>
</div>
); );
} }