Refactor frontend components and contexts for improved structure and functionality
- Removed unnecessary comments and documentation from CardContainer, Header, HistoryCharts, MenuButton, PollutionMap, SideMenu, SummaryCards, and ThemeButton components. - Updated import paths to use aliasing for cleaner code. - Replaced the old context implementations (ConfigContext, DataContext, ThemeContext) with new hooks and context structure for better state management. - Introduced a new axios instance for API calls to streamline requests. - Added new utility functions for date and error parsing. - Updated the main entry point and pages to reflect new context and component structures. - Created new configuration files for development and production environments. - Enhanced data fetching logic with improved error handling and loading states.
This commit is contained in:
9
frontend/jsconfig.json
Normal file
9
frontend/jsconfig.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": ["src"]
|
||||||
|
}
|
||||||
115
frontend/package-lock.json
generated
115
frontend/package-lock.json
generated
@@ -13,6 +13,7 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
@@ -1718,6 +1719,12 @@
|
|||||||
"node": ">= 0.4"
|
"node": ">= 0.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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/available-typed-arrays": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.7",
|
"version": "1.0.7",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
|
||||||
@@ -1734,6 +1741,17 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.9.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz",
|
||||||
|
"integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@@ -1827,7 +1845,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
|
||||||
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -1934,6 +1951,18 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"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"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@@ -2094,6 +2123,15 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/doctrine": {
|
"node_modules/doctrine": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
|
||||||
@@ -2111,7 +2149,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -2199,7 +2236,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
|
||||||
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2209,7 +2245,6 @@
|
|||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
|
||||||
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2247,7 +2282,6 @@
|
|||||||
"version": "1.1.1",
|
"version": "1.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
|
||||||
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0"
|
"es-errors": "^1.3.0"
|
||||||
@@ -2260,7 +2294,6 @@
|
|||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
|
||||||
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"es-errors": "^1.3.0",
|
"es-errors": "^1.3.0",
|
||||||
@@ -2656,6 +2689,26 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/for-each": {
|
"node_modules/for-each": {
|
||||||
"version": "0.3.5",
|
"version": "0.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
|
||||||
@@ -2672,6 +2725,21 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz",
|
||||||
|
"integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"es-set-tostringtag": "^2.1.0",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@@ -2691,7 +2759,6 @@
|
|||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
|
||||||
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/ljharb"
|
"url": "https://github.com/sponsors/ljharb"
|
||||||
@@ -2742,7 +2809,6 @@
|
|||||||
"version": "1.2.7",
|
"version": "1.2.7",
|
||||||
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz",
|
||||||
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
"integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"call-bind-apply-helpers": "^1.0.1",
|
"call-bind-apply-helpers": "^1.0.1",
|
||||||
@@ -2767,7 +2833,6 @@
|
|||||||
"version": "1.0.1",
|
"version": "1.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
|
||||||
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"dunder-proto": "^1.0.1",
|
"dunder-proto": "^1.0.1",
|
||||||
@@ -2842,7 +2907,6 @@
|
|||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2907,7 +2971,6 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
|
||||||
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"node": ">= 0.4"
|
||||||
@@ -2920,7 +2983,6 @@
|
|||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
|
||||||
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"has-symbols": "^1.0.3"
|
"has-symbols": "^1.0.3"
|
||||||
@@ -2936,7 +2998,6 @@
|
|||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
|
||||||
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"function-bind": "^1.1.2"
|
"function-bind": "^1.1.2"
|
||||||
@@ -3558,12 +3619,32 @@
|
|||||||
"version": "1.1.0",
|
"version": "1.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
|
||||||
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.4"
|
"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/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@@ -3911,6 +3992,12 @@
|
|||||||
"react-is": "^16.13.1"
|
"react-is": "^16.13.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"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==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/punycode": {
|
"node_modules/punycode": {
|
||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
"@fortawesome/free-regular-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
"@fortawesome/free-solid-svg-icons": "^6.7.2",
|
||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
|
"axios": "^1.9.0",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
"chart.js": "^4.4.8",
|
"chart.js": "^4.4.8",
|
||||||
"leaflet": "^1.9.4",
|
"leaflet": "^1.9.4",
|
||||||
|
|||||||
98
frontend/public/config/settings.prod.json
Normal file
98
frontend/public/config/settings.prod.json
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"userConfig": {
|
||||||
|
"city": [
|
||||||
|
37.38283,
|
||||||
|
-5.97317
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"appConfig": {
|
||||||
|
"endpoints": {
|
||||||
|
"DATA_URL": "https://contaminus.miarma.net/api/raw/v1",
|
||||||
|
"LOGIC_URL": "https://contaminus.miarma.net/api/v1",
|
||||||
|
"GET_GROUPS": "/groups",
|
||||||
|
"GET_GROUP_BY_ID": "/groups/{0}",
|
||||||
|
"GET_GROUP_DEVICES": "/groups/{0}/devices",
|
||||||
|
"POST_GROUPS": "/groups",
|
||||||
|
"PUT_GROUP_BY_ID": "/groups/{0}",
|
||||||
|
"GET_DEVICES": "/devices",
|
||||||
|
"GET_DEVICE_BY_ID": "/devices/{0}",
|
||||||
|
"GET_DEVICE_SENSORS": "/devices/{0}/sensors",
|
||||||
|
"GET_DEVICE_LATEST_VALUES": "/devices/{0}/latest",
|
||||||
|
"GET_DEVICE_POLLUTION_MAP": "/devices/{0}/pollution-map",
|
||||||
|
"GET_DEVICE_HISTORY": "/devices/{0}/history",
|
||||||
|
"POST_DEVICES": "/devices",
|
||||||
|
"PUT_DEVICE_BY_ID": "/devices/{0}",
|
||||||
|
"GET_SENSORS": "/sensors",
|
||||||
|
"GET_SENSOR_BY_ID": "/sensors/{0}",
|
||||||
|
"GET_SENSOR_VALUES": "/sensors/{0}/values",
|
||||||
|
"POST_SENSORS": "/sensors",
|
||||||
|
"PUT_SENSOR_BY_ID": "/sensors/{0}",
|
||||||
|
"GET_ACTUATORS": "/actuators",
|
||||||
|
"GET_ACTUATOR_BY_ID": "/actuators/{0}",
|
||||||
|
"POST_ACTUATORS": "/actuators",
|
||||||
|
"PUT_ACTUATOR_BY_ID": "/actuators/{0}",
|
||||||
|
"GET_GPS_VALUES": "/gps-values",
|
||||||
|
"GET_GPS_VALUE_BY_ID": "/gps-values/{0}",
|
||||||
|
"POST_GPS_VALUES": "/gps-values",
|
||||||
|
"GET_AIR_VALUES": "/air-values",
|
||||||
|
"GET_AIR_VALUE_BY_ID": "/air-values/{0}",
|
||||||
|
"POST_AIR_VALUES": "/air-values"
|
||||||
|
},
|
||||||
|
"historyChartConfig": {
|
||||||
|
"chartOptionsDark": {
|
||||||
|
"responsive": true,
|
||||||
|
"maintainAspectRatio": false,
|
||||||
|
"scales": {
|
||||||
|
"x": {
|
||||||
|
"grid": {
|
||||||
|
"color": "rgba(255, 255, 255, 0.1)"
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"color": "#E0E0E0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"grid": {
|
||||||
|
"color": "rgba(255, 255, 255, 0.1)"
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"color": "#E0E0E0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"legend": {
|
||||||
|
"display": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chartOptionsLight": {
|
||||||
|
"responsive": true,
|
||||||
|
"maintainAspectRatio": false,
|
||||||
|
"scales": {
|
||||||
|
"x": {
|
||||||
|
"grid": {
|
||||||
|
"color": "rgba(0, 0, 0, 0.1)"
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"color": "#333"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"y": {
|
||||||
|
"grid": {
|
||||||
|
"color": "rgba(0, 0, 0, 0.1)"
|
||||||
|
},
|
||||||
|
"ticks": {
|
||||||
|
"color": "#333"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"plugins": {
|
||||||
|
"legend": {
|
||||||
|
"display": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
frontend/src/api/axiosInstance.js
Normal file
14
frontend/src/api/axiosInstance.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
const createAxiosInstance = (baseURL, token) => {
|
||||||
|
const instance = axios.create({
|
||||||
|
baseURL,
|
||||||
|
headers: {
|
||||||
|
...(token && { Authorization: `Bearer ${token}` }),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default createAxiosInstance;
|
||||||
@@ -1,45 +1,18 @@
|
|||||||
import '../css/App.css'
|
import '@/css/App.css'
|
||||||
import 'leaflet/dist/leaflet.css'
|
import 'leaflet/dist/leaflet.css'
|
||||||
import 'bootstrap/dist/css/bootstrap.min.css'
|
import 'bootstrap/dist/css/bootstrap.min.css'
|
||||||
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
|
import 'bootstrap/dist/js/bootstrap.bundle.min.js'
|
||||||
|
|
||||||
import Home from '../pages/Home.jsx'
|
import Home from '@/pages/Home.jsx'
|
||||||
import Dashboard from '../pages/Dashboard.jsx'
|
import Dashboard from '@/pages/Dashboard.jsx'
|
||||||
import MenuButton from './MenuButton.jsx'
|
import MenuButton from './MenuButton.jsx'
|
||||||
import SideMenu from './SideMenu.jsx'
|
import SideMenu from './SideMenu.jsx'
|
||||||
import ThemeButton from '../components/ThemeButton.jsx'
|
import ThemeButton from '@/components/ThemeButton.jsx'
|
||||||
import Header from '../components/Header.jsx'
|
import Header from '@/components/Header.jsx'
|
||||||
|
|
||||||
import { Routes, Route } from 'react-router-dom'
|
import { Routes, Route } from 'react-router-dom'
|
||||||
import { useState } from 'react'
|
import { useState } from 'react'
|
||||||
|
|
||||||
/**
|
|
||||||
* App.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente App, que es el componente principal de la aplicación.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - '../css/App.css': Archivo CSS que contiene los estilos globales de la aplicación.
|
|
||||||
* - 'leaflet/dist/leaflet.css': Archivo CSS que contiene los estilos para los mapas de Leaflet.
|
|
||||||
* - 'bootstrap/dist/css/bootstrap.min.css': Archivo CSS que contiene los estilos de Bootstrap.
|
|
||||||
* - 'bootstrap/dist/js/bootstrap.bundle.min.js': Archivo JS que contiene los scripts de Bootstrap.
|
|
||||||
* - Header: Componente que representa el encabezado de la página.
|
|
||||||
* - Home: Componente que representa la página principal de la aplicación.
|
|
||||||
* - MenuButton: Componente que representa el botón del menú lateral.
|
|
||||||
* - SideMenu: Componente que representa el menú lateral.
|
|
||||||
* - ThemeButton: Componente que representa el botón de cambio de tema.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - App: Componente principal que renderiza la página Home.
|
|
||||||
* - Planea añadir un React Router en el futuro.
|
|
||||||
* - El componente Header muestra el título y subtítulo de la página.
|
|
||||||
* - El componente MenuButton muestra un botón para abrir el menú lateral.
|
|
||||||
* - El componente SideMenu muestra un menú lateral con opciones de navegación.
|
|
||||||
* - El componente ThemeButton muestra un botón para cambiar el tema de la aplicación.
|
|
||||||
* - El componente Home contiene el contenido principal de la aplicación.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const [isSideMenuOpen, setIsSideMenuOpen] = useState(false);
|
const [isSideMenuOpen, setIsSideMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -1,32 +1,7 @@
|
|||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
import { useState, useEffect, useRef } from "react";
|
import { useState, useEffect, useRef } from "react";
|
||||||
import "../css/Card.css";
|
import "@/css/Card.css";
|
||||||
import { useTheme } from "../contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
|
||||||
/**
|
|
||||||
* Card.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente Card, que representa una tarjeta individual con un título, estado y contenido.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
* - useState, useEffect, useRef: Hooks de React para manejar estados, efectos secundarios y referencias.
|
|
||||||
* - "../css/Card.css": Archivo CSS que contiene los estilos para las tarjetas.
|
|
||||||
* - useTheme: Hook personalizado para acceder al contexto del tema.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - Card: Componente que renderiza una tarjeta con un título, estado y contenido.
|
|
||||||
* - Utiliza el hook `useTheme` para aplicar la clase correspondiente al tema actual.
|
|
||||||
* - Ajusta el título de la tarjeta según el tamaño de la tarjeta.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - Card espera una propiedad `title` que es un string requerido.
|
|
||||||
* - Card espera una propiedad `status` que es un string requerido.
|
|
||||||
* - Card espera una propiedad `children` que es un nodo de React requerido.
|
|
||||||
* - Card espera una propiedad `styleMode` que es opcional y puede ser "override" o una cadena vacía.
|
|
||||||
* - Card espera una propiedad `className` que es un string opcional.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Card = ({ title, status, children, styleMode, className, titleIcon }) => {
|
const Card = ({ title, status, children, styleMode, className, titleIcon }) => {
|
||||||
const cardRef = useRef(null);
|
const cardRef = useRef(null);
|
||||||
|
|||||||
@@ -1,25 +1,6 @@
|
|||||||
import Card from "./Card.jsx";
|
import Card from "./Card.jsx";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
/**
|
|
||||||
* CardContainer.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente CardContainer, que actúa como contenedor para múltiples componentes Card.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - Card: Componente que representa una tarjeta individual.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - CardContainer: Componente que renderiza un contenedor (`div`) con una fila de tarjetas (`Card`).
|
|
||||||
* - Utiliza `props.cards` para mapear y renderizar cada tarjeta con su contenido.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - CardContainer espera una propiedad `cards` que es un array de objetos con las propiedades `title`, `content` y `status`.
|
|
||||||
* - CardContainer espera una propiedad `className` que es un string opcional.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const CardContainer = ({ cards, className }) => {
|
const CardContainer = ({ cards, className }) => {
|
||||||
return (
|
return (
|
||||||
<div className={`row justify-content-center g-0 ${className}`}>
|
<div className={`row justify-content-center g-0 ${className}`}>
|
||||||
|
|||||||
@@ -1,26 +1,6 @@
|
|||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import '../css/Header.css';
|
import '@/css/Header.css';
|
||||||
import { useTheme } from "../contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
|
||||||
/**
|
|
||||||
* Header.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente Header, que muestra el encabezado de la página con un título y un subtítulo.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
* - "../css/Header.css": Archivo CSS que contiene los estilos para el encabezado.
|
|
||||||
* - useTheme: Hook personalizado para acceder al contexto del tema.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - Header: Componente que renderiza un encabezado con un título y un subtítulo.
|
|
||||||
* - Utiliza el hook `useTheme` para aplicar la clase correspondiente al tema actual.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - Header espera una propiedad `title` que es un string requerido.
|
|
||||||
* - Header espera una propiedad `subtitle` que es un string opcional.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const Header = (props) => {
|
const Header = (props) => {
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|||||||
@@ -1,39 +1,13 @@
|
|||||||
import { Line } from "react-chartjs-2";
|
import { Line } from "react-chartjs-2";
|
||||||
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Filler } from "chart.js";
|
import { Chart as ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Filler } from "chart.js";
|
||||||
import CardContainer from "./CardContainer";
|
import CardContainer from "./CardContainer";
|
||||||
import "../css/HistoryCharts.css";
|
import "@/css/HistoryCharts.css";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
import { useTheme } from "../contexts/ThemeContext.jsx";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
import { DataProvider, useData } from "../contexts/DataContext.jsx";
|
import { DataProvider } from "@/context/DataContext.jsx";
|
||||||
import { useConfig } from "../contexts/ConfigContext.jsx";
|
import { useDataContext } from "@/hooks/useDataContext";
|
||||||
|
import { useConfig } from "@/hooks/useConfig";
|
||||||
/**
|
|
||||||
* HistoryCharts.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente HistoryCharts, que muestra gráficos históricos de datos obtenidos de sensores.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - Line: Componente de react-chartjs-2 para renderizar gráficos de líneas.
|
|
||||||
* - ChartJS, LineElement, PointElement, LinearScale, CategoryScale, Filler: Módulos de chart.js para configurar y registrar los elementos del gráfico.
|
|
||||||
* - CardContainer: Componente que actúa como contenedor para las tarjetas.
|
|
||||||
* - "../css/HistoryCharts.css": Archivo CSS que contiene los estilos para los gráficos históricos.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
* - useTheme: Hook personalizado para acceder al contexto del tema.
|
|
||||||
* - DataProvider, useData: Funciones del contexto de datos para obtener y manejar datos.
|
|
||||||
* - useConfig: Hook personalizado para acceder al contexto de configuración.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - HistoryCharts: Componente que configura la solicitud de datos y utiliza el DataProvider para obtener datos de sensores.
|
|
||||||
* - Muestra mensajes de carga y error según el estado de la configuración.
|
|
||||||
* - HistoryChartsContent: Componente que procesa los datos obtenidos y renderiza los gráficos históricos.
|
|
||||||
* - Utiliza el hook `useData` para acceder a los datos de sensores.
|
|
||||||
* - Renderiza gráficos de líneas con diferentes colores según el tipo de dato (temperatura, humedad, contaminación).
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - HistoryChartsContent espera propiedades `options` (objeto), `timeLabels` (array) y `data` (array).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
ChartJS.register(LineElement, PointElement, LinearScale, CategoryScale, Filler);
|
||||||
|
|
||||||
@@ -44,8 +18,8 @@ const HistoryCharts = () => {
|
|||||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||||
if (!config) return <p>Configuración no disponible.</p>;
|
if (!config) return <p>Configuración no disponible.</p>;
|
||||||
|
|
||||||
const BASE = config.appConfig.endpoints.LOGIC_URL;
|
const BASE = config.appConfig.endpoints.DATA_URL;
|
||||||
const ENDPOINT = config.appConfig.endpoints.sensors;
|
const ENDPOINT = config.appConfig.endpoints.GET_SENSORS;
|
||||||
|
|
||||||
const reqConfig = {
|
const reqConfig = {
|
||||||
baseUrl: `${BASE}${ENDPOINT}`,
|
baseUrl: `${BASE}${ENDPOINT}`,
|
||||||
@@ -61,7 +35,7 @@ const HistoryCharts = () => {
|
|||||||
|
|
||||||
const HistoryChartsContent = () => {
|
const HistoryChartsContent = () => {
|
||||||
const { config } = useConfig();
|
const { config } = useConfig();
|
||||||
const { data, loading } = useData();
|
const { data, loading, error } = useDataContext();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
const optionsDark = config?.appConfig?.historyChartConfig?.chartOptionsDark ?? {};
|
const optionsDark = config?.appConfig?.historyChartConfig?.chartOptionsDark ?? {};
|
||||||
@@ -74,6 +48,7 @@ const HistoryChartsContent = () => {
|
|||||||
]
|
]
|
||||||
|
|
||||||
if (loading) return <p>Cargando datos...</p>;
|
if (loading) return <p>Cargando datos...</p>;
|
||||||
|
if (error) return <p>Datos no disponibles.</p>;
|
||||||
|
|
||||||
const temperatureData = [];
|
const temperatureData = [];
|
||||||
const humidityData = [];
|
const humidityData = [];
|
||||||
|
|||||||
@@ -1,28 +1,10 @@
|
|||||||
import "../css/MenuButton.css";
|
import "@/css/MenuButton.css";
|
||||||
|
|
||||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||||
import { faBars } from '@fortawesome/free-solid-svg-icons';
|
import { faBars } from '@fortawesome/free-solid-svg-icons';
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
/** ⚠️ EN PRUEBAS ⚠️
|
const MenuButton = ({ onClick }) => {
|
||||||
* MenuButton.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente MenuButton, que muestra un botón de menú con un icono de barras.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - "../css/MenuButton.css": Archivo CSS que contiene los estilos para el botón de menú.
|
|
||||||
* - FontAwesomeIcon, faBars: Componentes e iconos de FontAwesome para mostrar el icono de barras.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - MenuButton: Componente que renderiza un botón con un icono de barras.
|
|
||||||
* - Utiliza la propiedad `onClick` para manejar el evento de clic del botón.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - MenuButton espera una propiedad `onClick` que es una función requerida.
|
|
||||||
* ⚠️ EN PRUEBAS ⚠️ **/
|
|
||||||
|
|
||||||
export default function MenuButton({ onClick }) {
|
|
||||||
return (
|
return (
|
||||||
<button className="menuBtn" onClick={onClick}>
|
<button className="menuBtn" onClick={onClick}>
|
||||||
<FontAwesomeIcon icon={faBars} />
|
<FontAwesomeIcon icon={faBars} />
|
||||||
@@ -33,3 +15,5 @@ export default function MenuButton({ onClick }) {
|
|||||||
MenuButton.propTypes = {
|
MenuButton.propTypes = {
|
||||||
onClick: PropTypes.func.isRequired,
|
onClick: PropTypes.func.isRequired,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default MenuButton;
|
||||||
@@ -1,29 +1,10 @@
|
|||||||
import { MapContainer, TileLayer, Circle, Popup } from 'react-leaflet';
|
import { MapContainer, TileLayer, Circle, Popup } from 'react-leaflet';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
|
|
||||||
import { useConfig } from '../contexts/ConfigContext.jsx';
|
import { useConfig } from '@/hooks/useConfig.js';
|
||||||
|
|
||||||
import { DataProvider } from '../contexts/DataContext.jsx';
|
import { DataProvider } from '@/context/DataContext.jsx';
|
||||||
import { useData } from '../contexts/DataContext.jsx';
|
import { useDataContext } from '@/hooks/useDataContext';
|
||||||
|
|
||||||
/**
|
|
||||||
* PollutionMap.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente PollutionMap, que muestra un mapa con los niveles de contaminación en diferentes ubicaciones.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - MapContainer, TileLayer, Circle, Popup: Componentes de react-leaflet para renderizar el mapa y los círculos de contaminación.
|
|
||||||
* - useConfig: Hook personalizado para acceder al contexto de configuración.
|
|
||||||
* - DataProvider, useData: Funciones del contexto de datos para obtener y manejar datos.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - PollutionMap: Componente que configura la solicitud de datos y utiliza el DataProvider para obtener datos de sensores.
|
|
||||||
* - Muestra mensajes de carga y error según el estado de la configuración.
|
|
||||||
* - PollutionMapContent: Componente que procesa los datos obtenidos y renderiza los círculos de contaminación en el mapa.
|
|
||||||
* - Utiliza el hook `useData` para acceder a los datos de sensores.
|
|
||||||
* - Renderiza círculos de diferentes colores y tamaños según el nivel de contaminación.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const PollutionCircles = ({ data }) => {
|
const PollutionCircles = ({ data }) => {
|
||||||
return data.map(({ lat, lng, level }, index) => {
|
return data.map(({ lat, lng, level }, index) => {
|
||||||
@@ -83,7 +64,7 @@ const PollutionMap = ({ deviceId }) => {
|
|||||||
|
|
||||||
const PollutionMapContent = () => {
|
const PollutionMapContent = () => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
const { config, configLoading, configError } = useConfig();
|
||||||
const { data, dataLoading, dataError } = useData();
|
const { data, dataLoading, dataError } = useDataContext();
|
||||||
|
|
||||||
if (configLoading) return <p>Cargando configuración...</p>;
|
if (configLoading) return <p>Cargando configuración...</p>;
|
||||||
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
if (configError) return <p>Error al cargar configuración: {configError}</p>;
|
||||||
|
|||||||
@@ -1,36 +1,16 @@
|
|||||||
import "../css/SideMenu.css";
|
import "@/css/SideMenu.css";
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faTimes, faHome } from '@fortawesome/free-solid-svg-icons';
|
import { faTimes, faHome } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { DataProvider } from '../contexts/DataContext';
|
import { DataProvider } from '@/context/DataContext';
|
||||||
import { useData } from '../contexts/DataContext';
|
import { useDataContext } from "@/hooks/useDataContext";
|
||||||
|
|
||||||
import { useConfig } from '../contexts/ConfigContext';
|
import { useConfig } from '@/hooks/useConfig.js';
|
||||||
import { useTheme } from "../contexts/ThemeContext";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
|
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
|
|
||||||
/** ⚠️ EN PRUEBAS ⚠️
|
|
||||||
* SideMenu.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente SideMenu, que muestra un menú lateral con enlaces de navegación.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - "../css/SideMenu.css": Archivo CSS que contiene los estilos para el menú lateral.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
* - FontAwesomeIcon, faTimes: Componentes e iconos de FontAwesome para mostrar el icono de cerrar.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - SideMenu: Componente que renderiza un menú lateral con enlaces de navegación.
|
|
||||||
* - Utiliza la propiedad `isOpen` para determinar si el menú debe estar visible.
|
|
||||||
* - Utiliza la propiedad `onClose` para manejar el evento de cierre del menú.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - SideMenu espera una propiedad `isOpen` que es un booleano requerido.
|
|
||||||
* - SideMenu espera una propiedad `onClose` que es una función requerida.
|
|
||||||
* ⚠️ EN PRUEBAS ⚠️ **/
|
|
||||||
|
|
||||||
const SideMenu = ({ isOpen, onClose }) => {
|
const SideMenu = ({ isOpen, onClose }) => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
const { config, configLoading, configError } = useConfig();
|
||||||
|
|
||||||
@@ -54,7 +34,7 @@ const SideMenu = ({ isOpen, onClose }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const SideMenuContent = ({ isOpen, onClose }) => {
|
const SideMenuContent = ({ isOpen, onClose }) => {
|
||||||
const { data, dataLoading, dataError } = useData();
|
const { data, dataLoading, dataError } = useDataContext();
|
||||||
const { theme } = useTheme();
|
const { theme } = useTheme();
|
||||||
|
|
||||||
if (dataLoading) return <p>Cargando datos...</p>;
|
if (dataLoading) return <p>Cargando datos...</p>;
|
||||||
|
|||||||
@@ -4,34 +4,11 @@ import CardContainer from './CardContainer';
|
|||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import { faCloud, faClock, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
|
import { faCloud, faClock, faTemperature0, faWater } from '@fortawesome/free-solid-svg-icons';
|
||||||
|
|
||||||
import { DataProvider } from '../contexts/DataContext';
|
import { DataProvider } from '@/context/DataContext';
|
||||||
import { useData } from '../contexts/DataContext';
|
import { useDataContext } from '@/hooks/useDataContext';
|
||||||
|
|
||||||
import { useConfig } from '../contexts/ConfigContext';
|
import { useConfig } from '@/hooks/useConfig.js';
|
||||||
import { timestampToTime, formatTime } from '../util/date.js';
|
import { DateParser } from '@/util/dateParser';
|
||||||
|
|
||||||
/**
|
|
||||||
* SummaryCards.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente SummaryCards, que muestra tarjetas resumen con información relevante obtenida de sensores.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
* - CardContainer: Componente que actúa como contenedor para las tarjetas.
|
|
||||||
* - DataProvider, useData: Funciones del contexto de datos para obtener y manejar datos.
|
|
||||||
* - useConfig: Hook personalizado para acceder al contexto de configuración.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - SummaryCards: Componente que configura la solicitud de datos y utiliza el DataProvider para obtener datos de sensores.
|
|
||||||
* - Muestra mensajes de carga y error según el estado de la configuración.
|
|
||||||
* - SummaryCardsContent: Componente que procesa los datos obtenidos y actualiza el contenido de las tarjetas.
|
|
||||||
* - Utiliza el hook `useData` para acceder a los datos de sensores.
|
|
||||||
* - Actualiza el contenido y estado de las tarjetas según los datos obtenidos.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - SummaryCards espera una propiedad `data` que es un array.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const SummaryCards = ({ deviceId }) => {
|
const SummaryCards = ({ deviceId }) => {
|
||||||
const { config, configLoading, configError } = useConfig();
|
const { config, configLoading, configError } = useConfig();
|
||||||
@@ -51,13 +28,13 @@ const SummaryCards = ({ deviceId }) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DataProvider config={reqConfig}>
|
<DataProvider config={reqConfig}>
|
||||||
<SummaryCardsContent deviceId={deviceId} />
|
<SummaryCardsContent />
|
||||||
</DataProvider>
|
</DataProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const SummaryCardsContent = () => {
|
const SummaryCardsContent = () => {
|
||||||
const { data, dataLoading, dataError } = useData();
|
const { data, dataLoading, dataError } = useDataContext();
|
||||||
|
|
||||||
if (dataLoading) return <p>Cargando datos...</p>;
|
if (dataLoading) return <p>Cargando datos...</p>;
|
||||||
if (dataError) return <p>Error al cargar datos: {dataError}</p>;
|
if (dataError) return <p>Error al cargar datos: {dataError}</p>;
|
||||||
@@ -74,7 +51,7 @@ const SummaryCardsContent = () => {
|
|||||||
let coData = data[1];
|
let coData = data[1];
|
||||||
let tempData = data[2];
|
let tempData = data[2];
|
||||||
|
|
||||||
let lastTime = timestampToTime(coData.airValuesTimestamp);
|
let lastTime = DateParser.timestampToString(coData.airValuesTimestamp);
|
||||||
let lastDate = new Date(coData.airValuesTimestamp);
|
let lastDate = new Date(coData.airValuesTimestamp);
|
||||||
|
|
||||||
CardsData[0].content = tempData.temperature + "°C";
|
CardsData[0].content = tempData.temperature + "°C";
|
||||||
@@ -83,7 +60,7 @@ const SummaryCardsContent = () => {
|
|||||||
CardsData[1].status = "Humedad actual";
|
CardsData[1].status = "Humedad actual";
|
||||||
CardsData[2].content = coData.carbonMonoxide + " ppm";
|
CardsData[2].content = coData.carbonMonoxide + " ppm";
|
||||||
CardsData[2].status = "Nivel de CO actual";
|
CardsData[2].status = "Nivel de CO actual";
|
||||||
CardsData[3].content = formatTime(lastTime);
|
CardsData[3].content = lastTime;
|
||||||
CardsData[3].status = "Día " + lastDate.toLocaleDateString();
|
CardsData[3].status = "Día " + lastDate.toLocaleDateString();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,7 @@
|
|||||||
import { useTheme } from "../contexts/ThemeContext.jsx";
|
import { useTheme } from "@/hooks/useTheme";
|
||||||
import "../css/ThemeButton.css";
|
import "@/css/ThemeButton.css";
|
||||||
|
|
||||||
/**
|
const ThemeButton = () => {
|
||||||
* ThemeButton.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el componente ThemeButton, que permite a los usuarios cambiar entre temas claro y oscuro.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - useTheme: Hook personalizado para acceder al contexto del tema.
|
|
||||||
* - "../css/ThemeButton.css": Archivo CSS que contiene los estilos para el botón de cambio de tema.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - ThemeButton: Componente que renderiza un botón para alternar entre temas claro y oscuro.
|
|
||||||
* - Utiliza el hook `useTheme` para acceder al tema actual y la función para cambiarlo.
|
|
||||||
* - El botón muestra un icono de sol (☀️) si el tema actual es oscuro, y un icono de luna (🌙) si el tema actual es claro.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
export default function ThemeButton() {
|
|
||||||
const { theme, toggleTheme } = useTheme();
|
const { theme, toggleTheme } = useTheme();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -26,3 +10,5 @@ export default function ThemeButton() {
|
|||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default ThemeButton;
|
||||||
41
frontend/src/context/ConfigContext.jsx
Normal file
41
frontend/src/context/ConfigContext.jsx
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
import { createContext, useState, useEffect } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
|
||||||
|
const ConfigContext = createContext();
|
||||||
|
|
||||||
|
export const ConfigProvider = ({ children }) => {
|
||||||
|
const [config, setConfig] = useState(null);
|
||||||
|
const [configLoading, setLoading] = useState(true);
|
||||||
|
const [configError, setError] = useState(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
try {
|
||||||
|
const response = import.meta.env.MODE === 'production'
|
||||||
|
? await fetch("/config/settings.prod.json")
|
||||||
|
: await fetch("/config/settings.dev.json");
|
||||||
|
if (!response.ok) throw new Error("Error al cargar settings.*.json");
|
||||||
|
const json = await response.json();
|
||||||
|
setConfig(json);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ConfigContext.Provider value={{ config, configLoading, configError }}>
|
||||||
|
{children}
|
||||||
|
</ConfigContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
ConfigProvider.propTypes = {
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
export {ConfigContext};
|
||||||
23
frontend/src/context/DataContext.jsx
Normal file
23
frontend/src/context/DataContext.jsx
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import { createContext } from "react";
|
||||||
|
import PropTypes from "prop-types";
|
||||||
|
import { useData } from "@/hooks/useData";
|
||||||
|
|
||||||
|
export const DataContext = createContext();
|
||||||
|
|
||||||
|
export const DataProvider = ({ config, children }) => {
|
||||||
|
const data = useData(config);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DataContext.Provider value={data}>
|
||||||
|
{children}
|
||||||
|
</DataContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
DataProvider.propTypes = {
|
||||||
|
config: PropTypes.shape({
|
||||||
|
baseUrl: PropTypes.string.isRequired,
|
||||||
|
params: PropTypes.object,
|
||||||
|
}).isRequired,
|
||||||
|
children: PropTypes.node.isRequired,
|
||||||
|
};
|
||||||
31
frontend/src/context/ThemeContext.jsx
Normal file
31
frontend/src/context/ThemeContext.jsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { createContext, useEffect, useState } from "react";
|
||||||
|
|
||||||
|
export const ThemeContext = createContext();
|
||||||
|
|
||||||
|
export const ThemeProvider = ({ children }) => {
|
||||||
|
const [theme, setTheme] = useState(() => {
|
||||||
|
return (
|
||||||
|
localStorage.getItem("theme") ||
|
||||||
|
(window.matchMedia("(prefers-color-scheme: dark)").matches ? "dark" : "light")
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = document.documentElement;
|
||||||
|
document.body.classList.remove("light", "dark");
|
||||||
|
document.body.classList.add(theme);
|
||||||
|
root.classList.remove("light", "dark");
|
||||||
|
root.classList.add(theme);
|
||||||
|
localStorage.setItem("theme", theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const toggleTheme = () => {
|
||||||
|
setTheme((prev) => (prev === "light" ? "dark" : "light"));
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
||||||
|
{children}
|
||||||
|
</ThemeContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConfigContext.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el contexto de configuración para la aplicación, permitiendo cargar y manejar la configuración desde un archivo externo.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - createContext, useContext, useState, useEffect: Funciones de React para crear y utilizar contextos, manejar estados y efectos secundarios.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - ConfigContext: Contexto que almacena la configuración cargada, el estado de carga y cualquier error ocurrido durante la carga de la configuración.
|
|
||||||
* - ConfigProvider: Proveedor de contexto que maneja la carga de la configuración y proporciona el estado de la configuración a los componentes hijos.
|
|
||||||
* - Utiliza `fetch` para cargar la configuración desde un archivo JSON.
|
|
||||||
* - Maneja el estado de carga y errores durante la carga de la configuración.
|
|
||||||
* - useConfig: Hook personalizado para acceder al contexto de configuración.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - ConfigProvider espera un único hijo (`children`) que es requerido y debe ser un nodo de React.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ConfigContext = createContext();
|
|
||||||
|
|
||||||
export const ConfigProvider = ({ children }) => {
|
|
||||||
const [config, setConfig] = useState(null);
|
|
||||||
const [configLoading, setLoading] = useState(true);
|
|
||||||
const [configError, setError] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchConfig = async () => {
|
|
||||||
try {
|
|
||||||
const response = await fetch("/config/settings.json");
|
|
||||||
if (!response.ok) throw new Error("Error al cargar settings.json");
|
|
||||||
const json = await response.json();
|
|
||||||
setConfig(json);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchConfig();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ConfigContext.Provider value={{ config, configLoading, configError }}>
|
|
||||||
{children}
|
|
||||||
</ConfigContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ConfigProvider.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useConfig = () => useContext(ConfigContext);
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
import { createContext, useContext, useState, useEffect } from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* DataContext.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el contexto de datos para la aplicación, permitiendo obtener y manejar datos de una fuente externa.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - createContext, useContext, useState, useEffect: Funciones de React para crear y utilizar contextos, manejar estados y efectos secundarios.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - DataContext: Contexto que almacena los datos obtenidos, el estado de carga y cualquier error ocurrido durante la obtención de datos.
|
|
||||||
* - DataProvider: Proveedor de contexto que maneja la obtención de datos y proporciona el estado de los datos a los componentes hijos.
|
|
||||||
* - Utiliza `fetch` para obtener datos de una URL construida a partir de la configuración proporcionada.
|
|
||||||
* - Maneja el estado de carga y errores durante la obtención de datos.
|
|
||||||
* - useData: Hook personalizado para acceder al contexto de datos.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - DataProvider espera un único hijo (`children`) que es requerido y debe ser un nodo de React.
|
|
||||||
* - DataProvider también espera una configuración (`config`) que debe incluir `baseUrl` (string) y opcionalmente `params` (objeto).
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const DataContext = createContext();
|
|
||||||
|
|
||||||
export const DataProvider = ({ children, config }) => {
|
|
||||||
const [data, setData] = useState(null);
|
|
||||||
const [dataLoading, setLoading] = useState(true);
|
|
||||||
const [dataError, setError] = useState(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchData = async () => {
|
|
||||||
try {
|
|
||||||
const queryParams = new URLSearchParams(config.params).toString();
|
|
||||||
const url = `${config.baseUrl}?${queryParams}`;
|
|
||||||
const response = await fetch(url);
|
|
||||||
if (!response.ok) throw new Error("Error al obtener datos");
|
|
||||||
const result = await response.json();
|
|
||||||
setData(result);
|
|
||||||
} catch (err) {
|
|
||||||
setError(err.message);
|
|
||||||
} finally {
|
|
||||||
setLoading(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
fetchData();
|
|
||||||
}, [config]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DataContext.Provider value={{ data, dataLoading, dataError }}>
|
|
||||||
{children}
|
|
||||||
</DataContext.Provider>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
DataProvider.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
config: PropTypes.shape({
|
|
||||||
baseUrl: PropTypes.string.isRequired,
|
|
||||||
params: PropTypes.object,
|
|
||||||
}).isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export const useData = () => useContext(DataContext);
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from "react";
|
|
||||||
import PropTypes from "prop-types";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ThemeContext.jsx
|
|
||||||
*
|
|
||||||
* Este archivo define el contexto de tema para la aplicación, permitiendo cambiar entre temas claro y oscuro.
|
|
||||||
*
|
|
||||||
* Importaciones:
|
|
||||||
* - createContext, useContext, useEffect, useState: Funciones de React para crear y utilizar contextos, manejar efectos secundarios y estados.
|
|
||||||
* - PropTypes: Librería para la validación de tipos de propiedades en componentes de React.
|
|
||||||
*
|
|
||||||
* Funcionalidad:
|
|
||||||
* - ThemeContext: Contexto que almacena el tema actual y la función para cambiarlo.
|
|
||||||
* - ThemeProvider: Proveedor de contexto que maneja el estado del tema y proporciona la función para alternar entre temas.
|
|
||||||
* - Utiliza `localStorage` para persistir el tema seleccionado.
|
|
||||||
* - Aplica la clase correspondiente al `body` del documento para reflejar el tema actual.
|
|
||||||
* - useTheme: Hook personalizado para acceder al contexto del tema.
|
|
||||||
*
|
|
||||||
* PropTypes:
|
|
||||||
* - ThemeProvider espera un único hijo (`children`) que es requerido y debe ser un nodo de React.
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
const ThemeContext = createContext();
|
|
||||||
|
|
||||||
export function ThemeProvider({ children }) {
|
|
||||||
const [theme, setTheme] = useState(() => {
|
|
||||||
return localStorage.getItem("theme") || "light";
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
document.body.classList.remove("light", "dark");
|
|
||||||
document.body.classList.add(theme);
|
|
||||||
localStorage.setItem("theme", theme);
|
|
||||||
}, [theme]);
|
|
||||||
|
|
||||||
const toggleTheme = () => {
|
|
||||||
setTheme(prevTheme => (prevTheme === "light" ? "dark" : "light"));
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeContext.Provider value={{ theme, toggleTheme }}>
|
|
||||||
{children}
|
|
||||||
</ThemeContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ThemeProvider.propTypes = {
|
|
||||||
children: PropTypes.node.isRequired,
|
|
||||||
};
|
|
||||||
|
|
||||||
export function useTheme() {
|
|
||||||
return useContext(ThemeContext);
|
|
||||||
}
|
|
||||||
4
frontend/src/hooks/useConfig.js
Normal file
4
frontend/src/hooks/useConfig.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { ConfigContext } from "@/context/ConfigContext.jsx";
|
||||||
|
|
||||||
|
export const useConfig = () => useContext(ConfigContext);
|
||||||
130
frontend/src/hooks/useData.js
Normal file
130
frontend/src/hooks/useData.js
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export const useData = (config) => {
|
||||||
|
const [data, setData] = useState(null);
|
||||||
|
const [dataLoading, setLoading] = useState(true);
|
||||||
|
const [dataError, setError] = useState(null);
|
||||||
|
const configRef = useRef();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (config?.baseUrl) {
|
||||||
|
configRef.current = config;
|
||||||
|
}
|
||||||
|
}, [config]);
|
||||||
|
|
||||||
|
const getAuthHeaders = () => ({
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fetchData = useCallback(async () => {
|
||||||
|
const current = configRef.current;
|
||||||
|
if (!current?.baseUrl) return;
|
||||||
|
|
||||||
|
setLoading(true);
|
||||||
|
setError(null);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.get(current.baseUrl, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
params: current.params,
|
||||||
|
});
|
||||||
|
setData(response.data);
|
||||||
|
} catch (err) {
|
||||||
|
setError(err.response?.data?.message || err.message);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (config?.baseUrl) {
|
||||||
|
fetchData();
|
||||||
|
}
|
||||||
|
}, [config, fetchData]);
|
||||||
|
|
||||||
|
const getData = async (url, params = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
params,
|
||||||
|
});
|
||||||
|
return { data: response.data, error: null };
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
data: null,
|
||||||
|
error: err.response?.data?.message || err.message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const postData = async (endpoint, payload) => {
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
|
||||||
|
};
|
||||||
|
const response = await axios.post(endpoint, payload, { headers });
|
||||||
|
await fetchData();
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const postDataValidated = async (endpoint, payload) => {
|
||||||
|
try {
|
||||||
|
const headers = {
|
||||||
|
Authorization: `Bearer ${localStorage.getItem("token")}`,
|
||||||
|
...(payload instanceof FormData ? {} : { "Content-Type": "application/json" }),
|
||||||
|
};
|
||||||
|
const response = await axios.post(endpoint, payload, { headers });
|
||||||
|
return { data: response.data, errors: null };
|
||||||
|
} catch (err) {
|
||||||
|
const raw = err.response?.data?.message;
|
||||||
|
let parsed = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
parsed = JSON.parse(raw);
|
||||||
|
} catch {
|
||||||
|
return { data: null, errors: { general: raw || err.message } };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { data: null, errors: parsed };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const putData = async (endpoint, payload) => {
|
||||||
|
const response = await axios.put(endpoint, payload, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
});
|
||||||
|
await fetchData();
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteData = async (endpoint) => {
|
||||||
|
const response = await axios.delete(endpoint, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
});
|
||||||
|
await fetchData();
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteDataWithBody = async (endpoint, payload) => {
|
||||||
|
const response = await axios.delete(endpoint, {
|
||||||
|
headers: getAuthHeaders(),
|
||||||
|
data: payload,
|
||||||
|
});
|
||||||
|
await fetchData();
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
data,
|
||||||
|
dataLoading,
|
||||||
|
dataError,
|
||||||
|
getData,
|
||||||
|
postData,
|
||||||
|
postDataValidated,
|
||||||
|
putData,
|
||||||
|
deleteData,
|
||||||
|
deleteDataWithBody,
|
||||||
|
};
|
||||||
|
};
|
||||||
4
frontend/src/hooks/useDataContext.js
Normal file
4
frontend/src/hooks/useDataContext.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { DataContext } from "@/context/DataContext";
|
||||||
|
|
||||||
|
export const useDataContext = () => useContext(DataContext);
|
||||||
10
frontend/src/hooks/useTheme.js
Normal file
10
frontend/src/hooks/useTheme.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
import { ThemeContext } from "@/context/ThemeContext";
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const context = useContext(ThemeContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useTheme debe usarse dentro de un <ThemeProvider>");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
};
|
||||||
@@ -4,8 +4,8 @@ import { BrowserRouter } from 'react-router-dom'
|
|||||||
import './css/index.css'
|
import './css/index.css'
|
||||||
import App from './components/App.jsx'
|
import App from './components/App.jsx'
|
||||||
|
|
||||||
import { ThemeProvider } from './contexts/ThemeContext.jsx'
|
import { ThemeProvider } from './context/ThemeContext.jsx'
|
||||||
import { ConfigProvider } from './contexts/ConfigContext.jsx'
|
import { ConfigProvider } from './context/ConfigContext.jsx'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* main.jsx
|
* main.jsx
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import PollutionMap from '../components/PollutionMap.jsx'
|
import PollutionMap from '@/components/PollutionMap.jsx'
|
||||||
import HistoryCharts from '../components/HistoryCharts.jsx'
|
import HistoryCharts from '@/components/HistoryCharts.jsx'
|
||||||
import SummaryCards from '../components/SummaryCards.jsx'
|
import SummaryCards from '@/components/SummaryCards.jsx'
|
||||||
|
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import '../css/Home.css';
|
import '@/css/Home.css';
|
||||||
|
|
||||||
const Home = () => {
|
const Home = () => {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,19 +0,0 @@
|
|||||||
const timestampToTime = (timestamp) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
return date.toLocaleTimeString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const timestampToDate = (timestamp) => {
|
|
||||||
const date = new Date(timestamp);
|
|
||||||
return date.toLocaleDateString();
|
|
||||||
}
|
|
||||||
|
|
||||||
const formatTime = (date) => {
|
|
||||||
if (date.length === 8) {
|
|
||||||
return date.slice(0,5);
|
|
||||||
} else {
|
|
||||||
return `0${date.slice(0,3)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export { timestampToTime, timestampToDate, formatTime };
|
|
||||||
28
frontend/src/util/dateParser.js
Normal file
28
frontend/src/util/dateParser.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
export const DateParser = {
|
||||||
|
// ...deja sqlToString como está si la sigues usando...
|
||||||
|
timestampToString: (timestamp) => {
|
||||||
|
if (!timestamp) return '—';
|
||||||
|
|
||||||
|
// 1) Si viene un número, lo convertimos a ISO-string
|
||||||
|
let tsString = timestamp;
|
||||||
|
if (typeof timestamp === 'number') {
|
||||||
|
tsString = new Date(timestamp).toISOString();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2) Ahora hacemos el split sabiendo que tsString es algo tipo "YYYY-MM-DDTHH:mm:…"
|
||||||
|
const [datePart] = tsString.split('T');
|
||||||
|
const [year, month, day] = datePart.split('-');
|
||||||
|
return `${day}/${month}/${year}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
isoToStringWithTime: (isoString) => {
|
||||||
|
if (!isoString) return '—';
|
||||||
|
const date = new Date(isoString);
|
||||||
|
if (isNaN(date)) return '—';
|
||||||
|
return new Intl.DateTimeFormat('es-ES', {
|
||||||
|
day: '2-digit', month: '2-digit', year: 'numeric',
|
||||||
|
hour: '2-digit', minute: '2-digit', hour12: false,
|
||||||
|
timeZone: 'Europe/Madrid'
|
||||||
|
}).format(date);
|
||||||
|
}
|
||||||
|
};
|
||||||
10
frontend/src/util/errorParser.js
Normal file
10
frontend/src/util/errorParser.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
export const errorParser = (err) => {
|
||||||
|
const message = err.response?.data?.message;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(message);
|
||||||
|
return Object.values(parsed)[0];
|
||||||
|
// eslint-disable-next-line no-unused-vars
|
||||||
|
} catch (e) {
|
||||||
|
return message || err.message || "Unknown error";
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -5,7 +5,13 @@ import cleanPlugin from 'vite-plugin-clean'
|
|||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
server: {
|
server: {
|
||||||
port: 5173,
|
host: "localhost",
|
||||||
|
port: 3000,
|
||||||
|
},
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'@/': '/src/',
|
||||||
|
},
|
||||||
},
|
},
|
||||||
plugins: [react(), cleanPlugin()],
|
plugins: [react(), cleanPlugin()],
|
||||||
build: {
|
build: {
|
||||||
|
|||||||
Reference in New Issue
Block a user