diff --git a/.gitignore b/.gitignore
index e69de29..3c3629e 100644
--- a/.gitignore
+++ b/.gitignore
@@ -0,0 +1 @@
+node_modules
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..9f2a491
--- /dev/null
+++ b/susconecta/app/agendamento/page.tsx
@@ -0,0 +1,139 @@
+// app/agendamento/page.tsx
+'use client';
+
+import { useState } from 'react';
+import dynamic from 'next/dynamic';
+
+// Importação dinâmica para evitar erros de SSR
+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 });
+
+// Dados mockados
+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/components/agenda/page.tsx b/susconecta/components/agenda/page.tsx
new file mode 100644
index 0000000..c247a88
--- /dev/null
+++ b/susconecta/components/agenda/page.tsx
@@ -0,0 +1,119 @@
+// app/agenda/page.tsx
+'use client';
+
+import { useState } from 'react';
+import { AgendaCalendar, AppointmentModal, ListaEspera } from '@/components/agendamento';
+
+// Dados mockados - substitua pelos seus dados reais
+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) {
+ // Editar agendamento existente
+ setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
+ } else {
+ // Novo agendamento
+ 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) => {
+ // Lógica para notificar paciente
+ 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..a135d11
--- /dev/null
+++ b/susconecta/components/agendamento/AgendaCalendar.tsx
@@ -0,0 +1,303 @@
+// components/agendamento/AgendaCalendar.tsx (atualizado)
+'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());
+ };
+
+ // Filtra os agendamentos por profissional selecionado
+ 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
+
+
+
+
+ {/* Visualização de Dia/Semana (calendário) */}
+ {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'}
+
+
+
+ );
+ })}
+
+
+
+
+
+ )}
+
+ {/* Visualização de Mês (lista) */}
+ {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..4c8b7f5
--- /dev/null
+++ b/susconecta/components/agendamento/ListaEspera.tsx
@@ -0,0 +1,144 @@
+// components/agendamento/ListaEspera.tsx
+'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..9d650c0 100644
--- a/susconecta/components/dashboard/sidebar.tsx
+++ b/susconecta/components/dashboard/sidebar.tsx
@@ -7,7 +7,7 @@ import { Home, Calendar, Users, UserCheck, FileText, BarChart3, Settings, Stetho
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: "Consultas", href: "/dashboard/consultas", icon: UserCheck },
{ name: "Prontuários", href: "/dashboard/prontuarios", icon: FileText },
diff --git a/susconecta/components/ui/sidebar.tsx b/susconecta/components/ui/sidebar.tsx
index 1ee5a45..116d4d3 100644
--- a/susconecta/components/ui/sidebar.tsx
+++ b/susconecta/components/ui/sidebar.tsx
@@ -698,6 +698,7 @@ function SidebarMenuSubButton({
)
}
+
export {
Sidebar,
SidebarContent,
diff --git a/susconecta/hooks/UseAgenda.ts b/susconecta/hooks/UseAgenda.ts
new file mode 100644
index 0000000..0620037
--- /dev/null
+++ b/susconecta/hooks/UseAgenda.ts
@@ -0,0 +1,95 @@
+// hooks/useAgenda.ts
+import { useState } from 'react';
+
+export interface Appointment {
+ id: string;
+ patient: string;
+ time: string;
+ duration: number;
+ type: 'consulta' | 'exame' | 'retorno';
+ status: 'confirmed' | 'pending' | 'absent';
+ professional: string;
+ notes?: string;
+}
+
+export interface Professional {
+ id: string;
+ name: string;
+ specialty: string;
+}
+
+export interface WaitingPatient {
+ id: string;
+ name: string;
+ specialty: string;
+ preferredDate: string;
+ priority: 'high' | 'medium' | 'low';
+ contact: string;
+}
+
+export const useAgenda = () => {
+ const [appointments, setAppointments] = useState([]);
+ const [waitingList, setWaitingList] = useState([]);
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ const [selectedAppointment, setSelectedAppointment] = useState(null);
+ const [isWaitlistModalOpen, setIsWaitlistModalOpen] = useState(false);
+
+ const professionals: Professional[] = [
+ { 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' },
+ ];
+
+ const handleSaveAppointment = (appointment: Appointment) => {
+ if (appointment.id) {
+ // Editar agendamento existente
+ setAppointments(prev => prev.map(a => a.id === appointment.id ? appointment : a));
+ } else {
+ // Novo agendamento
+ const newAppointment = {
+ ...appointment,
+ id: Date.now().toString(),
+ };
+ setAppointments(prev => [...prev, newAppointment]);
+ }
+ };
+
+ const handleEditAppointment = (appointment: Appointment) => {
+ setSelectedAppointment(appointment);
+ setIsModalOpen(true);
+ };
+
+ const handleAddAppointment = () => {
+ setSelectedAppointment(null);
+ setIsModalOpen(true);
+ };
+
+ const handleCloseModal = () => {
+ setIsModalOpen(false);
+ setSelectedAppointment(null);
+ };
+
+ const handleNotifyPatient = (patientId: string) => {
+ // Lógica para notificar paciente
+ console.log(`Notificando paciente ${patientId}`);
+ };
+
+ const handleAddToWaitlist = () => {
+ setIsWaitlistModalOpen(true);
+ };
+
+ return {
+ appointments,
+ waitingList,
+ professionals,
+ isModalOpen,
+ selectedAppointment,
+ isWaitlistModalOpen,
+ handleSaveAppointment,
+ handleEditAppointment,
+ handleAddAppointment,
+ handleCloseModal,
+ handleNotifyPatient,
+ handleAddToWaitlist,
+ };
+};
\ No newline at end of file
diff --git a/susconecta/package-lock.json b/susconecta/package-lock.json
index 0ce2b43..10f2ea2 100644
--- a/susconecta/package-lock.json
+++ b/susconecta/package-lock.json
@@ -2465,7 +2465,7 @@
"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==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
@@ -2475,7 +2475,7 @@
"version": "19.1.9",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.1.9.tgz",
"integrity": "sha512-qXRuZaOsAdXKFyOhRBg6Lqqc0yay13vN7KrIg4L7N4aaHN68ma9OK3NE1BoDFgFOTfM7zg+3/8+2n8rLUH3OKQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
"@types/react": "^19.0.0"
@@ -2722,7 +2722,7 @@
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
- "dev": true,
+ "devOptional": true,
"license": "MIT"
},
"node_modules/d3-array": {
@@ -3473,7 +3473,6 @@
"version": "8.5.6",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz",
"integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==",
- "dev": true,
"funding": [
{
"type": "opencollective",
@@ -3562,6 +3561,13 @@
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
+ "node_modules/react-is": {
+ "version": "19.1.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-19.1.1.tgz",
+ "integrity": "sha512-tr41fA15Vn8p4X9ntI+yCyeGSf1TlYaY5vlTZfQmeLBrFo3psOPX6HhTDnFNL9uj3EhP0KAQ80cugCl4b4BERA==",
+ "license": "MIT",
+ "peer": true
+ },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
@@ -3851,7 +3857,6 @@
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.12.tgz",
"integrity": "sha512-DzFtxOi+7NsFf7DBtI3BJsynR+0Yp6etH+nRPTbpWnS2pZBaSksv/JGctNwSWzbFjp0vxSqknaUylseZqMDGrA==",
- "dev": true,
"license": "MIT"
},
"node_modules/tailwindcss-animate": {