Display live and cached consumption on dashboard
This commit is contained in:
		
							
								
								
									
										350
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										350
									
								
								central_frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -15,6 +15,7 @@
 | 
			
		||||
        "@mdi/react": "^1.6.1",
 | 
			
		||||
        "@mui/icons-material": "^6.0.1",
 | 
			
		||||
        "@mui/material": "^6.0.1",
 | 
			
		||||
        "@mui/x-charts": "^7.15.0",
 | 
			
		||||
        "@mui/x-date-pickers": "^7.15.0",
 | 
			
		||||
        "date-and-time": "^3.5.0",
 | 
			
		||||
        "dayjs": "^1.11.13",
 | 
			
		||||
@@ -1377,6 +1378,93 @@
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mui/x-charts": {
 | 
			
		||||
      "version": "7.15.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-7.15.0.tgz",
 | 
			
		||||
      "integrity": "sha512-eAyrLWMrFt6NKKca1vCWvVoThapGSdTzvDSY76CiA5Q1eOzZIhIriafh26QFqQLSuLYnCwHdX+gBVmCoZorcWA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.25.4",
 | 
			
		||||
        "@mui/utils": "^5.16.6",
 | 
			
		||||
        "@mui/x-charts-vendor": "7.15.0",
 | 
			
		||||
        "@react-spring/rafz": "^9.7.4",
 | 
			
		||||
        "@react-spring/web": "^9.7.4",
 | 
			
		||||
        "clsx": "^2.1.1",
 | 
			
		||||
        "prop-types": "^15.8.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=14.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@emotion/react": "^11.9.0",
 | 
			
		||||
        "@emotion/styled": "^11.8.1",
 | 
			
		||||
        "@mui/material": "^5.15.14 || ^6.0.0",
 | 
			
		||||
        "@mui/system": "^5.15.14 || ^6.0.0",
 | 
			
		||||
        "react": "^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react-dom": "^17.0.0 || ^18.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@emotion/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "@emotion/styled": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mui/x-charts-vendor": {
 | 
			
		||||
      "version": "7.15.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-7.15.0.tgz",
 | 
			
		||||
      "integrity": "sha512-5PNzV3//gdS3jlsbvAc/kj6k7KTikjNhprXVaTIrqj9/Jm2MU0cNtmwDwDVkekXLpjeXGGT6nrIehb129GIinw==",
 | 
			
		||||
      "license": "MIT AND ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.25.4",
 | 
			
		||||
        "@types/d3-color": "^3.1.3",
 | 
			
		||||
        "@types/d3-delaunay": "^6.0.4",
 | 
			
		||||
        "@types/d3-interpolate": "^3.0.4",
 | 
			
		||||
        "@types/d3-scale": "^4.0.8",
 | 
			
		||||
        "@types/d3-shape": "^3.1.6",
 | 
			
		||||
        "@types/d3-time": "^3.0.3",
 | 
			
		||||
        "d3-color": "^3.1.0",
 | 
			
		||||
        "d3-delaunay": "^6.0.4",
 | 
			
		||||
        "d3-interpolate": "^3.0.1",
 | 
			
		||||
        "d3-scale": "^4.0.2",
 | 
			
		||||
        "d3-shape": "^3.2.0",
 | 
			
		||||
        "d3-time": "^3.1.0",
 | 
			
		||||
        "delaunator": "^5.0.1",
 | 
			
		||||
        "robust-predicates": "^3.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mui/x-charts/node_modules/@mui/utils": {
 | 
			
		||||
      "version": "5.16.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.16.6.tgz",
 | 
			
		||||
      "integrity": "sha512-tWiQqlhxAt3KENNiSRL+DIn9H5xNVK6Jjf70x3PnfQPz1MPBdh7yyIcAyVBT9xiw7hP3SomRhPR7hzBMBCjqEA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.23.9",
 | 
			
		||||
        "@mui/types": "^7.2.15",
 | 
			
		||||
        "@types/prop-types": "^15.7.12",
 | 
			
		||||
        "clsx": "^2.1.1",
 | 
			
		||||
        "prop-types": "^15.8.1",
 | 
			
		||||
        "react-is": "^18.3.1"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/mui-org"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react": "^17.0.0 || ^18.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@mui/x-date-pickers": {
 | 
			
		||||
      "version": "7.15.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.15.0.tgz",
 | 
			
		||||
@@ -1516,6 +1604,78 @@
 | 
			
		||||
        "url": "https://opencollective.com/popperjs"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/animated": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/animated/-/animated-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-7As+8Pty2QlemJ9O5ecsuPKjmO0NKvmVkRR1n6mEotFgWar8FKuQt2xgxz3RTgxcccghpx1YdS1FCdElQNexmQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@react-spring/shared": "~9.7.4",
 | 
			
		||||
        "@react-spring/types": "~9.7.4"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/core": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/core/-/core-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-GzjA44niEJBFUe9jN3zubRDDDP2E4tBlhNlSIkTChiNf9p4ZQlgXBg50qbXfSXHQPHak/ExYxwhipKVsQ/sUTw==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@react-spring/animated": "~9.7.4",
 | 
			
		||||
        "@react-spring/shared": "~9.7.4",
 | 
			
		||||
        "@react-spring/types": "~9.7.4"
 | 
			
		||||
      },
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/react-spring/donate"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/rafz": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/rafz/-/rafz-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-mqDI6rW0Ca8IdryOMiXRhMtVGiEGLIO89vIOyFQXRIwwIMX30HLya24g9z4olDvFyeDW3+kibiKwtZnA4xhldA==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/shared": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/shared/-/shared-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-bEPI7cQp94dOtCFSEYpxvLxj0+xQfB5r9Ru1h8OMycsIq7zFZon1G0sHrBLaLQIWeMCllc4tVDYRTLIRv70C8w==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@react-spring/rafz": "~9.7.4",
 | 
			
		||||
        "@react-spring/types": "~9.7.4"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/types": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/types/-/types-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-iQVztO09ZVfsletMiY+DpT/JRiBntdsdJ4uqk3UJFhrhS8mIC9ZOZbmfGSRs/kdbNPQkVyzucceDicQ/3Mlj9g==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@react-spring/web": {
 | 
			
		||||
      "version": "9.7.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@react-spring/web/-/web-9.7.4.tgz",
 | 
			
		||||
      "integrity": "sha512-UMvCZp7I5HCVIleSa4BwbNxynqvj+mJjG2m20VO2yPoi2pnCYANy58flvz9v/YcXTAvsmL655FV3pm5fbr6akA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@react-spring/animated": "~9.7.4",
 | 
			
		||||
        "@react-spring/core": "~9.7.4",
 | 
			
		||||
        "@react-spring/shared": "~9.7.4",
 | 
			
		||||
        "@react-spring/types": "~9.7.4"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0",
 | 
			
		||||
        "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@remix-run/router": {
 | 
			
		||||
      "version": "1.19.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
 | 
			
		||||
@@ -1790,6 +1950,57 @@
 | 
			
		||||
        "@babel/types": "^7.20.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-color": {
 | 
			
		||||
      "version": "3.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-delaunay": {
 | 
			
		||||
      "version": "6.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-ZMaSKu4THYCU6sV64Lhg6qjf1orxBthaC161plr5KuPHo3CNm8DTHiLw/5Eq2b6TsNP0W0iJrUOFscY6Q450Hw==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-interpolate": {
 | 
			
		||||
      "version": "3.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/d3-color": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-path": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-scale": {
 | 
			
		||||
      "version": "4.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz",
 | 
			
		||||
      "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/d3-time": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-shape": {
 | 
			
		||||
      "version": "3.1.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz",
 | 
			
		||||
      "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==",
 | 
			
		||||
      "license": "MIT",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/d3-path": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/d3-time": {
 | 
			
		||||
      "version": "3.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==",
 | 
			
		||||
      "license": "MIT"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/estree": {
 | 
			
		||||
      "version": "1.0.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
 | 
			
		||||
@@ -2304,6 +2515,121 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
 | 
			
		||||
      "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-array": {
 | 
			
		||||
      "version": "3.2.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "internmap": "1 - 2"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-color": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-delaunay": {
 | 
			
		||||
      "version": "6.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "delaunator": "5"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-format": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-YyUI6AEuY/Wpt8KWLgZHsIU86atmikuoOmCfommt0LYHiQSPjvX2AcFc38PX0CBpr2RCyZhjex+NS/LPOv6YqA==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-interpolate": {
 | 
			
		||||
      "version": "3.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3-color": "1 - 3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-path": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-scale": {
 | 
			
		||||
      "version": "4.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3-array": "2.10.0 - 3",
 | 
			
		||||
        "d3-format": "1 - 3",
 | 
			
		||||
        "d3-interpolate": "1.2.0 - 3",
 | 
			
		||||
        "d3-time": "2.1.1 - 3",
 | 
			
		||||
        "d3-time-format": "2 - 4"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-shape": {
 | 
			
		||||
      "version": "3.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3-path": "^3.1.0"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-time": {
 | 
			
		||||
      "version": "3.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3-array": "2 - 3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/d3-time-format": {
 | 
			
		||||
      "version": "4.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz",
 | 
			
		||||
      "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "d3-time": "1 - 3"
 | 
			
		||||
      },
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/date-and-time": {
 | 
			
		||||
      "version": "3.5.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.5.0.tgz",
 | 
			
		||||
@@ -2338,6 +2664,15 @@
 | 
			
		||||
      "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
 | 
			
		||||
      "dev": true
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/delaunator": {
 | 
			
		||||
      "version": "5.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/delaunator/-/delaunator-5.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-8nvh+XBe96aCESrGOqMp/84b13H9cdKbG5P2ejQCh4d4sK9RL4371qou9drQjMhvnPmhWl5hnmqbEE0fXr9Xnw==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "robust-predicates": "^3.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/doctrine": {
 | 
			
		||||
      "version": "3.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
 | 
			
		||||
@@ -3045,6 +3380,15 @@
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "license": "ISC"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/internmap": {
 | 
			
		||||
      "version": "2.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==",
 | 
			
		||||
      "license": "ISC",
 | 
			
		||||
      "engines": {
 | 
			
		||||
        "node": ">=12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/is-arrayish": {
 | 
			
		||||
      "version": "0.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
 | 
			
		||||
@@ -3703,6 +4047,12 @@
 | 
			
		||||
        "url": "https://github.com/sponsors/isaacs"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/robust-predicates": {
 | 
			
		||||
      "version": "3.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==",
 | 
			
		||||
      "license": "Unlicense"
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/rollup": {
 | 
			
		||||
      "version": "4.21.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",
 | 
			
		||||
 
 | 
			
		||||
@@ -17,6 +17,7 @@
 | 
			
		||||
    "@mdi/react": "^1.6.1",
 | 
			
		||||
    "@mui/icons-material": "^6.0.1",
 | 
			
		||||
    "@mui/material": "^6.0.1",
 | 
			
		||||
    "@mui/x-charts": "^7.15.0",
 | 
			
		||||
    "@mui/x-date-pickers": "^7.15.0",
 | 
			
		||||
    "date-and-time": "^3.5.0",
 | 
			
		||||
    "dayjs": "^1.11.13",
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										26
									
								
								central_frontend/src/api/EnergyApi.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								central_frontend/src/api/EnergyApi.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
			
		||||
import { Api } from "@mui/icons-material";
 | 
			
		||||
import { APIClient } from "./ApiClient";
 | 
			
		||||
 | 
			
		||||
export class EnergyApi {
 | 
			
		||||
  /**
 | 
			
		||||
   * Get current house consumption
 | 
			
		||||
   */
 | 
			
		||||
  static async CurrConsumption(): Promise<number> {
 | 
			
		||||
    const data = await APIClient.exec({
 | 
			
		||||
      method: "GET",
 | 
			
		||||
      uri: "/energy/curr_consumption",
 | 
			
		||||
    });
 | 
			
		||||
    return data.data.consumption;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  /**
 | 
			
		||||
   * Get current cached consumption
 | 
			
		||||
   */
 | 
			
		||||
  static async CachedConsumption(): Promise<number> {
 | 
			
		||||
    const data = await APIClient.exec({
 | 
			
		||||
      method: "GET",
 | 
			
		||||
      uri: "/energy/cached_consumption",
 | 
			
		||||
    });
 | 
			
		||||
    return data.data.consumption;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,27 @@
 | 
			
		||||
import { Typography } from "@mui/material";
 | 
			
		||||
import { CurrConsumptionWidget } from "./HomeRoute/CurrConsumptionWidget";
 | 
			
		||||
import Grid from "@mui/material/Grid2";
 | 
			
		||||
import { CachedConsumptionWidget } from "./HomeRoute/CachedConsumptionWidget";
 | 
			
		||||
 | 
			
		||||
export function HomeRoute(): React.ReactElement {
 | 
			
		||||
  return <>home authenticated todo</>;
 | 
			
		||||
  return (
 | 
			
		||||
    <div style={{ flex: 1, padding: "10px" }}>
 | 
			
		||||
      <Typography component="h2" variant="h6" sx={{ mb: 2 }}>
 | 
			
		||||
        Overview
 | 
			
		||||
      </Typography>
 | 
			
		||||
      <Grid
 | 
			
		||||
        container
 | 
			
		||||
        spacing={2}
 | 
			
		||||
        columns={12}
 | 
			
		||||
        sx={{ mb: (theme) => theme.spacing(2) }}
 | 
			
		||||
      >
 | 
			
		||||
        <Grid size={{ xs: 12, sm: 6, lg: 3 }}>
 | 
			
		||||
          <CurrConsumptionWidget />
 | 
			
		||||
        </Grid>
 | 
			
		||||
        <Grid size={{ xs: 12, sm: 6, lg: 3 }}>
 | 
			
		||||
          <CachedConsumptionWidget />
 | 
			
		||||
        </Grid>
 | 
			
		||||
      </Grid>
 | 
			
		||||
    </div>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { EnergyApi } from "../../api/EnergyApi";
 | 
			
		||||
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
			
		||||
import StatCard from "../../widgets/StatCard";
 | 
			
		||||
 | 
			
		||||
export function CachedConsumptionWidget(): React.ReactElement {
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [val, setVal] = React.useState<undefined | number>();
 | 
			
		||||
 | 
			
		||||
  const refresh = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const s = await EnergyApi.CachedConsumption();
 | 
			
		||||
      setVal(s);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      snackbar("Failed to refresh cached consumption!");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    refresh();
 | 
			
		||||
    const i = setInterval(() => refresh(), 3000);
 | 
			
		||||
 | 
			
		||||
    return () => clearInterval(i);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <StatCard
 | 
			
		||||
      title="Cached consumption"
 | 
			
		||||
      data={[]}
 | 
			
		||||
      interval="Current data"
 | 
			
		||||
      trend="neutral"
 | 
			
		||||
      value={val?.toString() ?? "Loading"}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
@@ -0,0 +1,37 @@
 | 
			
		||||
import React from "react";
 | 
			
		||||
import { EnergyApi } from "../../api/EnergyApi";
 | 
			
		||||
import { useSnackbar } from "../../hooks/context_providers/SnackbarProvider";
 | 
			
		||||
import StatCard from "../../widgets/StatCard";
 | 
			
		||||
 | 
			
		||||
export function CurrConsumptionWidget(): React.ReactElement {
 | 
			
		||||
  const snackbar = useSnackbar();
 | 
			
		||||
 | 
			
		||||
  const [val, setVal] = React.useState<undefined | number>();
 | 
			
		||||
 | 
			
		||||
  const refresh = async () => {
 | 
			
		||||
    try {
 | 
			
		||||
      const s = await EnergyApi.CurrConsumption();
 | 
			
		||||
      setVal(s);
 | 
			
		||||
    } catch (e) {
 | 
			
		||||
      console.error(e);
 | 
			
		||||
      snackbar("Failed to refresh current consumption!");
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  React.useEffect(() => {
 | 
			
		||||
    refresh();
 | 
			
		||||
    const i = setInterval(() => refresh(), 3000);
 | 
			
		||||
 | 
			
		||||
    return () => clearInterval(i);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <StatCard
 | 
			
		||||
      title="Current consumption"
 | 
			
		||||
      data={[]}
 | 
			
		||||
      interval="Current data"
 | 
			
		||||
      trend="neutral"
 | 
			
		||||
      value={val?.toString() ?? "Loading"}
 | 
			
		||||
    />
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										128
									
								
								central_frontend/src/widgets/StatCard.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										128
									
								
								central_frontend/src/widgets/StatCard.tsx
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,128 @@
 | 
			
		||||
import { useTheme } from "@mui/material/styles";
 | 
			
		||||
import Box from "@mui/material/Box";
 | 
			
		||||
import Card from "@mui/material/Card";
 | 
			
		||||
import CardContent from "@mui/material/CardContent";
 | 
			
		||||
import Chip from "@mui/material/Chip";
 | 
			
		||||
import Stack from "@mui/material/Stack";
 | 
			
		||||
import Typography from "@mui/material/Typography";
 | 
			
		||||
import { SparkLineChart } from "@mui/x-charts/SparkLineChart";
 | 
			
		||||
import { areaElementClasses } from "@mui/x-charts/LineChart";
 | 
			
		||||
 | 
			
		||||
export type StatCardProps = {
 | 
			
		||||
  title: string;
 | 
			
		||||
  value: string;
 | 
			
		||||
  interval: string;
 | 
			
		||||
  trend: "up" | "down" | "neutral";
 | 
			
		||||
  data: number[];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
function getDaysInMonth(month: number, year: number) {
 | 
			
		||||
  const date = new Date(year, month, 0);
 | 
			
		||||
  const monthName = date.toLocaleDateString("en-US", {
 | 
			
		||||
    month: "short",
 | 
			
		||||
  });
 | 
			
		||||
  const daysInMonth = date.getDate();
 | 
			
		||||
  const days = [];
 | 
			
		||||
  let i = 1;
 | 
			
		||||
  while (days.length < daysInMonth) {
 | 
			
		||||
    days.push(`${monthName} ${i}`);
 | 
			
		||||
    i += 1;
 | 
			
		||||
  }
 | 
			
		||||
  return days;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function AreaGradient({ color, id }: { color: string; id: string }) {
 | 
			
		||||
  return (
 | 
			
		||||
    <defs>
 | 
			
		||||
      <linearGradient id={id} x1="50%" y1="0%" x2="50%" y2="100%">
 | 
			
		||||
        <stop offset="0%" stopColor={color} stopOpacity={0.3} />
 | 
			
		||||
        <stop offset="100%" stopColor={color} stopOpacity={0} />
 | 
			
		||||
      </linearGradient>
 | 
			
		||||
    </defs>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default function StatCard({
 | 
			
		||||
  title,
 | 
			
		||||
  value,
 | 
			
		||||
  interval,
 | 
			
		||||
  trend,
 | 
			
		||||
  data,
 | 
			
		||||
}: StatCardProps) {
 | 
			
		||||
  const theme = useTheme();
 | 
			
		||||
  const daysInWeek = getDaysInMonth(4, 2024);
 | 
			
		||||
 | 
			
		||||
  const trendColors = {
 | 
			
		||||
    up:
 | 
			
		||||
      theme.palette.mode === "light"
 | 
			
		||||
        ? theme.palette.success.main
 | 
			
		||||
        : theme.palette.success.dark,
 | 
			
		||||
    down:
 | 
			
		||||
      theme.palette.mode === "light"
 | 
			
		||||
        ? theme.palette.error.main
 | 
			
		||||
        : theme.palette.error.dark,
 | 
			
		||||
    neutral:
 | 
			
		||||
      theme.palette.mode === "light"
 | 
			
		||||
        ? theme.palette.grey[400]
 | 
			
		||||
        : theme.palette.grey[700],
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const labelColors = {
 | 
			
		||||
    up: "success" as const,
 | 
			
		||||
    down: "error" as const,
 | 
			
		||||
    neutral: "default" as const,
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  const color = labelColors[trend];
 | 
			
		||||
  const chartColor = trendColors[trend];
 | 
			
		||||
  const trendValues = { up: "+25%", down: "-25%", neutral: "+5%" };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
    <Card variant="outlined" sx={{ height: "100%", flexGrow: 1 }}>
 | 
			
		||||
      <CardContent>
 | 
			
		||||
        <Typography component="h2" variant="subtitle2" gutterBottom>
 | 
			
		||||
          {title}
 | 
			
		||||
        </Typography>
 | 
			
		||||
        <Stack
 | 
			
		||||
          direction="column"
 | 
			
		||||
          sx={{ justifyContent: "space-between", flexGrow: "1", gap: 1 }}
 | 
			
		||||
        >
 | 
			
		||||
          <Stack sx={{ justifyContent: "space-between" }}>
 | 
			
		||||
            <Stack
 | 
			
		||||
              direction="row"
 | 
			
		||||
              sx={{ justifyContent: "space-between", alignItems: "center" }}
 | 
			
		||||
            >
 | 
			
		||||
              <Typography variant="h4" component="p">
 | 
			
		||||
                {value}
 | 
			
		||||
              </Typography>
 | 
			
		||||
              <Chip size="small" color={color} label={trendValues[trend]} />
 | 
			
		||||
            </Stack>
 | 
			
		||||
            <Typography variant="caption" sx={{ color: "text.secondary" }}>
 | 
			
		||||
              {interval}
 | 
			
		||||
            </Typography>
 | 
			
		||||
          </Stack>
 | 
			
		||||
          <Box sx={{ width: "100%", height: 50 }}>
 | 
			
		||||
            <SparkLineChart
 | 
			
		||||
              colors={[chartColor]}
 | 
			
		||||
              data={data}
 | 
			
		||||
              area
 | 
			
		||||
              showHighlight
 | 
			
		||||
              showTooltip
 | 
			
		||||
              xAxis={{
 | 
			
		||||
                scaleType: "band",
 | 
			
		||||
                data: daysInWeek, // Use the correct property 'data' for xAxis
 | 
			
		||||
              }}
 | 
			
		||||
              sx={{
 | 
			
		||||
                [`& .${areaElementClasses.root}`]: {
 | 
			
		||||
                  fill: `url(#area-gradient-${value})`,
 | 
			
		||||
                },
 | 
			
		||||
              }}
 | 
			
		||||
            >
 | 
			
		||||
              <AreaGradient color={chartColor} id={`area-gradient-${value}`} />
 | 
			
		||||
            </SparkLineChart>
 | 
			
		||||
          </Box>
 | 
			
		||||
        </Stack>
 | 
			
		||||
      </CardContent>
 | 
			
		||||
    </Card>
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user