diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..963283f
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,576 @@
+{
+ "name": "riseup-squad20",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "dependencies": {
+ "@headlessui/react": "^2.2.7",
+ "@heroicons/react": "^2.2.0",
+ "date-fns": "^4.1.0",
+ "react-big-calendar": "^1.19.4"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react": {
+ "version": "0.26.28",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react/-/react-0.26.28.tgz",
+ "integrity": "sha512-yORQuuAtVpiRjpMhdc0wJj06b9JFjrYF4qp96j++v2NBpbi6SEGF7donUJ3TMieerQ6qVkAv1tgr7L4r5roTqw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.1.2",
+ "@floating-ui/utils": "^0.2.8",
+ "tabbable": "^6.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
+ "node_modules/@headlessui/react": {
+ "version": "2.2.7",
+ "resolved": "https://registry.npmjs.org/@headlessui/react/-/react-2.2.7.tgz",
+ "integrity": "sha512-WKdTymY8Y49H8/gUc/lIyYK1M+/6dq0Iywh4zTZVAaiTDprRfioxSgD0wnXTQTBpjpGJuTL1NO/mqEvc//5SSg==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react": "^0.26.16",
+ "@react-aria/focus": "^3.20.2",
+ "@react-aria/interactions": "^3.25.0",
+ "@tanstack/react-virtual": "^3.13.9",
+ "use-sync-external-store": "^1.5.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@heroicons/react": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@heroicons/react/-/react-2.2.0.tgz",
+ "integrity": "sha512-LMcepvRaS9LYHJGsF0zzmgKCUim/X3N/DQKc4jepAXJ7l8QxJ1PmxJzqplF2Z3FE4PqBAIGyJAQ/w4B5dsqbtQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
+ }
+ },
+ "node_modules/@popperjs/core": {
+ "version": "2.11.8",
+ "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
+ "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
+ "license": "MIT",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
+ }
+ },
+ "node_modules/@react-aria/focus": {
+ "version": "3.21.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/focus/-/focus-3.21.1.tgz",
+ "integrity": "sha512-hmH1IhHlcQ2lSIxmki1biWzMbGgnhdxJUM0MFfzc71Rv6YAzhlx4kX3GYn4VNcjCeb6cdPv4RZ5vunV4kgMZYQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/interactions": "^3.25.5",
+ "@react-aria/utils": "^3.30.1",
+ "@react-types/shared": "^3.32.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "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"
+ }
+ },
+ "node_modules/@react-aria/interactions": {
+ "version": "3.25.5",
+ "resolved": "https://registry.npmjs.org/@react-aria/interactions/-/interactions-3.25.5.tgz",
+ "integrity": "sha512-EweYHOEvMwef/wsiEqV73KurX/OqnmbzKQa2fLxdULbec5+yDj6wVGaRHIzM4NiijIDe+bldEl5DG05CAKOAHA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.10",
+ "@react-aria/utils": "^3.30.1",
+ "@react-stately/flags": "^3.1.2",
+ "@react-types/shared": "^3.32.0",
+ "@swc/helpers": "^0.5.0"
+ },
+ "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"
+ }
+ },
+ "node_modules/@react-aria/ssr": {
+ "version": "3.9.10",
+ "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.10.tgz",
+ "integrity": "sha512-hvTm77Pf+pMBhuBm760Li0BVIO38jv1IBws1xFm1NoL26PU+fe+FMW5+VZWyANR6nYL65joaJKZqOdTQMkO9IQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-aria/utils": {
+ "version": "3.30.1",
+ "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.30.1.tgz",
+ "integrity": "sha512-zETcbDd6Vf9GbLndO6RiWJadIZsBU2MMm23rBACXLmpRztkrIqPEb2RVdlLaq1+GklDx0Ii6PfveVjx+8S5U6A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@react-aria/ssr": "^3.9.10",
+ "@react-stately/flags": "^3.1.2",
+ "@react-stately/utils": "^3.10.8",
+ "@react-types/shared": "^3.32.0",
+ "@swc/helpers": "^0.5.0",
+ "clsx": "^2.0.0"
+ },
+ "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"
+ }
+ },
+ "node_modules/@react-stately/flags": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@react-stately/flags/-/flags-3.1.2.tgz",
+ "integrity": "sha512-2HjFcZx1MyQXoPqcBGALwWWmgFVUk2TuKVIQxCbRq7fPyWXIl6VHcakCLurdtYC2Iks7zizvz0Idv48MQ38DWg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ }
+ },
+ "node_modules/@react-stately/utils": {
+ "version": "3.10.8",
+ "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.8.tgz",
+ "integrity": "sha512-SN3/h7SzRsusVQjQ4v10LaVsDc81jyyR0DD5HnsQitm/I5WDpaSr2nRHtyloPFU48jlql1XX/S04T2DLQM7Y3g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@swc/helpers": "^0.5.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@react-types/shared": {
+ "version": "3.32.0",
+ "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.32.0.tgz",
+ "integrity": "sha512-t+cligIJsZYFMSPFMvsJMjzlzde06tZMOIOFa1OV5Z0BcMowrb2g4mB57j/9nP28iJIRYn10xCniQts+qadrqQ==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1"
+ }
+ },
+ "node_modules/@restart/hooks": {
+ "version": "0.4.16",
+ "resolved": "https://registry.npmjs.org/@restart/hooks/-/hooks-0.4.16.tgz",
+ "integrity": "sha512-f7aCv7c+nU/3mF7NWLtVVr0Ra80RqsO89hO72r+Y/nvQr5+q0UFGkocElTH6MJApvReVh6JHUFYn2cw1WdHF3w==",
+ "license": "MIT",
+ "dependencies": {
+ "dequal": "^2.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.17",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.17.tgz",
+ "integrity": "sha512-5IKx/Y13RsYd+sauPb2x+U/xZikHjolzfuDgTAl/Tdf3Q8rslRvC19NKDLgAJQ6wsqADk10ntlv08nPFw/gO/A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tanstack/react-virtual": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.13.12.tgz",
+ "integrity": "sha512-Gd13QdxPSukP8ZrkbgS2RwoZseTTbQPLnQEn7HY/rqtM+8Zt95f7xKC7N0EsKs7aoz0WzZ+fditZux+F8EzYxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.13.12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "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"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.13.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.13.12.tgz",
+ "integrity": "sha512-1YBOJfRHV4sXUmWsFSf5rQor4Ss82G8dQWLRbnk3GA4jeP8hQt1hxXh0tmflpC0dz3VgEv/1+qwPyLeWkQuPFA==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.1.12",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.12.tgz",
+ "integrity": "sha512-cMoR+FoAf/Jyq6+Df2/Z41jISvGZZ2eTlnsaJRptmZ76Caldwy1odD4xTr/gNV9VLj0AWgg/nmkevIyUfIIq5w==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@types/warning": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/warning/-/warning-3.0.3.tgz",
+ "integrity": "sha512-D1XC7WK8K+zZEveUPY+cf4+kgauk8N4eHr/XIHXGlGYkHLud6hK9lYfZk1ry1TNh798cZUCgb6MqGEG8DkJt6Q==",
+ "license": "MIT"
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
+ },
+ "node_modules/date-arithmetic": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-arithmetic/-/date-arithmetic-4.1.0.tgz",
+ "integrity": "sha512-QWxYLR5P/6GStZcdem+V1xoto6DMadYWpMXU82ES3/RfR3Wdwr3D0+be7mgOJ+Ov0G9D5Dmb9T17sNLQYj9XOg==",
+ "license": "MIT"
+ },
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/dayjs": {
+ "version": "1.11.18",
+ "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz",
+ "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==",
+ "license": "MIT"
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/dom-helpers": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
+ "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.8.7",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/globalize": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/globalize/-/globalize-0.1.1.tgz",
+ "integrity": "sha512-5e01v8eLGfuQSOvx2MsDMOWS0GFtCx1wPzQSmcHw4hkxFzrQDBO3Xwg/m8Hr/7qXMrHeOIE29qWVzyv06u1TZA=="
+ },
+ "node_modules/invariant": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz",
+ "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/luxon": {
+ "version": "3.7.2",
+ "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz",
+ "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/memoize-one": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
+ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==",
+ "license": "MIT"
+ },
+ "node_modules/moment": {
+ "version": "2.30.1",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
+ "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.48",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.48.tgz",
+ "integrity": "sha512-f22b8LV1gbTO2ms2j2z13MuPogNoh5UzxL3nzNAYKGraILnbGc9NEE6dyiiiLv46DGRb8A4kg8UKWLjPthxBHw==",
+ "license": "MIT",
+ "dependencies": {
+ "moment": "^2.29.4"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
+ "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-big-calendar": {
+ "version": "1.19.4",
+ "resolved": "https://registry.npmjs.org/react-big-calendar/-/react-big-calendar-1.19.4.tgz",
+ "integrity": "sha512-FrvbDx2LF6JAWFD96LU1jjloppC5OgIvMYUYIPzAw5Aq+ArYFPxAjLqXc4DyxfsQDN0TJTMuS/BIbcSB7Pg0YA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.20.7",
+ "clsx": "^1.2.1",
+ "date-arithmetic": "^4.1.0",
+ "dayjs": "^1.11.7",
+ "dom-helpers": "^5.2.1",
+ "globalize": "^0.1.1",
+ "invariant": "^2.2.4",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "luxon": "^3.2.1",
+ "memoize-one": "^6.0.0",
+ "moment": "^2.29.4",
+ "moment-timezone": "^0.5.40",
+ "prop-types": "^15.8.1",
+ "react-overlays": "^5.2.1",
+ "uncontrollable": "^7.2.1"
+ },
+ "peerDependencies": {
+ "react": "^16.14.0 || ^17 || ^18 || ^19",
+ "react-dom": "^16.14.0 || ^17 || ^18 || ^19"
+ }
+ },
+ "node_modules/react-big-calendar/node_modules/clsx": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz",
+ "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
+ "integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "scheduler": "^0.26.0"
+ },
+ "peerDependencies": {
+ "react": "^19.1.1"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/react-lifecycles-compat": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
+ "integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==",
+ "license": "MIT"
+ },
+ "node_modules/react-overlays": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/react-overlays/-/react-overlays-5.2.1.tgz",
+ "integrity": "sha512-GLLSOLWr21CqtJn8geSwQfoJufdt3mfdsnIiQswouuQ2MMPns+ihZklxvsTDKD3cR2tF8ELbi5xUsvqVhR6WvA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.13.8",
+ "@popperjs/core": "^2.11.6",
+ "@restart/hooks": "^0.4.7",
+ "@types/warning": "^3.0.0",
+ "dom-helpers": "^5.2.0",
+ "prop-types": "^15.7.2",
+ "uncontrollable": "^7.2.1",
+ "warning": "^4.0.3"
+ },
+ "peerDependencies": {
+ "react": ">=16.3.0",
+ "react-dom": ">=16.3.0"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.26.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.26.0.tgz",
+ "integrity": "sha512-NlHwttCI/l5gCPR3D1nNXtWABUmBwvZpEQiD4IXSbIDq8BzLIK/7Ir5gTFSGZDUu37K5cMNp0hFtzO38sC7gWA==",
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/tabbable": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.2.0.tgz",
+ "integrity": "sha512-Cat63mxsVJlzYvN51JmVXIgNoUokrIaT2zLclCXjRd8boZ0004U4KCs/sToJ75C6sdlByWxpYnb5Boif1VSFew==",
+ "license": "MIT"
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/uncontrollable": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/uncontrollable/-/uncontrollable-7.2.1.tgz",
+ "integrity": "sha512-svtcfoTADIB0nT9nltgjujTi7BzVmwjZClOmskKu/E8FW9BXzg9os8OLr4f8Dlnk0rYWJIWr4wv9eKUXiQvQwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.6.3",
+ "@types/react": ">=16.9.11",
+ "invariant": "^2.2.4",
+ "react-lifecycles-compat": "^3.0.4"
+ },
+ "peerDependencies": {
+ "react": ">=15.0.0"
+ }
+ },
+ "node_modules/use-sync-external-store": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
+ "integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/warning": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz",
+ "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.0.0"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..e0d27d7
--- /dev/null
+++ b/package.json
@@ -0,0 +1,8 @@
+{
+ "dependencies": {
+ "@headlessui/react": "^2.2.7",
+ "@heroicons/react": "^2.2.0",
+ "date-fns": "^4.1.0",
+ "react-big-calendar": "^1.19.4"
+ }
+}
diff --git a/susconecta/app/agendamento/page.tsx b/susconecta/app/agendamento/page.tsx
new file mode 100644
index 0000000..0b7248c
--- /dev/null
+++ b/susconecta/app/agendamento/page.tsx
@@ -0,0 +1,139 @@
+
+'use client';
+
+import { useState } from 'react';
+import dynamic from 'next/dynamic';
+
+
+const AgendaCalendar = dynamic(() => import('@/components/agendamento/AgendaCalendar'), {
+ ssr: false,
+ loading: () => (
+
+ )
+});
+
+const AppointmentModal = dynamic(() => import('@/components/agendamento/AppointmentModal'), { ssr: false });
+const ListaEspera = dynamic(() => import('@/components/agendamento/ListaEspera'), { ssr: false });
+
+
+const mockAppointments = [
+ { id: '1', patient: 'Ana Costa', time: '2025-09-10T09:00', duration: 30, type: 'consulta' as const, status: 'confirmed' as const, professional: '1', notes: '' },
+ { id: '2', patient: 'Pedro Alves', time: '2025-09-10T10:30', duration: 45, type: 'retorno' as const, status: 'pending' as const, professional: '2', notes: '' },
+ { id: '3', patient: 'Mariana Lima', time: '2025-09-10T14:00', duration: 60, type: 'exame' as const, status: 'confirmed' as const, professional: '3', notes: '' },
+];
+
+const mockWaitingList = [
+ { id: '1', name: 'Ana Costa', specialty: 'Cardiologia', preferredDate: '2025-09-12', priority: 'high' as const, contact: '(11) 99999-9999' },
+ { id: '2', name: 'Pedro Alves', specialty: 'Dermatologia', preferredDate: '2025-09-15', priority: 'medium' as const, contact: '(11) 98888-8888' },
+ { id: '3', name: 'Mariana Lima', specialty: 'Ortopedia', preferredDate: '2025-09-20', priority: 'low' as const, contact: '(11) 97777-7777' },
+];
+
+const mockProfessionals = [
+ { id: '1', name: 'Dr. Carlos Silva', specialty: 'Cardiologia' },
+ { id: '2', name: 'Dra. Maria Santos', specialty: 'Dermatologia' },
+ { id: '3', name: 'Dr. João Oliveira', specialty: 'Ortopedia' },
+];
+
+export default function AgendamentoPage() {
+ const [appointments, setAppointments] = useState(mockAppointments);
+ const [waitingList, setWaitingList] = useState(mockWaitingList);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedAppointment, setSelectedAppointment] = useState(null);
+ const [activeTab, setActiveTab] = useState<'agenda' | 'espera'>('agenda');
+
+ const handleSaveAppointment = (appointment: any) => {
+ if (appointment.id) {
+ setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
+ } else {
+ const newAppointment = {
+ ...appointment,
+ id: Date.now().toString(),
+ };
+ setAppointments(prev => [...prev, newAppointment]);
+ }
+ };
+
+ const handleEditAppointment = (appointment: any) => {
+ setSelectedAppointment(appointment);
+ setIsModalOpen(true);
+ };
+
+ const handleAddAppointment = () => {
+ setSelectedAppointment(null);
+ setIsModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setIsModalOpen(false);
+ setSelectedAppointment(null);
+ };
+
+ const handleNotifyPatient = (patientId: string) => {
+ console.log(`Notificando paciente ${patientId}`);
+ };
+
+ return (
+
+
+
+
Agendamento
+
Gerencie a agenda da clínica
+
+
+
+
+
+
+
+ {activeTab === 'agenda' ? (
+
+ ) : (
+
{}}
+ />
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/susconecta/app/dashboard/pacientes/page.tsx b/susconecta/app/dashboard/pacientes/page.tsx
index d35bbd1..106bf4c 100644
--- a/susconecta/app/dashboard/pacientes/page.tsx
+++ b/susconecta/app/dashboard/pacientes/page.tsx
@@ -1,381 +1,251 @@
-"use client"
-import { useState } from "react"
-import { Button } from "@/components/ui/button"
-import { Input } from "@/components/ui/input"
-import { Badge } from "@/components/ui/badge"
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select"
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"
-import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
-import {
- Dialog,
- DialogContent,
- DialogDescription,
- DialogHeader,
- DialogTitle,
- DialogTrigger,
-} from "@/components/ui/dialog"
-import { Label } from "@/components/ui/label"
-import { Search, Filter, Plus, MoreHorizontal, Calendar, Gift, Eye, Edit, Trash2, CalendarPlus } from "lucide-react"
+"use client";
-const patients = [
- {
- id: 1,
- name: "Aaron Avalos Perez",
- phone: "(75) 99982-6363",
- city: "Aracaju",
- state: "Sergipe",
- lastAppointment: "26/09/2025 14:30",
- nextAppointment: "19/08/2025 15:00",
- isVip: false,
- convenio: "unimed",
- birthday: "1985-03-15",
- age: 40,
- },
- {
- id: 2,
- name: "ABENANDO OLIVEIRA DE JESUS",
- phone: "(75) 99986-0093",
- city: "-",
- state: "-",
- lastAppointment: "Ainda não houve atendimento",
- nextAppointment: "Nenhum atendimento agendado",
- isVip: false,
- convenio: "particular",
- birthday: "1978-12-03",
- age: 46,
- },
- {
- id: 3,
- name: "ABDIAS DANTAS DOS SANTOS",
- phone: "(75) 99125-7267",
- city: "São Cristóvão",
- state: "Sergipe",
- lastAppointment: "30/12/2024 08:40",
- nextAppointment: "Nenhum atendimento agendado",
- isVip: true,
- convenio: "bradesco",
- birthday: "1990-12-03",
- age: 34,
- },
- {
- id: 4,
- name: "Abdias Matheus Rodrigues Ferreira",
- phone: "(75) 99983-7711",
- city: "Pirambu",
- state: "Sergipe",
- lastAppointment: "04/09/2024 16:20",
- nextAppointment: "Nenhum atendimento agendado",
- isVip: false,
- convenio: "amil",
- birthday: "1982-12-03",
- age: 42,
- },
- {
- id: 5,
- name: "Abdon Ferreira Guerra",
- phone: "(75) 99971-0228",
- city: "-",
- state: "-",
- lastAppointment: "08/05/2025 08:00",
- nextAppointment: "Nenhum atendimento agendado",
- isVip: false,
- convenio: "unimed",
- birthday: "1975-12-03",
- age: 49,
- },
-]
+import { useEffect, useMemo, useState } from "react";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table";
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
+import { MoreHorizontal, Plus, Search, Eye, Edit, Trash2, ArrowLeft } from "lucide-react";
+
+import { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
+import { PatientRegistrationForm } from "@/components/forms/patient-registration-form";
+
+
+function normalizePaciente(p: any): Paciente {
+ const endereco: Endereco = {
+ cep: p.endereco?.cep ?? p.cep ?? "",
+ logradouro: p.endereco?.logradouro ?? p.logradouro ?? "",
+ numero: p.endereco?.numero ?? p.numero ?? "",
+ complemento: p.endereco?.complemento ?? p.complemento ?? "",
+ bairro: p.endereco?.bairro ?? p.bairro ?? "",
+ cidade: p.endereco?.cidade ?? p.cidade ?? "",
+ estado: p.endereco?.estado ?? p.estado ?? "",
+ };
+
+ return {
+ id: String(p.id ?? p.uuid ?? p.paciente_id ?? ""),
+ nome: p.nome ?? "",
+ nome_social: p.nome_social ?? null,
+ cpf: p.cpf ?? "",
+ rg: p.rg ?? null,
+ sexo: p.sexo ?? null,
+ data_nascimento: p.data_nascimento ?? null,
+ telefone: p.telefone ?? "",
+ email: p.email ?? "",
+ endereco,
+ observacoes: p.observacoes ?? null,
+ foto_url: p.foto_url ?? null,
+ };
+}
export default function PacientesPage() {
- const [searchTerm, setSearchTerm] = useState("")
- const [selectedConvenio, setSelectedConvenio] = useState("all") // Updated default value to "all"
- const [showVipOnly, setShowVipOnly] = useState(false)
- const [showBirthdays, setShowBirthdays] = useState(false)
- const [advancedFilters, setAdvancedFilters] = useState({
- city: "",
- state: "",
- minAge: "",
- maxAge: "",
- lastAppointmentFrom: "",
- lastAppointmentTo: "",
- })
- const [isAdvancedFilterOpen, setIsAdvancedFilterOpen] = useState(false)
+ const [patients, setPatients] = useState([]);
+ const [loading, setLoading] = useState(true);
+ const [error, setError] = useState(null);
- const filteredPatients = patients.filter((patient) => {
- const matchesSearch =
- patient.name.toLowerCase().includes(searchTerm.toLowerCase()) || patient.phone.includes(searchTerm)
+ const [search, setSearch] = useState("");
+ const [showForm, setShowForm] = useState(false);
+ const [editingId, setEditingId] = useState(null);
- const matchesConvenio = selectedConvenio === "all" || patient.convenio === selectedConvenio
- const matchesVip = !showVipOnly || patient.isVip
+ async function loadAll() {
+ try {
+ setLoading(true);
+ const data = await listarPacientes({ page: 1, limit: 20 });
+ setPatients((data ?? []).map(normalizePaciente));
+ setError(null);
+ } catch (e: any) {
+ setPatients([]);
+ setError(e?.message || "Erro ao carregar pacientes.");
+ } finally {
+ setLoading(false);
+ }
+ }
- // Check if patient has birthday this month
- const currentMonth = new Date().getMonth() + 1
- const patientBirthMonth = new Date(patient.birthday).getMonth() + 1
- const matchesBirthday = !showBirthdays || patientBirthMonth === currentMonth
+ useEffect(() => {
+ loadAll();
+ }, []);
- // Advanced filters
- const matchesCity = !advancedFilters.city || patient.city.toLowerCase().includes(advancedFilters.city.toLowerCase())
- const matchesState =
- !advancedFilters.state || patient.state.toLowerCase().includes(advancedFilters.state.toLowerCase())
- const matchesMinAge = !advancedFilters.minAge || patient.age >= Number.parseInt(advancedFilters.minAge)
- const matchesMaxAge = !advancedFilters.maxAge || patient.age <= Number.parseInt(advancedFilters.maxAge)
+ const filtered = useMemo(() => {
+ if (!search.trim()) return patients;
+ const q = search.toLowerCase();
+ const qDigits = q.replace(/\D/g, "");
+ return patients.filter((p) => {
+ const byName = (p.nome || "").toLowerCase().includes(q);
+ const byCPF = (p.cpf || "").replace(/\D/g, "").includes(qDigits);
+ const byId = String(p.id || "").includes(qDigits);
+ return byName || byCPF || byId;
+ });
+ }, [patients, search]);
+ function handleAdd() {
+ setEditingId(null);
+ setShowForm(true);
+ }
+
+ function handleEdit(id: string) {
+ setEditingId(id);
+ setShowForm(true);
+ }
+
+ async function handleDelete(id: string) {
+ if (!confirm("Excluir este paciente?")) return;
+ try {
+ await excluirPaciente(id);
+ setPatients((prev) => prev.filter((x) => String(x.id) !== String(id)));
+ } catch (e: any) {
+ alert(e?.message || "Não foi possível excluir.");
+ }
+ }
+
+ function handleSaved(p: Paciente) {
+ const saved = normalizePaciente(p);
+ setPatients((prev) => {
+ const i = prev.findIndex((x) => String(x.id) === String(saved.id));
+ if (i < 0) return [saved, ...prev];
+ const clone = [...prev];
+ clone[i] = saved;
+ return clone;
+ });
+ setShowForm(false);
+ }
+
+ async function handleBuscarServidor() {
+ const q = search.trim();
+ if (!q) return loadAll();
+
+
+ if (/^\d+$/.test(q)) {
+ try {
+ setLoading(true);
+ const one = await buscarPacientePorId(q);
+ setPatients(one ? [normalizePaciente(one)] : []);
+ setError(one ? null : "Paciente não encontrado.");
+ } catch (e: any) {
+ setPatients([]);
+ setError(e?.message || "Paciente não encontrado.");
+ } finally {
+ setLoading(false);
+ }
+ return;
+ }
+
+
+ await loadAll();
+ setTimeout(() => setSearch(q), 0);
+ }
+
+ if (loading) return Carregando pacientes...
;
+ if (error) return {error}
;
+
+ if (showForm) {
return (
- matchesSearch &&
- matchesConvenio &&
- matchesVip &&
- matchesBirthday &&
- matchesCity &&
- matchesState &&
- matchesMinAge &&
- matchesMaxAge
- )
- })
+
+
+
+
{editingId ? "Editar paciente" : "Novo paciente"}
+
- const clearAdvancedFilters = () => {
- setAdvancedFilters({
- city: "",
- state: "",
- minAge: "",
- maxAge: "",
- lastAppointmentFrom: "",
- lastAppointmentTo: "",
- })
- }
-
- const handleViewDetails = (patientId: number) => {
- console.log("[v0] Ver detalhes do paciente:", patientId)
- // TODO: Navigate to patient details page
- }
-
- const handleEditPatient = (patientId: number) => {
- console.log("[v0] Editar paciente:", patientId)
- // TODO: Navigate to edit patient form
- }
-
- const handleScheduleAppointment = (patientId: number) => {
- console.log("[v0] Marcar consulta para paciente:", patientId)
- // TODO: Open appointment scheduling modal
- }
-
- const handleDeletePatient = (patientId: number) => {
- console.log("[v0] Excluir paciente:", patientId)
- // TODO: Show confirmation dialog and delete patient
+
setShowForm(false)}
+ />
+
+ );
}
return (
- {/* Header */}
-
+ {}
+
-
Pacientes
-
Gerencie as informações de seus pacientes
-
-
-
-
- {/* Filters */}
-
-
-
-
setSearchTerm(e.target.value)}
- className="pl-10"
- />
+
Pacientes
+
Gerencie os pacientes
-
-
-
-
-
-
-
+
+
+
+ setSearch(e.target.value)}
+ onKeyDown={(e) => e.key === "Enter" && handleBuscarServidor()}
+ />
+
+
+
+
- {/* Table */}
-
+
Nome
+ CPF
Telefone
Cidade
Estado
- Último atendimento
- Próximo atendimento
Ações
- {filteredPatients.map((patient) => (
-
-
-
-
- {patient.name.charAt(0).toUpperCase()}
-
-
- {patient.isVip && (
-
- VIP
-
- )}
-
-
- {patient.phone}
- {patient.city}
- {patient.state}
-
-
- {patient.lastAppointment}
-
-
-
-
- {patient.nextAppointment}
-
-
-
-
-
-
-
-
- handleViewDetails(patient.id)}>
-
- Ver detalhes
-
- handleEditPatient(patient.id)}>
-
- Editar
-
- handleScheduleAppointment(patient.id)}>
-
- Marcar consulta
-
- handleDeletePatient(patient.id)} className="text-destructive">
-
- Excluir
-
-
-
+ {filtered.length > 0 ? (
+ filtered.map((p) => (
+
+ {p.nome || "(sem nome)"}
+ {p.cpf || "-"}
+ {p.telefone || "-"}
+ {p.endereco?.cidade || "-"}
+ {p.endereco?.estado || "-"}
+
+
+
+
+
+
+ alert(JSON.stringify(p, null, 2))}>
+
+ Ver
+
+ handleEdit(String(p.id))}>
+
+ Editar
+
+ handleDelete(String(p.id))} className="text-destructive">
+
+ Excluir
+
+
+
+
+
+ ))
+ ) : (
+
+
+ Nenhum paciente encontrado
- ))}
+ )}
-
- Mostrando {filteredPatients.length} de {patients.length} pacientes
-
+
Mostrando {filtered.length} de {patients.length}
- )
+ );
}
diff --git a/susconecta/app/layout.tsx b/susconecta/app/layout.tsx
index 853cc31..f127de6 100644
--- a/susconecta/app/layout.tsx
+++ b/susconecta/app/layout.tsx
@@ -1,26 +1,13 @@
import type React from "react"
import type { Metadata } from "next"
-import { Geist, Geist_Mono } from "next/font/google"
import "./globals.css"
-const geistSans = Geist({
- subsets: ["latin"],
- display: "swap",
- variable: "--font-geist-sans",
-})
-
-const geistMono = Geist_Mono({
- subsets: ["latin"],
- display: "swap",
- variable: "--font-geist-mono",
-})
-
export const metadata: Metadata = {
title: "SUSConecta - Conectando Pacientes e Profissionais de Saúde",
description:
"Plataforma inovadora que conecta pacientes e médicos de forma prática, segura e humanizada. Experimente o futuro dos agendamentos médicos.",
keywords: "saúde, médicos, pacientes, agendamento, telemedicina, SUS",
- generator: 'v0.app'
+ generator: 'v0.app'
}
export default function RootLayout({
@@ -29,8 +16,8 @@ export default function RootLayout({
children: React.ReactNode
}) {
return (
-
- {children}
+
+ {children}
)
}
diff --git a/susconecta/app/profissional/page.tsx b/susconecta/app/profissional/page.tsx
new file mode 100644
index 0000000..d69ffda
--- /dev/null
+++ b/susconecta/app/profissional/page.tsx
@@ -0,0 +1,858 @@
+"use client";
+
+import React, { useState } from "react";
+import Link from "next/link";
+import { Button } from "@/components/ui/button";
+import { Input } from "@/components/ui/input";
+import { Label } from "@/components/ui/label";
+import { Textarea } from "@/components/ui/textarea";
+import {
+ Table,
+ TableBody,
+ TableCell,
+ TableHead,
+ TableHeader,
+ TableRow,
+} from "@/components/ui/table";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"
+import { User, FolderOpen, X, Users, MessageSquare, ClipboardList, Plus, Edit, Trash2, ChevronLeft, ChevronRight, Clock } from "lucide-react"
+import { Calendar as CalendarIcon, FileText, Settings } from "lucide-react";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+
+import dynamic from "next/dynamic";
+import dayGridPlugin from "@fullcalendar/daygrid";
+import timeGridPlugin from "@fullcalendar/timegrid";
+import interactionPlugin from "@fullcalendar/interaction";
+import ptBrLocale from "@fullcalendar/core/locales/pt-br";
+
+const FullCalendar = dynamic(() => import("@fullcalendar/react"), {
+ ssr: false,
+});
+
+const pacientes = [
+ { nome: "Ana Souza", cpf: "123.456.789-00", idade: 42, statusLaudo: "Finalizado" },
+ { nome: "Bruno Lima", cpf: "987.654.321-00", idade: 33, statusLaudo: "Pendente" },
+ { nome: "Carla Menezes", cpf: "111.222.333-44", idade: 67, statusLaudo: "Rascunho" },
+];
+
+const medico = {
+ nome: "Dr. Carlos Andrade",
+ identificacao: "CRM 000000 • Cardiologia",
+ fotoUrl: "",
+}
+
+
+const colorsByType = {
+ Rotina: "#4dabf7",
+ Cardiologia: "#f76c6c",
+ Otorrino: "#f7b84d",
+ Pediatria: "#6cf78b",
+ Dermatologia: "#9b59b6",
+ Oftalmologia: "#2ecc71"
+};
+
+const ProfissionalPage = () => {
+ const [activeSection, setActiveSection] = useState('calendario');
+ const [pacienteSelecionado, setPacienteSelecionado] = useState
(null);
+ const [events, setEvents] = useState([
+
+ {
+ id: 1,
+ title: "Ana Souza",
+ type: "Cardiologia",
+ time: "09:00",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "123.456.789-00",
+ color: colorsByType.Cardiologia
+ },
+ {
+ id: 2,
+ title: "Bruno Lima",
+ type: "Rotina",
+ time: "10:30",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "987.654.321-00",
+ color: colorsByType.Rotina
+ },
+ {
+ id: 3,
+ title: "Carla Menezes",
+ type: "Dermatologia",
+ time: "14:00",
+ date: new Date().toISOString().split('T')[0],
+ pacienteId: "111.222.333-44",
+ color: colorsByType.Dermatologia
+ }
+ ]);
+ const [editingEvent, setEditingEvent] = useState(null);
+ const [showPopup, setShowPopup] = useState(false);
+ const [showActionModal, setShowActionModal] = useState(false);
+ const [step, setStep] = useState(1);
+ const [newEvent, setNewEvent] = useState({
+ title: "",
+ type: "",
+ time: "",
+ pacienteId: ""
+ });
+ const [selectedDate, setSelectedDate] = useState(null);
+ const [selectedEvent, setSelectedEvent] = useState(null);
+ const [currentCalendarDate, setCurrentCalendarDate] = useState(new Date());
+
+ const handleSave = (event: React.MouseEvent) => {
+ event.preventDefault();
+ console.log("Laudo salvo!");
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ };
+
+ const handleAbrirProntuario = (paciente: any) => {
+ setPacienteSelecionado(paciente);
+
+ const pacienteLaudo = document.getElementById('pacienteLaudo') as HTMLInputElement;
+ if (pacienteLaudo) pacienteLaudo.value = paciente.nome;
+
+ const destinatario = document.getElementById('destinatario') as HTMLInputElement;
+ if (destinatario) destinatario.value = `${paciente.nome} - ${paciente.cpf}`;
+
+ const prontuarioSection = document.getElementById('prontuario-paciente');
+ if (prontuarioSection) {
+ prontuarioSection.scrollIntoView({ behavior: 'smooth' });
+ }
+ };
+
+ const handleFecharProntuario = () => {
+ setPacienteSelecionado(null);
+ };
+
+
+ const navigateDate = (direction: 'prev' | 'next') => {
+ const newDate = new Date(currentCalendarDate);
+ newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1));
+ setCurrentCalendarDate(newDate);
+ };
+
+ const goToToday = () => {
+ setCurrentCalendarDate(new Date());
+ };
+
+ const formatDate = (date: Date) => {
+ return date.toLocaleDateString('pt-BR', {
+ weekday: 'long',
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ });
+ };
+
+ // Filtrar eventos do dia atual
+ const getTodayEvents = () => {
+ const today = currentCalendarDate.toISOString().split('T')[0];
+ return events
+ .filter(event => event.date === today)
+ .sort((a, b) => a.time.localeCompare(b.time));
+ };
+
+ const getStatusColor = (type: string) => {
+ return colorsByType[type as keyof typeof colorsByType] || "#4dabf7";
+ };
+
+
+ const handleDateClick = (arg: any) => {
+ setSelectedDate(arg.dateStr);
+ setNewEvent({ title: "", type: "", time: "", pacienteId: "" });
+ setStep(1);
+ setEditingEvent(null);
+ setShowPopup(true);
+ };
+
+
+ const handleAddEvent = () => {
+ const paciente = pacientes.find(p => p.nome === newEvent.title);
+ const eventToAdd = {
+ id: Date.now(),
+ title: newEvent.title,
+ type: newEvent.type,
+ time: newEvent.time,
+ date: selectedDate || currentCalendarDate.toISOString().split('T')[0],
+ pacienteId: paciente ? paciente.cpf : "",
+ color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7"
+ };
+ setEvents((prev) => [...prev, eventToAdd]);
+ setShowPopup(false);
+ };
+
+
+ const handleEditEvent = () => {
+ setEvents((prevEvents) =>
+ prevEvents.map((ev) =>
+ ev.id.toString() === editingEvent.id.toString()
+ ? {
+ ...ev,
+ title: newEvent.title,
+ type: newEvent.type,
+ time: newEvent.time,
+ color: colorsByType[newEvent.type as keyof typeof colorsByType] || "#4dabf7"
+ }
+ : ev
+ )
+ );
+ setEditingEvent(null);
+ setShowPopup(false);
+ setShowActionModal(false);
+ };
+
+
+ const handleNextStep = () => {
+ if (step < 3) setStep(step + 1);
+ else editingEvent ? handleEditEvent() : handleAddEvent();
+ };
+
+
+ const handleEventClick = (clickInfo: any) => {
+ setSelectedEvent(clickInfo.event);
+ setShowActionModal(true);
+ };
+
+
+ const handleDeleteEvent = () => {
+ if (!selectedEvent) return;
+ setEvents((prevEvents) =>
+ prevEvents.filter((ev: any) => ev.id.toString() !== selectedEvent.id.toString())
+ );
+ setShowActionModal(false);
+ };
+
+
+ const handleStartEdit = () => {
+ if (!selectedEvent) return;
+ setEditingEvent(selectedEvent);
+ setNewEvent({
+ title: selectedEvent.title,
+ type: selectedEvent.extendedProps.type,
+ time: selectedEvent.extendedProps.time,
+ pacienteId: selectedEvent.extendedProps.pacienteId || ""
+ });
+ setStep(1);
+ setShowActionModal(false);
+ setShowPopup(true);
+ };
+
+
+ const renderEventContent = (eventInfo: any) => {
+ const bg = eventInfo.event.backgroundColor || eventInfo.event.extendedProps?.color || "#4dabf7";
+
+ return (
+
+ {eventInfo.event.title}
+ •
+ {eventInfo.event.extendedProps.type}
+ •
+ {eventInfo.event.extendedProps.time}
+
+ );
+ };
+
+
+ const renderCalendarioSection = () => {
+ const todayEvents = getTodayEvents();
+
+ return (
+
+
+
Agenda do Dia
+
+
+ {/* Navegação de Data */}
+
+
+
+
+ {formatDate(currentCalendarDate)}
+
+
+
+
+
+ {todayEvents.length} consulta{todayEvents.length !== 1 ? 's' : ''} agendada{todayEvents.length !== 1 ? 's' : ''}
+
+
+
+ {/* Lista de Pacientes do Dia */}
+
+ {todayEvents.length === 0 ? (
+
+
+
Nenhuma consulta agendada para este dia
+
Agenda livre para este dia
+
+ ) : (
+ todayEvents.map((appointment) => {
+ const paciente = pacientes.find(p => p.nome === appointment.title);
+ return (
+
+
+
+
+
+
+
+ {appointment.title}
+
+ {paciente && (
+
+ CPF: {paciente.cpf} • {paciente.idade} anos
+
+ )}
+
+
+
+
+ {appointment.time}
+
+
+
+ {appointment.type}
+
+
+
+
+
+
+ Ver informações do paciente
+
+
+
+
+
+
+ );
+ })
+ )}
+
+
+ );
+ };
+
+
+ const renderPacientesSection = () => (
+
+
Gerenciamento de Pacientes
+
+
+
+ Paciente
+ CPF
+ Idade
+ Status do laudo
+ Ações
+
+
+
+ {pacientes.map((paciente) => (
+
+ {paciente.nome}
+ {paciente.cpf}
+ {paciente.idade}
+ {paciente.statusLaudo}
+
+
+
+
+
+ Ver informações do paciente
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+
+
+ const renderProntuarioSection = () => (
+
+
Prontuário do Paciente
+ {pacienteSelecionado && (
+
+
+
Dados do Paciente
+
+
+
+
+
Nome:
+
{pacienteSelecionado.nome}
+
+
+
CPF:
+
{pacienteSelecionado.cpf}
+
+
+
Idade:
+
{pacienteSelecionado.idade} anos
+
+
+
+ )}
+
+
+
+
+ 03/09/2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+
+
+ const renderLaudosSection = () => (
+
+ );
+
+
+ const renderComunicacaoSection = () => (
+
+
Comunicação com o Paciente
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
03/09/2025
+
+
+
+
Pendente
+
+
+
+
+
+
"Ok, obrigado pelo lembrete!"
+
03/09/2025 14:30
+
+
+
+
+
+
+
+ );
+
+
+ const renderPerfilSection = () => (
+
+ );
+
+
+ const renderActiveSection = () => {
+ switch (activeSection) {
+ case 'calendario':
+ return renderCalendarioSection();
+ case 'pacientes':
+ return renderPacientesSection();
+ case 'prontuario':
+ return renderProntuarioSection();
+ case 'laudos':
+ return renderLaudosSection();
+ case 'comunicacao':
+ return renderComunicacaoSection();
+ case 'perfil':
+ return renderPerfilSection();
+ default:
+ return renderCalendarioSection();
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
Conta do profissional
+
{medico.nome}
+
{medico.identificacao}
+
+
+
+
+ {}
+
+
+
+
+
Área do Profissional de Saúde
+
+
+ Bem-vindo à sua área exclusiva.
+
+ {renderActiveSection()}
+
+
+
+ {}
+ {showPopup && (
+
+
+
+
+ {step === 1 && (
+ <>
+
Selecionar Paciente
+
+ Data: {selectedDate ? new Date(selectedDate + 'T00:00:00').toLocaleDateString('pt-BR') : 'Não selecionada'}
+
+
+
+
+
+
+ >
+ )}
+
+ {step === 2 && (
+ <>
+
Tipo da Consulta
+
+
+
+
+
+ >
+ )}
+
+ {step === 3 && (
+ <>
+
Horário da Consulta
+
setNewEvent({ ...newEvent, time: e.target.value })}
+ className="mb-4"
+ />
+
+
+
+
+ >
+ )}
+
+
+ )}
+
+ {}
+ {showActionModal && selectedEvent && (
+
+
+
+ Consulta de {selectedEvent.title}
+
+
+ {selectedEvent.extendedProps.type} às {selectedEvent.extendedProps.time}
+
+
+
+
+
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ProfissionalPage;
\ No newline at end of file
diff --git a/susconecta/components/about-section.tsx b/susconecta/components/about-section.tsx
index 1b18ef8..bc84654 100644
--- a/susconecta/components/about-section.tsx
+++ b/susconecta/components/about-section.tsx
@@ -8,9 +8,9 @@ export function AboutSection() {
- {/* Left Content */}
+ {}
- {/* Professional Image */}
+ {}
- {/* Objective Card */}
+ {}
@@ -36,7 +36,7 @@ export function AboutSection() {
- {/* Right Content */}
+ {}
diff --git a/susconecta/components/agenda/page.tsx b/susconecta/components/agenda/page.tsx
new file mode 100644
index 0000000..2117f9f
--- /dev/null
+++ b/susconecta/components/agenda/page.tsx
@@ -0,0 +1,119 @@
+
+'use client';
+
+import { useState } from 'react';
+import { AgendaCalendar, AppointmentModal, ListaEspera } from '@/components/agendamento';
+
+
+const mockAppointments = [
+ { id: '1', patient: 'Ana Costa', time: '2025-09-10T09:00', duration: 30, type: 'consulta' as const, status: 'confirmed' as const, professional: '1', notes: '' },
+ { id: '2', patient: 'Pedro Alves', time: '2025-09-10T10:30', duration: 45, type: 'retorno' as const, status: 'pending' as const, professional: '2', notes: '' },
+ { id: '3', patient: 'Mariana Lima', time: '2025-09-10T14:00', duration: 60, type: 'exame' as const, status: 'confirmed' as const, professional: '3', notes: '' },
+];
+
+const mockWaitingList = [
+ { id: '1', name: 'Ana Costa', specialty: 'Cardiologia', preferredDate: '2025-09-12', priority: 'high' as const, contact: '(11) 99999-9999' },
+ { id: '2', name: 'Pedro Alves', specialty: 'Dermatologia', preferredDate: '2025-09-15', priority: 'medium' as const, contact: '(11) 98888-8888' },
+ { id: '3', name: 'Mariana Lima', specialty: 'Ortopedia', preferredDate: '2025-09-20', priority: 'low' as const, contact: '(11) 97777-7777' },
+];
+
+const mockProfessionals = [
+ { id: '1', name: 'Dr. Carlos Silva', specialty: 'Cardiologia' },
+ { id: '2', name: 'Dra. Maria Santos', specialty: 'Dermatologia' },
+ { id: '3', name: 'Dr. João Oliveira', specialty: 'Ortopedia' },
+];
+
+export default function AgendaPage() {
+ const [appointments, setAppointments] = useState(mockAppointments);
+ const [waitingList, setWaitingList] = useState(mockWaitingList);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedAppointment, setSelectedAppointment] = useState
(null);
+ const [activeTab, setActiveTab] = useState<'agenda' | 'espera'>('agenda');
+
+ const handleSaveAppointment = (appointment: any) => {
+ if (appointment.id) {
+
+ setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
+ } else {
+
+ const newAppointment = {
+ ...appointment,
+ id: Date.now().toString(),
+ };
+ setAppointments(prev => [...prev, newAppointment]);
+ }
+ };
+
+ const handleEditAppointment = (appointment: any) => {
+ setSelectedAppointment(appointment);
+ setIsModalOpen(true);
+ };
+
+ const handleAddAppointment = () => {
+ setSelectedAppointment(null);
+ setIsModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setIsModalOpen(false);
+ setSelectedAppointment(null);
+ };
+
+ const handleNotifyPatient = (patientId: string) => {
+
+ console.log(`Notificando paciente ${patientId}`);
+ };
+
+ return (
+
+
+
Agendamento
+
+
+
+
+
+
+ {activeTab === 'agenda' ? (
+
+ ) : (
+
{/* implementar */}}
+ />
+ )}
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/susconecta/components/agendamento/AgendaCalendar.tsx b/susconecta/components/agendamento/AgendaCalendar.tsx
new file mode 100644
index 0000000..da0abe1
--- /dev/null
+++ b/susconecta/components/agendamento/AgendaCalendar.tsx
@@ -0,0 +1,303 @@
+
+'use client';
+
+import { useState } from 'react';
+import { ChevronLeft, ChevronRight, Plus, Clock, User, Calendar as CalendarIcon } from 'lucide-react';
+
+interface Appointment {
+ id: string;
+ patient: string;
+ time: string;
+ duration: number;
+ type: 'consulta' | 'exame' | 'retorno';
+ status: 'confirmed' | 'pending' | 'absent';
+ professional: string;
+ notes: string;
+}
+
+interface Professional {
+ id: string;
+ name: string;
+ specialty: string;
+}
+
+interface AgendaCalendarProps {
+ professionals: Professional[];
+ appointments: Appointment[];
+ onAddAppointment: () => void;
+ onEditAppointment: (appointment: Appointment) => void;
+}
+
+export default function AgendaCalendar({
+ professionals,
+ appointments,
+ onAddAppointment,
+ onEditAppointment
+}: AgendaCalendarProps) {
+ const [view, setView] = useState<'day' | 'week' | 'month'>('week');
+ const [selectedProfessional, setSelectedProfessional] = useState('all');
+ const [currentDate, setCurrentDate] = useState(new Date());
+
+ const timeSlots = Array.from({ length: 11 }, (_, i) => {
+ const hour = i + 8; // Das 8h às 18h
+ return [`${hour.toString().padStart(2, '0')}:00`, `${hour.toString().padStart(2, '0')}:30`];
+ }).flat();
+
+ const getStatusColor = (status: string) => {
+ switch (status) {
+ case 'confirmed': return 'bg-green-100 border-green-500 text-green-800';
+ case 'pending': return 'bg-yellow-100 border-yellow-500 text-yellow-800';
+ case 'absent': return 'bg-red-100 border-red-500 text-red-800';
+ default: return 'bg-gray-100 border-gray-500 text-gray-800';
+ }
+ };
+
+ const getTypeIcon = (type: string) => {
+ switch (type) {
+ case 'consulta': return '🩺';
+ case 'exame': return '📋';
+ case 'retorno': return '↩️';
+ default: return '📅';
+ }
+ };
+
+ const formatDate = (date: Date) => {
+ return date.toLocaleDateString('pt-BR', {
+ weekday: 'long',
+ day: 'numeric',
+ month: 'long',
+ year: 'numeric'
+ });
+ };
+
+ const navigateDate = (direction: 'prev' | 'next') => {
+ const newDate = new Date(currentDate);
+ if (view === 'day') {
+ newDate.setDate(newDate.getDate() + (direction === 'next' ? 1 : -1));
+ } else if (view === 'week') {
+ newDate.setDate(newDate.getDate() + (direction === 'next' ? 7 : -7));
+ } else {
+ newDate.setMonth(newDate.getMonth() + (direction === 'next' ? 1 : -1));
+ }
+ setCurrentDate(newDate);
+ };
+
+ const goToToday = () => {
+ setCurrentDate(new Date());
+ };
+
+
+ const filteredAppointments = selectedProfessional === 'all'
+ ? appointments
+ : appointments.filter(app => app.professional === selectedProfessional);
+
+ return (
+
+
+
+
Agenda
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {formatDate(currentDate)}
+
+
+
+
+
+ Atalhos: 'C' para calendário, 'F' para fila de espera
+
+
+
+
+ {}
+ {view !== 'month' && (
+
+
+
+
+
+ Hora
+
+ {timeSlots.map(time => (
+
+ {time}
+
+ ))}
+
+
+
+
+ {currentDate.toLocaleDateString('pt-BR', { weekday: 'long' })}
+
+
+ {timeSlots.map(time => (
+
+ ))}
+
+ {filteredAppointments.map(app => {
+ const [date, timeStr] = app.time.split('T');
+ const [hours, minutes] = timeStr.split(':');
+ const hour = parseInt(hours);
+ const minute = parseInt(minutes);
+
+ return (
+
onEditAppointment(app)}
+ >
+
+
+
+
+ {app.patient}
+
+
+
+ {hours}:{minutes} - {app.type} {getTypeIcon(app.type)}
+
+
+ {professionals.find(p => p.id === app.professional)?.name}
+
+
+
+ {app.status === 'confirmed' ? 'confirmado' : app.status === 'pending' ? 'pendente' : 'ausente'}
+
+
+
+ );
+ })}
+
+
+
+
+
+ )}
+
+ {}
+ {view === 'month' && (
+
+
+ {filteredAppointments.map(app => {
+ const [date, timeStr] = app.time.split('T');
+ const [hours, minutes] = timeStr.split(':');
+
+ return (
+
+
+
+
+ {app.patient}
+
+
+
+ {hours}:{minutes} - {app.type} {getTypeIcon(app.type)}
+
+
+ {professionals.find(p => p.id === app.professional)?.name}
+
+
+ {app.notes && (
+
+ {app.notes}
+
+ )}
+
+
+
+
+ );
+ })}
+
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/susconecta/components/agendamento/AppointmentModal.tsx b/susconecta/components/agendamento/AppointmentModal.tsx
new file mode 100644
index 0000000..3cd5d19
--- /dev/null
+++ b/susconecta/components/agendamento/AppointmentModal.tsx
@@ -0,0 +1,227 @@
+'use client';
+
+import { useState, useEffect } from 'react';
+import { X } from 'lucide-react';
+
+interface Appointment {
+ id?: string;
+ patient: string;
+ time: string;
+ duration: number;
+ type: 'consulta' | 'exame' | 'retorno';
+ status: 'confirmed' | 'pending' | 'absent';
+ professional: string;
+ notes?: string;
+}
+
+interface Professional {
+ id: string;
+ name: string;
+ specialty: string;
+}
+
+interface AppointmentModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ onSave: (appointment: Appointment) => void;
+ professionals: Professional[];
+ appointment?: Appointment | null;
+}
+
+export default function AppointmentModal({
+ isOpen,
+ onClose,
+ onSave,
+ professionals,
+ appointment
+}: AppointmentModalProps) {
+ const [formData, setFormData] = useState({
+ patient: '',
+ time: '',
+ duration: 30,
+ type: 'consulta',
+ status: 'pending',
+ professional: '',
+ notes: ''
+ });
+
+ useEffect(() => {
+ if (appointment) {
+ setFormData(appointment);
+ } else {
+ setFormData({
+ patient: '',
+ time: '',
+ duration: 30,
+ type: 'consulta',
+ status: 'pending',
+ professional: professionals[0]?.id || '',
+ notes: ''
+ });
+ }
+ }, [appointment, professionals]);
+
+ const handleSubmit = (e: React.FormEvent) => {
+ e.preventDefault();
+ onSave(formData);
+ onClose();
+ };
+
+ const handleChange = (e: React.ChangeEvent) => {
+ const { name, value } = e.target;
+ setFormData(prev => ({
+ ...prev,
+ [name]: value
+ }));
+ };
+
+ if (!isOpen) return null;
+
+ return (
+
+
+
+
+ {appointment ? 'Editar Agendamento' : 'Novo Agendamento'}
+
+
+
+
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/susconecta/components/agendamento/ListaEspera.tsx b/susconecta/components/agendamento/ListaEspera.tsx
new file mode 100644
index 0000000..bc9da0c
--- /dev/null
+++ b/susconecta/components/agendamento/ListaEspera.tsx
@@ -0,0 +1,144 @@
+
+'use client';
+
+import { useState } from 'react';
+import { Bell, Plus } from 'lucide-react';
+
+interface WaitingPatient {
+ id: string;
+ name: string;
+ specialty: string;
+ preferredDate: string;
+ priority: 'high' | 'medium' | 'low';
+ contact: string;
+}
+
+interface ListaEsperaProps {
+ patients: WaitingPatient[];
+ onNotify: (patientId: string) => void;
+ onAddToWaitlist: () => void;
+}
+
+export default function ListaEspera({ patients, onNotify, onAddToWaitlist }: ListaEsperaProps) {
+ const [searchTerm, setSearchTerm] = useState('');
+
+ const filteredPatients = patients.filter(patient =>
+ patient.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
+ patient.specialty.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+
+ const getPriorityLabel = (priority: string) => {
+ switch (priority) {
+ case 'high': return 'Alta';
+ case 'medium': return 'Média';
+ case 'low': return 'Baixa';
+ default: return priority;
+ }
+ };
+
+ const getPriorityColor = (priority: string) => {
+ switch (priority) {
+ case 'high': return 'bg-red-100 text-red-800';
+ case 'medium': return 'bg-yellow-100 text-yellow-800';
+ case 'low': return 'bg-green-100 text-green-800';
+ default: return 'bg-gray-100 text-gray-800';
+ }
+ };
+
+ return (
+
+
+
+
Lista de Espera Inteligente
+
+
+
+
+
+
+
+ 🔍
+
+
setSearchTerm(e.target.value)}
+ className="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-blue-500 focus:border-blue-500"
+ />
+
+
+
+
+
+
+
+ |
+ Paciente
+ |
+
+ Especialidade
+ |
+
+ Data Preferencial
+ |
+
+ Prioridade
+ |
+
+ Contato
+ |
+
+ Ações
+ |
+
+
+
+ {filteredPatients.map((patient) => (
+
+ |
+ {patient.name}
+ |
+
+ {patient.specialty}
+ |
+
+ {new Date(patient.preferredDate).toLocaleDateString('pt-BR')}
+ |
+
+
+ {getPriorityLabel(patient.priority)}
+
+ |
+
+ {patient.contact}
+ |
+
+
+ |
+
+ ))}
+
+
+
+
+ {filteredPatients.length === 0 && (
+
+ Nenhum paciente encontrado na lista de espera
+
+ )}
+
+ );
+}
\ No newline at end of file
diff --git a/susconecta/components/agendamento/index.ts b/susconecta/components/agendamento/index.ts
new file mode 100644
index 0000000..e3f74c2
--- /dev/null
+++ b/susconecta/components/agendamento/index.ts
@@ -0,0 +1,4 @@
+// components/agendamento/index.ts
+export { default as AgendaCalendar } from './AgendaCalendar';
+export { default as AppointmentModal } from './AppointmentModal';
+export { default as ListaEspera } from './ListaEspera';
\ No newline at end of file
diff --git a/susconecta/components/dashboard/sidebar.tsx b/susconecta/components/dashboard/sidebar.tsx
index f277ea9..0a9bffa 100644
--- a/susconecta/components/dashboard/sidebar.tsx
+++ b/susconecta/components/dashboard/sidebar.tsx
@@ -3,12 +3,13 @@
import Link from "next/link"
import { usePathname } from "next/navigation"
import { cn } from "@/lib/utils"
-import { Home, Calendar, Users, UserCheck, FileText, BarChart3, Settings, Stethoscope } from "lucide-react"
+import { Home, Calendar, Users, UserCheck, FileText, BarChart3, Settings, Stethoscope, User } from "lucide-react"
const navigation = [
{ name: "Dashboard", href: "/dashboard", icon: Home },
- { name: "Agenda", href: "/dashboard/agenda", icon: Calendar },
+ { name: "Agendamento", href: "/agendamento", icon: Calendar },
{ name: "Pacientes", href: "/dashboard/pacientes", icon: Users },
+ { name: "Médicos", href: "/dashboard/medicos", icon: User },
{ name: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
{ name: "Prontuários", href: "/dashboard/prontuarios", icon: FileText },
{ name: "Relatórios", href: "/dashboard/relatorios", icon: BarChart3 },
diff --git a/susconecta/components/footer.tsx b/susconecta/components/footer.tsx
index c7ecb78..b8bfcb1 100644
--- a/susconecta/components/footer.tsx
+++ b/susconecta/components/footer.tsx
@@ -12,10 +12,10 @@ export function Footer() {