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
index 1505ea2..963283f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -5,287 +5,572 @@
"packages": {
"": {
"dependencies": {
- "axios": "^1.11.0"
+ "@headlessui/react": "^2.2.7",
+ "@heroicons/react": "^2.2.0",
+ "date-fns": "^4.1.0",
+ "react-big-calendar": "^1.19.4"
}
},
- "node_modules/asynckit": {
- "version": "0.4.0",
- "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
- "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "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/axios": {
- "version": "1.11.0",
- "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz",
- "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==",
+ "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": {
- "follow-redirects": "^1.15.6",
- "form-data": "^4.0.4",
- "proxy-from-env": "^1.1.0"
- }
- },
- "node_modules/call-bind-apply-helpers": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
- "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "function-bind": "^1.1.2"
+ "@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": ">= 0.4"
- }
- },
- "node_modules/combined-stream": {
- "version": "1.0.8",
- "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
- "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
- "license": "MIT",
- "dependencies": {
- "delayed-stream": "~1.0.0"
+ "node": ">=10"
},
- "engines": {
- "node": ">= 0.8"
+ "peerDependencies": {
+ "react": "^18 || ^19 || ^19.0.0-rc",
+ "react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
- "node_modules/delayed-stream": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
- "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "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",
- "engines": {
- "node": ">=0.4.0"
+ "peerDependencies": {
+ "react": ">= 16 || ^19.0.0-rc"
}
},
- "node_modules/dunder-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
- "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "license": "MIT",
- "dependencies": {
- "call-bind-apply-helpers": "^1.0.1",
- "es-errors": "^1.3.0",
- "gopd": "^1.2.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-define-property": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
- "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-errors": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
- "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-object-atoms": {
- "version": "1.1.1",
- "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
- "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/es-set-tostringtag": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
- "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "license": "MIT",
- "dependencies": {
- "es-errors": "^1.3.0",
- "get-intrinsic": "^1.2.6",
- "has-tostringtag": "^1.0.2",
- "hasown": "^2.0.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/follow-redirects": {
- "version": "1.15.11",
- "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
- "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/RubenVerborgh"
- }
- ],
- "license": "MIT",
- "engines": {
- "node": ">=4.0"
- },
- "peerDependenciesMeta": {
- "debug": {
- "optional": true
- }
- }
- },
- "node_modules/form-data": {
- "version": "4.0.4",
- "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz",
- "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==",
- "license": "MIT",
- "dependencies": {
- "asynckit": "^0.4.0",
- "combined-stream": "^1.0.8",
- "es-set-tostringtag": "^2.1.0",
- "hasown": "^2.0.2",
- "mime-types": "^2.1.12"
- },
- "engines": {
- "node": ">= 6"
- }
- },
- "node_modules/function-bind": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
- "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "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": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "opencollective",
+ "url": "https://opencollective.com/popperjs"
}
},
- "node_modules/get-intrinsic": {
- "version": "1.3.0",
- "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
- "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "license": "MIT",
+ "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": {
- "call-bind-apply-helpers": "^1.0.2",
- "es-define-property": "^1.0.1",
- "es-errors": "^1.3.0",
- "es-object-atoms": "^1.1.1",
- "function-bind": "^1.1.2",
- "get-proto": "^1.0.1",
- "gopd": "^1.2.0",
- "has-symbols": "^1.1.0",
- "hasown": "^2.0.2",
- "math-intrinsics": "^1.1.0"
+ "@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": ">= 0.4"
+ "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": {
- "url": "https://github.com/sponsors/ljharb"
+ "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/get-proto": {
- "version": "1.0.1",
- "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
- "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "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",
- "dependencies": {
- "dunder-proto": "^1.0.1",
- "es-object-atoms": "^1.0.0"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/gopd": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
- "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
"funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
}
},
- "node_modules/has-symbols": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
- "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
- }
- },
- "node_modules/has-tostringtag": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
- "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "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": {
- "has-symbols": "^1.0.3"
- },
- "engines": {
- "node": ">= 0.4"
- },
- "funding": {
- "url": "https://github.com/sponsors/ljharb"
+ "csstype": "^3.0.2"
}
},
- "node_modules/hasown": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
- "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "license": "MIT",
- "dependencies": {
- "function-bind": "^1.1.2"
- },
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/math-intrinsics": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
- "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.4"
- }
- },
- "node_modules/mime-db": {
- "version": "1.52.0",
- "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
- "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
- "license": "MIT",
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/mime-types": {
- "version": "2.1.35",
- "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
- "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
- "license": "MIT",
- "dependencies": {
- "mime-db": "1.52.0"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/proxy-from-env": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
- "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "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/app/dashboard/pacientes/page.tsx b/susconecta/app/dashboard/pacientes/page.tsx
index d35bbd1..b1d8ff8 100644
--- a/susconecta/app/dashboard/pacientes/page.tsx
+++ b/susconecta/app/dashboard/pacientes/page.tsx
@@ -1,381 +1,251 @@
-"use client"
+/* src/app/dashboard/pacientes/page.tsx */
+"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"
+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";
-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 { Paciente, Endereco, listarPacientes, buscarPacientePorId, excluirPaciente } from "@/lib/api";
+import { PatientRegistrationForm } from "@/components/forms/patient-registration-form";
+
+// Converte qualquer formato que vier do mock para o shape do nosso tipo Paciente
+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();
+
+ // Se for apenas números, tentamos como ID no servidor
+ 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;
+ }
+
+ // Senão, recarrega e filtra local (o mock nem sempre filtra por nome/CPF)
+ 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 */}
-
+ {/* Cabeçalho + Busca (um único input no padrão do print) */}
+
-
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 e72fe16..f127de6 100644
--- a/susconecta/app/layout.tsx
+++ b/susconecta/app/layout.tsx
@@ -1,31 +1,23 @@
-import type React from "react";
-import type { Metadata } from "next";
-import "./globals.css";
-
-// Importa as famílias direto do pacote geist
-import { GeistSans } from "geist/font/sans";
-import { GeistMono } from "geist/font/mono";
+import type React from "react"
+import type { Metadata } from "next"
+import "./globals.css"
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({
children,
}: {
- children: React.ReactNode;
+ 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..c952f19
--- /dev/null
+++ b/susconecta/app/profissional/page.tsx
@@ -0,0 +1,435 @@
+"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 } from "lucide-react"
+import { Calendar as CalendarIcon, FileText, Settings } from "lucide-react";
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipProvider,
+ TooltipTrigger,
+} from "@/components/ui/tooltip";
+
+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 ProfissionalPage = () => {
+ const [pacienteSelecionado, setPacienteSelecionado] = useState
(null);
+
+ const handleSave = (event: React.MouseEvent) => {
+ event.preventDefault();
+ console.log("Laudo salvo!");
+ window.scrollTo({ top: 0, behavior: "smooth" });
+ };
+
+ const handleSmoothScroll = (event: React.MouseEvent, targetId: string) => {
+ event.preventDefault();
+ const element = document.getElementById(targetId);
+ if (element) {
+ element.scrollIntoView({
+ behavior: 'smooth',
+ block: 'start'
+ });
+ }
+ };
+
+ const handleAbrirProntuario = (paciente: any) => {
+ setPacienteSelecionado(paciente);
+
+ // Preencher campos da seção Gestão de Laudos
+ const pacienteLaudo = document.getElementById('pacienteLaudo') as HTMLInputElement;
+
+ if (pacienteLaudo) pacienteLaudo.value = paciente.nome;
+
+ // Preencher campos da seção Comunicação com o Paciente
+ const destinatario = document.getElementById('destinatario') as HTMLInputElement;
+
+ if (destinatario) destinatario.value = `${paciente.nome} - ${paciente.cpf}`;
+
+ // Rolar para a seção do prontuário
+ const prontuarioSection = document.getElementById('prontuario-paciente');
+ if (prontuarioSection) {
+ prontuarioSection.scrollIntoView({ behavior: 'smooth' });
+ }
+ };
+
+ const handleFecharProntuario = () => {
+ setPacienteSelecionado(null);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
Conta do profissional
+
{medico.nome}
+
{medico.identificacao}
+
+
+
+ {/* Sidebar */}
+
+
+
+
+
+
Área do Profissional de Saúde
+
+
+ Bem-vindo à sua área exclusiva.
+
+
+ Calendário
+
+ Seção do calendário (integração pode ser adicionada depois).
+
+
+
+
+
Gerenciamento de Pacientes
+
+
+
+ Paciente
+ CPF
+ Idade
+ Status do laudo
+ Ações
+
+
+
+ {pacientes.map((paciente) => (
+
+ {paciente.nome}
+ {paciente.cpf}
+ {paciente.idade}
+ {paciente.statusLaudo}
+
+
+
+
+
+
+
+ Detalhes do Paciente
+
+
+
+
+
+ ))}
+
+
+
+
+
+
Prontuário do Paciente
+ {pacienteSelecionado && (
+
+
+
Dados do Paciente
+
+
+
+
+
Nome:
+
{pacienteSelecionado.nome}
+
+
+
CPF:
+
{pacienteSelecionado.cpf}
+
+
+
Idade:
+
{pacienteSelecionado.idade} anos
+
+
+
+ )}
+
+
+
+
+ 03/09/2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Gestão de Laudos
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Dr. Carlos Andrade
+
+
+
+
+
+ 03/09/2025
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Comunicação com o Paciente
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
03/09/2025
+
+
+
+
Pendente
+
+
+
+
+
+
"Ok, obrigado pelo lembrete!"
+
03/09/2025 14:30
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ProfissionalPage;
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..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/forms/patient-registration-form.tsx b/susconecta/components/forms/patient-registration-form.tsx
new file mode 100644
index 0000000..0263a23
--- /dev/null
+++ b/susconecta/components/forms/patient-registration-form.tsx
@@ -0,0 +1,616 @@
+/* src/components/forms/patient-registration-form.tsx */
+"use client";
+
+import { useEffect, useMemo, useState } from "react";
+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 { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
+import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
+import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@/components/ui/collapsible";
+import { Alert, AlertDescription } from "@/components/ui/alert";
+import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
+import { AlertCircle, ChevronDown, ChevronUp, FileImage, Loader2, Save, Upload, User, X, XCircle, Trash2 } from "lucide-react";
+
+import {
+ Paciente,
+ PacienteInput,
+ buscarCepAPI,
+ validarCPF,
+ criarPaciente,
+ atualizarPaciente,
+ uploadFotoPaciente,
+ removerFotoPaciente,
+ adicionarAnexo,
+ listarAnexos,
+ removerAnexo,
+ buscarPacientePorId,
+} from "@/lib/api";
+
+type Mode = "create" | "edit";
+
+export interface PatientRegistrationFormProps {
+ open?: boolean;
+ onOpenChange?: (open: boolean) => void;
+ patientId?: number | null;
+ inline?: boolean;
+ mode?: Mode;
+ onSaved?: (paciente: Paciente) => void;
+ onClose?: () => void;
+}
+
+type FormData = {
+ photo: File | null;
+ nome: string;
+ nome_social: string;
+ cpf: string;
+ rg: string;
+ sexo: string;
+ data_nascimento: string;
+ email: string;
+ telefone: string;
+ cep: string;
+ logradouro: string;
+ numero: string;
+ complemento: string;
+ bairro: string;
+ cidade: string;
+ estado: string;
+ observacoes: string;
+ anexos: File[];
+};
+
+const initial: FormData = {
+ photo: null,
+ nome: "",
+ nome_social: "",
+ cpf: "",
+ rg: "",
+ sexo: "",
+ data_nascimento: "",
+ email: "",
+ telefone: "",
+ cep: "",
+ logradouro: "",
+ numero: "",
+ complemento: "",
+ bairro: "",
+ cidade: "",
+ estado: "",
+ observacoes: "",
+ anexos: [],
+};
+
+export function PatientRegistrationForm({
+ open = true,
+ onOpenChange,
+ patientId = null,
+ inline = false,
+ mode = "create",
+ onSaved,
+ onClose,
+}: PatientRegistrationFormProps) {
+ const [form, setForm] = useState(initial);
+ const [errors, setErrors] = useState>({});
+ const [expanded, setExpanded] = useState({ dados: true, contato: false, endereco: false, obs: false });
+ const [isSubmitting, setSubmitting] = useState(false);
+ const [isSearchingCEP, setSearchingCEP] = useState(false);
+ const [photoPreview, setPhotoPreview] = useState(null);
+ const [serverAnexos, setServerAnexos] = useState([]);
+
+ const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]);
+
+ // Edição
+ useEffect(() => {
+ async function load() {
+ if (mode !== "edit" || patientId == null) return;
+ try {
+ const p = await buscarPacientePorId(String(patientId));
+ setForm((s) => ({
+ ...s,
+ nome: p.nome || "",
+ nome_social: p.nome_social || "",
+ cpf: p.cpf || "",
+ rg: p.rg || "",
+ sexo: p.sexo || "",
+ data_nascimento: (p.data_nascimento as string) || "",
+ telefone: p.telefone || "",
+ email: p.email || "",
+ cep: p.endereco?.cep || "",
+ logradouro: p.endereco?.logradouro || "",
+ numero: p.endereco?.numero || "",
+ complemento: p.endereco?.complemento || "",
+ bairro: p.endereco?.bairro || "",
+ cidade: p.endereco?.cidade || "",
+ estado: p.endereco?.estado || "",
+ observacoes: p.observacoes || "",
+ }));
+ const ax = await listarAnexos(String(patientId)).catch(() => []);
+ setServerAnexos(Array.isArray(ax) ? ax : []);
+ } catch {
+ // ignora
+ }
+ }
+ load();
+ }, [mode, patientId]);
+
+ function setField(k: T, v: FormData[T]) {
+ setForm((s) => ({ ...s, [k]: v }));
+ if (errors[k as string]) setErrors((e) => ({ ...e, [k]: "" }));
+ }
+
+ function formatCPF(v: string) {
+ const n = v.replace(/\D/g, "").slice(0, 11);
+ return n.replace(/(\d{3})(\d{3})(\d{3})(\d{0,2})/, (_, a, b, c, d) => `${a}.${b}.${c}${d ? "-" + d : ""}`);
+ }
+ function handleCPFChange(v: string) {
+ setField("cpf", formatCPF(v));
+ }
+
+ function formatCEP(v: string) {
+ const n = v.replace(/\D/g, "").slice(0, 8);
+ return n.replace(/(\d{5})(\d{0,3})/, (_, a, b) => `${a}${b ? "-" + b : ""}`);
+ }
+ async function fillFromCEP(cep: string) {
+ const clean = cep.replace(/\D/g, "");
+ if (clean.length !== 8) return;
+ setSearchingCEP(true);
+ try {
+ const res = await buscarCepAPI(clean);
+ if (res?.erro) {
+ setErrors((e) => ({ ...e, cep: "CEP não encontrado" }));
+ } else {
+ setField("logradouro", res.logradouro ?? "");
+ setField("bairro", res.bairro ?? "");
+ setField("cidade", res.localidade ?? "");
+ setField("estado", res.uf ?? "");
+ }
+ } catch {
+ setErrors((e) => ({ ...e, cep: "Erro ao buscar CEP" }));
+ } finally {
+ setSearchingCEP(false);
+ }
+ }
+
+ function validateLocal(): boolean {
+ const e: Record = {};
+ if (!form.nome.trim()) e.nome = "Nome é obrigatório";
+ if (!form.cpf.trim()) e.cpf = "CPF é obrigatório";
+ setErrors(e);
+ return Object.keys(e).length === 0;
+ }
+
+ function toPayload(): PacienteInput {
+ return {
+ nome: form.nome,
+ nome_social: form.nome_social || null,
+ cpf: form.cpf,
+ rg: form.rg || null,
+ sexo: form.sexo || null,
+ data_nascimento: form.data_nascimento || null,
+ telefone: form.telefone || null,
+ email: form.email || null,
+ endereco: {
+ cep: form.cep || null,
+ logradouro: form.logradouro || null,
+ numero: form.numero || null,
+ complemento: form.complemento || null,
+ bairro: form.bairro || null,
+ cidade: form.cidade || null,
+ estado: form.estado || null,
+ },
+ observacoes: form.observacoes || null,
+ };
+ }
+
+ async function handleSubmit(ev: React.FormEvent) {
+ ev.preventDefault();
+ if (!validateLocal()) return;
+
+ // validação externa do CPF (mock → pode falhar, tratamos erro legível)
+ try {
+ const { valido, existe } = await validarCPF(form.cpf);
+ if (!valido) {
+ setErrors((e) => ({ ...e, cpf: "CPF inválido (validação externa)" }));
+ return;
+ }
+ if (existe && mode === "create") {
+ setErrors((e) => ({ ...e, cpf: "CPF já cadastrado no sistema" }));
+ return;
+ }
+ } catch {
+ // se o mock der 404/timeout, seguimos sem bloquear
+ }
+
+ setSubmitting(true);
+ try {
+ const payload = toPayload();
+
+ let saved: Paciente;
+ if (mode === "create") {
+ saved = await criarPaciente(payload);
+ } else {
+ if (patientId == null) throw new Error("Paciente inexistente para edição");
+ saved = await atualizarPaciente(String(patientId), payload);
+ }
+
+ if (form.photo && saved?.id) {
+ try {
+ await uploadFotoPaciente(saved.id, form.photo);
+ } catch {}
+ }
+
+ if (form.anexos.length && saved?.id) {
+ for (const f of form.anexos) {
+ try {
+ await adicionarAnexo(saved.id, f);
+ } catch {}
+ }
+ }
+
+ onSaved?.(saved);
+ setForm(initial);
+ setPhotoPreview(null);
+ setServerAnexos([]);
+
+ if (inline) onClose?.();
+ else onOpenChange?.(false);
+
+ alert(mode === "create" ? "Paciente cadastrado!" : "Paciente atualizado!");
+ } catch (err: any) {
+ setErrors({ submit: err?.message || "Erro ao salvar paciente." });
+ } finally {
+ setSubmitting(false);
+ }
+ }
+
+ function handlePhoto(e: React.ChangeEvent) {
+ const f = e.target.files?.[0];
+ if (!f) return;
+ if (f.size > 5 * 1024 * 1024) {
+ setErrors((e) => ({ ...e, photo: "Arquivo muito grande. Máx 5MB." }));
+ return;
+ }
+ setField("photo", f);
+ const fr = new FileReader();
+ fr.onload = (ev) => setPhotoPreview(String(ev.target?.result || ""));
+ fr.readAsDataURL(f);
+ }
+
+ function addLocalAnexos(e: React.ChangeEvent) {
+ const fs = Array.from(e.target.files || []);
+ setField("anexos", [...form.anexos, ...fs]);
+ }
+ function removeLocalAnexo(idx: number) {
+ const clone = [...form.anexos];
+ clone.splice(idx, 1);
+ setField("anexos", clone);
+ }
+
+ async function handleRemoverFotoServidor() {
+ if (mode !== "edit" || !patientId) return;
+ try {
+ await removerFotoPaciente(String(patientId));
+ alert("Foto removida.");
+ } catch (e: any) {
+ alert(e?.message || "Não foi possível remover a foto.");
+ }
+ }
+
+ async function handleRemoverAnexoServidor(anexoId: string | number) {
+ if (mode !== "edit" || !patientId) return;
+ try {
+ await removerAnexo(String(patientId), anexoId);
+ setServerAnexos((prev) => prev.filter((a) => String(a.id ?? a.anexo_id) !== String(anexoId)));
+ } catch (e: any) {
+ alert(e?.message || "Não foi possível remover o anexo.");
+ }
+ }
+
+ const content = (
+ <>
+ {errors.submit && (
+
+
+ {errors.submit}
+
+ )}
+
+
+ >
+ );
+
+ if (inline) return {content}
;
+
+ return (
+
+ );
+}
diff --git a/susconecta/components/header.tsx b/susconecta/components/header.tsx
index 5505e4c..b19cdcf 100644
--- a/susconecta/components/header.tsx
+++ b/susconecta/components/header.tsx
@@ -40,11 +40,9 @@ export function Header() {
>
Sou Paciente
-
-
-
+
-
-
-
+
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/lib/api.ts b/susconecta/lib/api.ts
new file mode 100644
index 0000000..1f88487
--- /dev/null
+++ b/susconecta/lib/api.ts
@@ -0,0 +1,255 @@
+/* src/lib/api.ts */
+
+export type ApiOk = {
+ success: boolean;
+ data: T;
+ message?: string;
+ pagination?: {
+ current_page?: number;
+ per_page?: number;
+ total_pages?: number;
+ total?: number;
+ };
+};
+
+export type Endereco = {
+ cep?: string;
+ logradouro?: string;
+ numero?: string;
+ complemento?: string;
+ bairro?: string;
+ cidade?: string;
+ estado?: string;
+};
+
+export type Paciente = {
+ id: string;
+ nome?: string;
+ nome_social?: string | null;
+ cpf?: string;
+ rg?: string | null;
+ sexo?: string | null;
+ data_nascimento?: string | null;
+ telefone?: string;
+ email?: string;
+ endereco?: Endereco;
+ observacoes?: string | null;
+ foto_url?: string | null;
+};
+
+export type PacienteInput = {
+ nome: string;
+ nome_social?: string | null;
+ cpf: string;
+ rg?: string | null;
+ sexo?: string | null;
+ data_nascimento?: string | null;
+ telefone?: string | null;
+ email?: string | null;
+ endereco?: {
+ cep?: string | null;
+ logradouro?: string | null;
+ numero?: string | null;
+ complemento?: string | null;
+ bairro?: string | null;
+ cidade?: string | null;
+ estado?: string | null;
+ };
+ observacoes?: string | null;
+};
+
+// -----------------------------------------------------------------------------
+// Config & helpers
+// -----------------------------------------------------------------------------
+
+const API_BASE = process.env.NEXT_PUBLIC_API_BASE ?? "https://mock.apidog.com/m1/1053378-0-default";
+
+const PATHS = {
+ pacientes: "/pacientes",
+ pacienteId: (id: string | number) => `/pacientes/${id}`,
+ foto: (id: string | number) => `/pacientes/${id}/foto`,
+ anexos: (id: string | number) => `/pacientes/${id}/anexos`,
+ anexoId: (id: string | number, anexoId: string | number) => `/pacientes/${id}/anexos/${anexoId}`,
+ validarCPF: "/pacientes/validar-cpf",
+ cep: (cep: string) => `/utils/cep/${cep}`,
+} as const;
+
+function headers(kind: "json" | "form" = "json"): Record {
+ const h: Record = {};
+ const token = process.env.NEXT_PUBLIC_API_TOKEN?.trim();
+ if (token) h.Authorization = `Bearer ${token}`;
+ if (kind === "json") h["Content-Type"] = "application/json";
+ return h;
+}
+
+function logAPI(title: string, info: { url?: string; payload?: any; result?: any } = {}) {
+ try {
+ console.group(`[API] ${title}`);
+ if (info.url) console.log("url:", info.url);
+ if (info.payload !== undefined) console.log("payload:", info.payload);
+ if (info.result !== undefined) console.log("API result:", info.result);
+ console.groupEnd();
+ } catch {}
+}
+
+async function parse(res: Response): Promise {
+ let json: any = null;
+ try {
+ json = await res.json();
+ } catch {
+ // ignore
+ }
+ if (!res.ok) {
+ const code = json?.apidogError?.code ?? res.status;
+ const msg = json?.apidogError?.message ?? res.statusText;
+ throw new Error(`${code}: ${msg}`);
+ }
+ // muitos endpoints do mock respondem { success, data }
+ return (json?.data ?? json) as T;
+}
+
+// -----------------------------------------------------------------------------
+// Pacientes (CRUD)
+// -----------------------------------------------------------------------------
+
+export async function listarPacientes(params?: { page?: number; limit?: number; q?: string }): Promise {
+ const query = new URLSearchParams();
+ if (params?.page) query.set("page", String(params.page));
+ if (params?.limit) query.set("limit", String(params.limit));
+ if (params?.q) query.set("q", params.q);
+ const url = `${API_BASE}${PATHS.pacientes}${query.toString() ? `?${query.toString()}` : ""}`;
+
+ const res = await fetch(url, { method: "GET", headers: headers("json") });
+ const data = await parse>(res);
+ logAPI("listarPacientes", { url, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function buscarPacientePorId(id: string | number): Promise {
+ const url = `${API_BASE}${PATHS.pacienteId(id)}`;
+ const res = await fetch(url, { method: "GET", headers: headers("json") });
+ const data = await parse>(res);
+ logAPI("buscarPacientePorId", { url, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function criarPaciente(input: PacienteInput): Promise {
+ const url = `${API_BASE}${PATHS.pacientes}`;
+ const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify(input) });
+ const data = await parse>(res);
+ logAPI("criarPaciente", { url, payload: input, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function atualizarPaciente(id: string | number, input: PacienteInput): Promise {
+ const url = `${API_BASE}${PATHS.pacienteId(id)}`;
+ const res = await fetch(url, { method: "PUT", headers: headers("json"), body: JSON.stringify(input) });
+ const data = await parse>(res);
+ logAPI("atualizarPaciente", { url, payload: input, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function excluirPaciente(id: string | number): Promise {
+ const url = `${API_BASE}${PATHS.pacienteId(id)}`;
+ const res = await fetch(url, { method: "DELETE", headers: headers("json") });
+ await parse(res);
+ logAPI("excluirPaciente", { url, result: { ok: true } });
+}
+
+// -----------------------------------------------------------------------------
+// Foto
+// -----------------------------------------------------------------------------
+
+export async function uploadFotoPaciente(id: string | number, file: File): Promise<{ foto_url?: string; thumbnail_url?: string }> {
+ const url = `${API_BASE}${PATHS.foto(id)}`;
+ const fd = new FormData();
+ // nome de campo mais comum no mock
+ fd.append("foto", file);
+ const res = await fetch(url, { method: "POST", headers: headers("form"), body: fd });
+ const data = await parse>(res);
+ logAPI("uploadFotoPaciente", { url, payload: { file: file.name }, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function removerFotoPaciente(id: string | number): Promise {
+ const url = `${API_BASE}${PATHS.foto(id)}`;
+ const res = await fetch(url, { method: "DELETE", headers: headers("json") });
+ await parse(res);
+ logAPI("removerFotoPaciente", { url, result: { ok: true } });
+}
+
+// -----------------------------------------------------------------------------
+// Anexos
+// -----------------------------------------------------------------------------
+
+export async function listarAnexos(id: string | number): Promise {
+ const url = `${API_BASE}${PATHS.anexos(id)}`;
+ const res = await fetch(url, { method: "GET", headers: headers("json") });
+ const data = await parse>(res);
+ logAPI("listarAnexos", { url, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function adicionarAnexo(id: string | number, file: File): Promise {
+ const url = `${API_BASE}${PATHS.anexos(id)}`;
+ const fd = new FormData();
+ // alguns mocks usam "arquivo" e outros "file"; tentamos ambos
+ fd.append("arquivo", file);
+ const res = await fetch(url, { method: "POST", body: fd, headers: headers("form") });
+ const data = await parse>(res);
+ logAPI("adicionarAnexo", { url, payload: { file: file.name }, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function removerAnexo(id: string | number, anexoId: string | number): Promise {
+ const url = `${API_BASE}${PATHS.anexoId(id, anexoId)}`;
+ const res = await fetch(url, { method: "DELETE", headers: headers("json") });
+ await parse(res);
+ logAPI("removerAnexo", { url, result: { ok: true } });
+}
+
+// -----------------------------------------------------------------------------
+// Validações
+// -----------------------------------------------------------------------------
+
+export async function validarCPF(cpf: string): Promise<{ valido: boolean; existe: boolean; paciente_id: string | null }> {
+ const url = `${API_BASE}${PATHS.validarCPF}`;
+ const payload = { cpf };
+ const res = await fetch(url, { method: "POST", headers: headers("json"), body: JSON.stringify(payload) });
+ const data = await parse>(res);
+ logAPI("validarCPF", { url, payload, result: data });
+ return data?.data ?? (data as any);
+}
+
+export async function buscarCepAPI(cep: string): Promise<{ logradouro?: string; bairro?: string; localidade?: string; uf?: string; erro?: boolean }> {
+ const clean = (cep || "").replace(/\D/g, "");
+ const urlMock = `${API_BASE}${PATHS.cep(clean)}`;
+
+ try {
+ const res = await fetch(urlMock, { method: "GET", headers: headers("json") });
+ const data = await parse(res); // pode vir direto ou dentro de {data}
+ logAPI("buscarCEP (mock)", { url: urlMock, payload: { cep: clean }, result: data });
+ const d = data?.data ?? data ?? {};
+ return {
+ logradouro: d.logradouro ?? d.street ?? "",
+ bairro: d.bairro ?? d.neighborhood ?? "",
+ localidade: d.localidade ?? d.city ?? "",
+ uf: d.uf ?? d.state ?? "",
+ erro: false,
+ };
+ } catch {
+ // fallback ViaCEP
+ const urlVia = `https://viacep.com.br/ws/${clean}/json/`;
+ const resV = await fetch(urlVia);
+ const jsonV = await resV.json().catch(() => ({}));
+ logAPI("buscarCEP (ViaCEP/fallback)", { url: urlVia, payload: { cep: clean }, result: jsonV });
+ if (jsonV?.erro) return { erro: true };
+ return {
+ logradouro: jsonV.logradouro ?? "",
+ bairro: jsonV.bairro ?? "",
+ localidade: jsonV.localidade ?? "",
+ uf: jsonV.uf ?? "",
+ erro: false,
+ };
+ }
+}
diff --git a/susconecta/package-lock.json b/susconecta/package-lock.json
index 451526c..8fe3d16 100644
--- a/susconecta/package-lock.json
+++ b/susconecta/package-lock.json
@@ -46,11 +46,11 @@
"geist": "^1.3.1",
"input-otp": "latest",
"lucide-react": "^0.454.0",
- "next": "15.2.4",
+ "next": "14.2.16",
"next-themes": "latest",
- "react": "^19",
+ "react": "^18",
"react-day-picker": "latest",
- "react-dom": "^19",
+ "react-dom": "^18",
"react-hook-form": "latest",
"react-resizable-panels": "latest",
"recharts": "latest",
@@ -63,8 +63,8 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
- "@types/react": "^19",
- "@types/react-dom": "^19",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
@@ -90,16 +90,6 @@
"integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
"license": "MIT"
},
- "node_modules/@emnapi/runtime": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz",
- "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "tslib": "^2.4.0"
- }
- },
"node_modules/@floating-ui/core": {
"version": "1.7.3",
"resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
@@ -147,367 +137,6 @@
"react-hook-form": "^7.0.0"
}
},
- "node_modules/@img/sharp-darwin-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz",
- "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-darwin-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz",
- "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "darwin"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-darwin-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz",
- "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-darwin-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz",
- "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "darwin"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz",
- "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==",
- "cpu": [
- "arm"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz",
- "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-s390x": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz",
- "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==",
- "cpu": [
- "s390x"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linux-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz",
- "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz",
- "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==",
- "cpu": [
- "arm64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-libvips-linuxmusl-x64": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz",
- "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==",
- "cpu": [
- "x64"
- ],
- "license": "LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "linux"
- ],
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-linux-arm": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz",
- "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==",
- "cpu": [
- "arm"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm": "1.0.5"
- }
- },
- "node_modules/@img/sharp-linux-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz",
- "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-s390x": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz",
- "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==",
- "cpu": [
- "s390x"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-s390x": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linux-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz",
- "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linux-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-arm64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz",
- "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==",
- "cpu": [
- "arm64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-linuxmusl-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz",
- "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0",
- "optional": true,
- "os": [
- "linux"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4"
- }
- },
- "node_modules/@img/sharp-wasm32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz",
- "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==",
- "cpu": [
- "wasm32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
- "optional": true,
- "dependencies": {
- "@emnapi/runtime": "^1.2.0"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-ia32": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz",
- "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==",
- "cpu": [
- "ia32"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
- "node_modules/@img/sharp-win32-x64": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz",
- "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==",
- "cpu": [
- "x64"
- ],
- "license": "Apache-2.0 AND LGPL-3.0-or-later",
- "optional": true,
- "os": [
- "win32"
- ],
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- }
- },
"node_modules/@isaacs/fs-minipass": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz",
@@ -572,15 +201,15 @@
}
},
"node_modules/@next/env": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/env/-/env-15.2.4.tgz",
- "integrity": "sha512-+SFtMgoiYP3WoSswuNmxJOCwi06TdWE733D+WPjpXIe4LXGULwEaofiiAy6kbS0+XjM5xF5n3lKuBwN2SnqD9g==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.16.tgz",
+ "integrity": "sha512-fLrX5TfJzHCbnZ9YUSnGW63tMV3L4nSfhgOQ0iCcX21Pt+VSTDuaLsSuL8J/2XAiVA5AnzvXDpf6pMs60QxOag==",
"license": "MIT"
},
"node_modules/@next/swc-darwin-arm64": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.2.4.tgz",
- "integrity": "sha512-1AnMfs655ipJEDC/FHkSr0r3lXBgpqKo4K1kiwfUf3iE68rDFXZ1TtHdMvf7D0hMItgDZ7Vuq3JgNMbt/+3bYw==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-14.2.16.tgz",
+ "integrity": "sha512-uFT34QojYkf0+nn6MEZ4gIWQ5aqGF11uIZ1HSxG+cSbj+Mg3+tYm8qXYd3dKN5jqKUm5rBVvf1PBRO/MeQ6rxw==",
"cpu": [
"arm64"
],
@@ -594,9 +223,9 @@
}
},
"node_modules/@next/swc-darwin-x64": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.2.4.tgz",
- "integrity": "sha512-3qK2zb5EwCwxnO2HeO+TRqCubeI/NgCe+kL5dTJlPldV/uwCnUgC7VbEzgmxbfrkbjehL4H9BPztWOEtsoMwew==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-14.2.16.tgz",
+ "integrity": "sha512-mCecsFkYezem0QiZlg2bau3Xul77VxUD38b/auAjohMA22G9KTJneUYMv78vWoCCFkleFAhY1NIvbyjj1ncG9g==",
"cpu": [
"x64"
],
@@ -610,9 +239,9 @@
}
},
"node_modules/@next/swc-linux-arm64-gnu": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.2.4.tgz",
- "integrity": "sha512-HFN6GKUcrTWvem8AZN7tT95zPb0GUGv9v0d0iyuTb303vbXkkbHDp/DxufB04jNVD+IN9yHy7y/6Mqq0h0YVaQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-14.2.16.tgz",
+ "integrity": "sha512-yhkNA36+ECTC91KSyZcgWgKrYIyDnXZj8PqtJ+c2pMvj45xf7y/HrgI17hLdrcYamLfVt7pBaJUMxADtPaczHA==",
"cpu": [
"arm64"
],
@@ -626,9 +255,9 @@
}
},
"node_modules/@next/swc-linux-arm64-musl": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.2.4.tgz",
- "integrity": "sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-14.2.16.tgz",
+ "integrity": "sha512-X2YSyu5RMys8R2lA0yLMCOCtqFOoLxrq2YbazFvcPOE4i/isubYjkh+JCpRmqYfEuCVltvlo+oGfj/b5T2pKUA==",
"cpu": [
"arm64"
],
@@ -642,9 +271,9 @@
}
},
"node_modules/@next/swc-linux-x64-gnu": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.2.4.tgz",
- "integrity": "sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-14.2.16.tgz",
+ "integrity": "sha512-9AGcX7VAkGbc5zTSa+bjQ757tkjr6C/pKS7OK8cX7QEiK6MHIIezBLcQ7gQqbDW2k5yaqba2aDtaBeyyZh1i6Q==",
"cpu": [
"x64"
],
@@ -658,9 +287,9 @@
}
},
"node_modules/@next/swc-linux-x64-musl": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.2.4.tgz",
- "integrity": "sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-14.2.16.tgz",
+ "integrity": "sha512-Klgeagrdun4WWDaOizdbtIIm8khUDQJ/5cRzdpXHfkbY91LxBXeejL4kbZBrpR/nmgRrQvmz4l3OtttNVkz2Sg==",
"cpu": [
"x64"
],
@@ -674,9 +303,9 @@
}
},
"node_modules/@next/swc-win32-arm64-msvc": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.2.4.tgz",
- "integrity": "sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-14.2.16.tgz",
+ "integrity": "sha512-PwW8A1UC1Y0xIm83G3yFGPiOBftJK4zukTmk7DI1CebyMOoaVpd8aSy7K6GhobzhkjYvqS/QmzcfsWG2Dwizdg==",
"cpu": [
"arm64"
],
@@ -689,10 +318,26 @@
"node": ">= 10"
}
},
+ "node_modules/@next/swc-win32-ia32-msvc": {
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-14.2.16.tgz",
+ "integrity": "sha512-jhPl3nN0oKEshJBNDAo0etGMzv0j3q3VYorTSFqH1o3rwv1MQRdor27u1zhkgsHPNeY1jxcgyx1ZsCkDD1IHgg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
"node_modules/@next/swc-win32-x64-msvc": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.2.4.tgz",
- "integrity": "sha512-SbnWkJmkS7Xl3kre8SdMF6F/XDh1DTFEhp0jRTj/uB8iPKoU2bb2NDfcu+iifv1+mxQEd1g2vvSxcZbXSKyWiQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-14.2.16.tgz",
+ "integrity": "sha512-OA7NtfxgirCjfqt+02BqxC3MIgM/JaGjw9tOe4fyZgPsqfseNiMPnCRP44Pfs+Gpo9zPN+SXaFsgP6vk8d571A==",
"cpu": [
"x64"
],
@@ -2104,12 +1749,13 @@
"license": "Apache-2.0"
},
"node_modules/@swc/helpers": {
- "version": "0.5.15",
- "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
- "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.5.tgz",
+ "integrity": "sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==",
"license": "Apache-2.0",
"dependencies": {
- "tslib": "^2.8.0"
+ "@swc/counter": "^0.1.3",
+ "tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/node": {
@@ -2461,24 +2107,32 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/prop-types": {
+ "version": "15.7.15",
+ "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz",
+ "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
"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==",
- "dev": true,
+ "version": "18.3.24",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.24.tgz",
+ "integrity": "sha512-0dLEBsA1kI3OezMBF8nSsb7Nk19ZnsyE1LLhB8r27KbgU5H4pvuqZLdtE+aUkJVoXgTVuA+iLIwmZ0TuK4tx6A==",
+ "devOptional": true,
"license": "MIT",
"dependencies": {
+ "@types/prop-types": "*",
"csstype": "^3.0.2"
}
},
"node_modules/@types/react-dom": {
- "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,
+ "version": "18.3.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz",
+ "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==",
+ "devOptional": true,
"license": "MIT",
"peerDependencies": {
- "@types/react": "^19.0.0"
+ "@types/react": "^18.0.0"
}
},
"node_modules/@types/use-sync-external-store": {
@@ -2673,56 +2327,11 @@
"react-dom": "^18 || ^19 || ^19.0.0-rc"
}
},
- "node_modules/color": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz",
- "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-convert": "^2.0.1",
- "color-string": "^1.9.0"
- },
- "engines": {
- "node": ">=12.5.0"
- }
- },
- "node_modules/color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-name": "~1.1.4"
- },
- "engines": {
- "node": ">=7.0.0"
- }
- },
- "node_modules/color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
- "license": "MIT",
- "optional": true
- },
- "node_modules/color-string": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz",
- "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "color-name": "^1.0.0",
- "simple-swizzle": "^0.2.2"
- }
- },
"node_modules/csstype": {
"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": {
@@ -2872,7 +2481,7 @@
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz",
"integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"engines": {
"node": ">=8"
@@ -2992,7 +2601,6 @@
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true,
"license": "ISC"
},
"node_modules/immer": {
@@ -3024,13 +2632,6 @@
"node": ">=12"
}
},
- "node_modules/is-arrayish": {
- "version": "0.3.2",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
- "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==",
- "license": "MIT",
- "optional": true
- },
"node_modules/jiti": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz",
@@ -3041,6 +2642,12 @@
"jiti": "lib/jiti-cli.mjs"
}
},
+ "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/lightningcss": {
"version": "1.30.1",
"resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.1.tgz",
@@ -3280,6 +2887,18 @@
"url": "https://opencollective.com/parcel"
}
},
+ "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/lucide-react": {
"version": "0.454.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.454.0.tgz",
@@ -3357,42 +2976,41 @@
}
},
"node_modules/next": {
- "version": "15.2.4",
- "resolved": "https://registry.npmjs.org/next/-/next-15.2.4.tgz",
- "integrity": "sha512-VwL+LAaPSxEkd3lU2xWbgEOtrM8oedmyhBqaVNmgKB+GvZlCy9rgaEc+y2on0wv+l0oSFqLtYD6dcC1eAedUaQ==",
+ "version": "14.2.16",
+ "resolved": "https://registry.npmjs.org/next/-/next-14.2.16.tgz",
+ "integrity": "sha512-LcO7WnFu6lYSvCzZoo1dB+IO0xXz5uEv52HF1IUN0IqVTUIZGHuuR10I5efiLadGt+4oZqTcNZyVVEem/TM5nA==",
"license": "MIT",
"dependencies": {
- "@next/env": "15.2.4",
- "@swc/counter": "0.1.3",
- "@swc/helpers": "0.5.15",
+ "@next/env": "14.2.16",
+ "@swc/helpers": "0.5.5",
"busboy": "1.6.0",
"caniuse-lite": "^1.0.30001579",
+ "graceful-fs": "^4.2.11",
"postcss": "8.4.31",
- "styled-jsx": "5.1.6"
+ "styled-jsx": "5.1.1"
},
"bin": {
"next": "dist/bin/next"
},
"engines": {
- "node": "^18.18.0 || ^19.8.0 || >= 20.0.0"
+ "node": ">=18.17.0"
},
"optionalDependencies": {
- "@next/swc-darwin-arm64": "15.2.4",
- "@next/swc-darwin-x64": "15.2.4",
- "@next/swc-linux-arm64-gnu": "15.2.4",
- "@next/swc-linux-arm64-musl": "15.2.4",
- "@next/swc-linux-x64-gnu": "15.2.4",
- "@next/swc-linux-x64-musl": "15.2.4",
- "@next/swc-win32-arm64-msvc": "15.2.4",
- "@next/swc-win32-x64-msvc": "15.2.4",
- "sharp": "^0.33.5"
+ "@next/swc-darwin-arm64": "14.2.16",
+ "@next/swc-darwin-x64": "14.2.16",
+ "@next/swc-linux-arm64-gnu": "14.2.16",
+ "@next/swc-linux-arm64-musl": "14.2.16",
+ "@next/swc-linux-x64-gnu": "14.2.16",
+ "@next/swc-linux-x64-musl": "14.2.16",
+ "@next/swc-win32-arm64-msvc": "14.2.16",
+ "@next/swc-win32-ia32-msvc": "14.2.16",
+ "@next/swc-win32-x64-msvc": "14.2.16"
},
"peerDependencies": {
"@opentelemetry/api": "^1.1.0",
"@playwright/test": "^1.41.2",
- "babel-plugin-react-compiler": "*",
- "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
- "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
"sass": "^1.3.0"
},
"peerDependenciesMeta": {
@@ -3402,9 +3020,6 @@
"@playwright/test": {
"optional": true
},
- "babel-plugin-react-compiler": {
- "optional": true
- },
"sass": {
"optional": true
}
@@ -3473,7 +3088,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",
@@ -3505,10 +3119,13 @@
"license": "MIT"
},
"node_modules/react": {
- "version": "19.1.1",
- "resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
- "integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
"license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
"engines": {
"node": ">=0.10.0"
}
@@ -3535,15 +3152,16 @@
}
},
"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==",
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
"license": "MIT",
"dependencies": {
- "scheduler": "^0.26.0"
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
},
"peerDependencies": {
- "react": "^19.1.1"
+ "react": "^18.3.1"
}
},
"node_modules/react-hook-form": {
@@ -3562,6 +3180,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",
@@ -3713,22 +3338,12 @@
"license": "MIT"
},
"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"
- },
- "node_modules/semver": {
- "version": "7.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
- "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
- "license": "ISC",
- "optional": true,
- "bin": {
- "semver": "bin/semver.js"
- },
- "engines": {
- "node": ">=10"
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
}
},
"node_modules/server-only": {
@@ -3737,56 +3352,6 @@
"integrity": "sha512-qepMx2JxAa5jjfzxG79yPPq+8BuFToHd1hm7kI+Z4zAq1ftQiP7HcxMhDDItrbtwVeLg/cY2JnKnrcFkmiswNA==",
"license": "MIT"
},
- "node_modules/sharp": {
- "version": "0.33.5",
- "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz",
- "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==",
- "hasInstallScript": true,
- "license": "Apache-2.0",
- "optional": true,
- "dependencies": {
- "color": "^4.2.3",
- "detect-libc": "^2.0.3",
- "semver": "^7.6.3"
- },
- "engines": {
- "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
- },
- "funding": {
- "url": "https://opencollective.com/libvips"
- },
- "optionalDependencies": {
- "@img/sharp-darwin-arm64": "0.33.5",
- "@img/sharp-darwin-x64": "0.33.5",
- "@img/sharp-libvips-darwin-arm64": "1.0.4",
- "@img/sharp-libvips-darwin-x64": "1.0.4",
- "@img/sharp-libvips-linux-arm": "1.0.5",
- "@img/sharp-libvips-linux-arm64": "1.0.4",
- "@img/sharp-libvips-linux-s390x": "1.0.4",
- "@img/sharp-libvips-linux-x64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-arm64": "1.0.4",
- "@img/sharp-libvips-linuxmusl-x64": "1.0.4",
- "@img/sharp-linux-arm": "0.33.5",
- "@img/sharp-linux-arm64": "0.33.5",
- "@img/sharp-linux-s390x": "0.33.5",
- "@img/sharp-linux-x64": "0.33.5",
- "@img/sharp-linuxmusl-arm64": "0.33.5",
- "@img/sharp-linuxmusl-x64": "0.33.5",
- "@img/sharp-wasm32": "0.33.5",
- "@img/sharp-win32-ia32": "0.33.5",
- "@img/sharp-win32-x64": "0.33.5"
- }
- },
- "node_modules/simple-swizzle": {
- "version": "0.2.2",
- "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
- "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==",
- "license": "MIT",
- "optional": true,
- "dependencies": {
- "is-arrayish": "^0.3.1"
- }
- },
"node_modules/sonner": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/sonner/-/sonner-2.0.7.tgz",
@@ -3815,9 +3380,9 @@
}
},
"node_modules/styled-jsx": {
- "version": "5.1.6",
- "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
- "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
+ "integrity": "sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==",
"license": "MIT",
"dependencies": {
"client-only": "0.0.1"
@@ -3826,7 +3391,7 @@
"node": ">= 12.0.0"
},
"peerDependencies": {
- "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0"
},
"peerDependenciesMeta": {
"@babel/core": {
@@ -3851,7 +3416,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": {
diff --git a/susconecta/package.json b/susconecta/package.json
index 8a8bba2..b5b701a 100644
--- a/susconecta/package.json
+++ b/susconecta/package.json
@@ -47,11 +47,11 @@
"geist": "^1.3.1",
"input-otp": "latest",
"lucide-react": "^0.454.0",
- "next": "15.2.4",
+ "next": "14.2.16",
"next-themes": "latest",
- "react": "^19",
+ "react": "^18",
"react-day-picker": "latest",
- "react-dom": "^19",
+ "react-dom": "^18",
"react-hook-form": "latest",
"react-resizable-panels": "latest",
"recharts": "latest",
@@ -64,8 +64,8 @@
"devDependencies": {
"@tailwindcss/postcss": "^4.1.9",
"@types/node": "^22",
- "@types/react": "^19",
- "@types/react-dom": "^19",
+ "@types/react": "^18",
+ "@types/react-dom": "^18",
"postcss": "^8.5",
"tailwindcss": "^4.1.9",
"tw-animate-css": "1.3.3",
diff --git a/susconecta/public/medico.jpg b/susconecta/public/medico.jpg
new file mode 100644
index 0000000..97b5ad2
Binary files /dev/null and b/susconecta/public/medico.jpg differ
diff --git a/susconecta/public/professional-doctor-in-white-coat-smiling-confiden.png b/susconecta/public/professional-doctor-in-white-coat-smiling-confiden.png
deleted file mode 100644
index 8c4267d..0000000
Binary files a/susconecta/public/professional-doctor-in-white-coat-smiling-confiden.png and /dev/null differ
diff --git a/susconecta/public/professional-working-on-laptop-in-modern-office-en.jpg b/susconecta/public/professional-working-on-laptop-in-modern-office-en.jpg
deleted file mode 100644
index e3b35d6..0000000
Binary files a/susconecta/public/professional-working-on-laptop-in-modern-office-en.jpg and /dev/null differ
diff --git a/susconecta/tsconfig.json b/susconecta/tsconfig.json
index 4b2dc7b..48c9ec9 100644
--- a/susconecta/tsconfig.json
+++ b/susconecta/tsconfig.json
@@ -22,6 +22,6 @@
"@/*": ["./*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
+ "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "lib/api.js"],
"exclude": ["node_modules"]
}