Display live and cached consumption on dashboard

This commit is contained in:
Pierre HUBERT 2024-09-02 22:17:34 +02:00
parent 539703b904
commit 1784a0a1f8
7 changed files with 604 additions and 1 deletions

View File

@ -15,6 +15,7 @@
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.0.1", "@mui/icons-material": "^6.0.1",
"@mui/material": "^6.0.1", "@mui/material": "^6.0.1",
"@mui/x-charts": "^7.15.0",
"@mui/x-date-pickers": "^7.15.0", "@mui/x-date-pickers": "^7.15.0",
"date-and-time": "^3.5.0", "date-and-time": "^3.5.0",
"dayjs": "^1.11.13", "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": { "node_modules/@mui/x-date-pickers": {
"version": "7.15.0", "version": "7.15.0",
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.15.0.tgz", "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" "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": { "node_modules/@remix-run/router": {
"version": "1.19.1", "version": "1.19.1",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz", "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.19.1.tgz",
@ -1790,6 +1950,57 @@
"@babel/types": "^7.20.7" "@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": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.5",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "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", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
"integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" "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": { "node_modules/date-and-time": {
"version": "3.5.0", "version": "3.5.0",
"resolved": "https://registry.npmjs.org/date-and-time/-/date-and-time-3.5.0.tgz", "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==", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true "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": { "node_modules/doctrine": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
@ -3045,6 +3380,15 @@
"dev": true, "dev": true,
"license": "ISC" "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": { "node_modules/is-arrayish": {
"version": "0.2.1", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
@ -3703,6 +4047,12 @@
"url": "https://github.com/sponsors/isaacs" "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": { "node_modules/rollup": {
"version": "4.21.2", "version": "4.21.2",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.21.2.tgz",

View File

@ -17,6 +17,7 @@
"@mdi/react": "^1.6.1", "@mdi/react": "^1.6.1",
"@mui/icons-material": "^6.0.1", "@mui/icons-material": "^6.0.1",
"@mui/material": "^6.0.1", "@mui/material": "^6.0.1",
"@mui/x-charts": "^7.15.0",
"@mui/x-date-pickers": "^7.15.0", "@mui/x-date-pickers": "^7.15.0",
"date-and-time": "^3.5.0", "date-and-time": "^3.5.0",
"dayjs": "^1.11.13", "dayjs": "^1.11.13",

View 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;
}
}

View File

@ -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 { 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>
);
} }

View File

@ -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"}
/>
);
}

View File

@ -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"}
/>
);
}

View 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>
);
}