feat: atualiza laudos, upload, histórico, botão coeso, preview
This commit is contained in:
parent
c56cd9ff63
commit
80aa1c3401
2
.next/trace
Normal file
2
.next/trace
Normal 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"}]
|
||||
@ -1,9 +1,9 @@
|
||||
{
|
||||
"dependencies": {
|
||||
"@headlessui/react": "^2.2.7",
|
||||
"@headlessui/react": "^2.2.9",
|
||||
"@heroicons/react": "^2.2.0",
|
||||
"date-fns": "^4.1.0",
|
||||
"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
520
pnpm-lock.yaml
generated
Normal 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
|
||||
@ -1,4 +1,118 @@
|
||||
"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 SignatureCanvas from "react-signature-canvas";
|
||||
@ -115,7 +229,7 @@ const ProfissionalPage = () => {
|
||||
const [prescricoesMedicas, setPrescricoesMedicas] = useState<any[]>([]);
|
||||
const [examesSolicitados, setExamesSolicitados] = 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 [abaProntuarioAtiva, setAbaProntuarioAtiva] = useState('nova-consulta');
|
||||
|
||||
@ -1514,6 +1628,14 @@ const ProfissionalPage = () => {
|
||||
);
|
||||
// --- LaudoEditor COMPONENT ---
|
||||
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 [paciente, setPaciente] = useState("");
|
||||
const [cpf, setCpf] = useState("");
|
||||
@ -1526,6 +1648,14 @@ function LaudoEditor() {
|
||||
const [laudos, setLaudos] = useState<any[]>([]);
|
||||
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 novoLaudo = {
|
||||
paciente,
|
||||
@ -1536,126 +1666,269 @@ function LaudoEditor() {
|
||||
conteudo,
|
||||
imagem,
|
||||
assinatura,
|
||||
arquivo: file,
|
||||
data: new Date().toLocaleString(),
|
||||
status
|
||||
};
|
||||
setLaudos(prev => [novoLaudo, ...prev]);
|
||||
setPaciente(""); setCpf(""); setIdade(""); setSexo(""); setCid(""); setConteudo(""); setImagem(null); setAssinatura(null);
|
||||
if (sigCanvasRef.current) sigCanvasRef.current.clear();
|
||||
setPaciente(""); setCpf(""); setIdade(""); setSexo(""); setCid(""); setConteudo(""); setImagem(null); setAssinatura(null); setFile(null);
|
||||
if (sigCanvasRef.current && sigCanvasRef.current.getContext) {
|
||||
const ctx = sigCanvasRef.current.getContext('2d');
|
||||
ctx.clearRect(0, 0, sigCanvasRef.current.width, sigCanvasRef.current.height);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-muted p-6">
|
||||
<div className="flex flex-col md:flex-row gap-6">
|
||||
<div className="bg-white p-6 rounded-lg shadow-md w-full md:w-1/3 flex flex-col gap-4">
|
||||
<h2 className="text-2xl font-bold text-primary text-center mb-4">Informações do Paciente</h2>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
<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">
|
||||
<option value="">Sexo</option>
|
||||
<option value="Masculino">Masculino</option>
|
||||
<option value="Feminino">Feminino</option>
|
||||
<option value="Outro">Outro</option>
|
||||
</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" />
|
||||
)}
|
||||
{/* Se ainda não selecionou paciente, mostra seleção */}
|
||||
{!paciente ? (
|
||||
<div className="bg-white rounded-xl shadow p-8 flex flex-col items-center">
|
||||
<span className="text-6xl text-gray-400 mb-2">
|
||||
<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>
|
||||
</span>
|
||||
<h2 className="text-2xl font-bold text-center mb-2">Selecionar Paciente</h2>
|
||||
<p className="text-gray-600 text-center mb-6">Escolha um paciente para visualizar o prontuário completo</p>
|
||||
<div className="w-full max-w-xl mb-8">
|
||||
<label htmlFor="select-paciente" className="block font-semibold mb-1">Escolha o paciente:</label>
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<label className="block mb-1 font-medium text-primary">Assinatura Digital</label>
|
||||
<div className="bg-muted rounded-md border p-2 flex flex-col items-center">
|
||||
<SignatureCanvas
|
||||
ref={sigCanvasRef}
|
||||
penColor="#0f172a"
|
||||
backgroundColor="#fff"
|
||||
canvasProps={{ width: 300, height: 100, className: "rounded border bg-white" }}
|
||||
onEnd={() => setAssinatura(sigCanvasRef.current?.isEmpty() ? null : sigCanvasRef.current?.toDataURL())}
|
||||
/>
|
||||
<div className="flex gap-2 mt-2">
|
||||
<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>
|
||||
<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>
|
||||
{assinatura && (
|
||||
<img src={assinatura} alt="Assinatura" className="mt-2 max-h-16 border rounded bg-white" />
|
||||
)}
|
||||
<div className="w-full max-w-5xl">
|
||||
<h3 className="text-xl font-bold mb-4">Ou selecione rapidamente:</h3>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
{pacientes.map((p, idx) => (
|
||||
<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"
|
||||
onClick={() => handleSelectPaciente({ ...p, idade: String(p.idade) })}
|
||||
style={{ minWidth: 220 }}
|
||||
>
|
||||
<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 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}} />
|
||||
) : (
|
||||
// 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>
|
||||
{/* Editor de laudo */}
|
||||
<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>
|
||||
{assinatura && (
|
||||
<div className="mt-2">
|
||||
<span className="text-xs text-gray-500">Assinatura salva.</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{/* 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 ? (
|
||||
<p className="text-gray-500">Nenhum laudo salvo.</p>
|
||||
) : (
|
||||
<ul className="space-y-4 max-h-80 overflow-y-auto">
|
||||
{laudos.map((laudo, idx) => (
|
||||
<li key={idx} className="border rounded-lg p-4 bg-gray-50 shadow flex flex-col gap-2">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="font-bold text-lg text-gray-800">{laudo.paciente} - {laudo.cpf}</span>
|
||||
<span className="text-xs text-gray-500">{laudo.data || new Date().toLocaleDateString()}</span>
|
||||
</div>
|
||||
<div className="text-sm text-gray-600 mb-1">CID: <span className="font-semibold">{laudo.cid}</span></div>
|
||||
<div className="text-base text-gray-700 whitespace-pre-line">{laudo.conteudo}</div>
|
||||
{laudo.arquivo && (
|
||||
<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>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
<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 className="mt-6">
|
||||
<h3 className="text-xl font-bold text-primary mb-4 text-center">Histórico de Laudos</h3>
|
||||
{laudos.length === 0 ? (
|
||||
<p className="text-muted-foreground text-center">Nenhum laudo registrado.</p>
|
||||
) : (
|
||||
laudos.map((laudo: any, idx: number) => (
|
||||
<div key={idx} className="border border-primary/20 rounded-lg p-4 mb-4 bg-muted shadow-sm">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-2">
|
||||
<p className="font-semibold text-primary-foreground">{laudo.paciente}</p>
|
||||
<p className="text-muted-foreground">CPF: {laudo.cpf}</p>
|
||||
<p className="text-muted-foreground">Idade: {laudo.idade}</p>
|
||||
<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>
|
||||
{laudo.imagem && (
|
||||
<div className="mb-2">
|
||||
<p className="font-semibold text-primary">Imagem:</p>
|
||||
<img src={laudo.imagem} alt="Imagem do laudo" className="rounded-md max-h-32 border mb-2" />
|
||||
</div>
|
||||
)}
|
||||
{laudo.assinatura && (
|
||||
<div className="mb-2">
|
||||
<p className="font-semibold text-primary">Assinatura Digital:</p>
|
||||
<img src={laudo.assinatura} alt="Assinatura digital" className="rounded-md max-h-16 border bg-white" />
|
||||
</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>
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user