UI: NodeList added Pie Chart and extracted Table Data
All checks were successful
build / test (pull_request) Successful in 39s

This commit is contained in:
Luis Hebendanz 2023-08-12 00:01:09 +02:00
parent 3d3dcc800b
commit de9cac534b
8 changed files with 1608 additions and 276 deletions

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,12 @@
"classnames": "^2.3.2",
"eslint": "8.46.0",
"eslint-config-next": "13.4.12",
"hex-rgb": "^5.0.0",
"next": "13.4.12",
"postcss": "8.4.27",
"react": "18.2.0",
"react-dom": "18.2.0",
"recharts": "^2.7.3",
"tailwindcss": "3.3.3",
"typescript": "5.1.6"
},
@ -929,6 +931,60 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/d3-array": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.5.tgz",
"integrity": "sha512-Qk7fpJ6qFp+26VeQ47WY0mkwXaiq8+76RJcncDEfMc2ocRzXLO67bLFRNI4OX1aGBoPzsM5Y2T+/m1pldOgD+A=="
},
"node_modules/@types/d3-color": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.0.tgz",
"integrity": "sha512-HKuicPHJuvPgCD+np6Se9MQvS6OCbJmOjGvylzMJRlDwUXjKTTXs6Pwgk79O09Vj/ho3u1ofXnhFOaEWWPrlwA=="
},
"node_modules/@types/d3-ease": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.0.tgz",
"integrity": "sha512-aMo4eaAOijJjA6uU+GIeW018dvy9+oH5Y2VPPzjjfxevvGQ/oRDs+tfYC9b50Q4BygRR8yE2QCLsrT0WtAVseA=="
},
"node_modules/@types/d3-interpolate": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.1.tgz",
"integrity": "sha512-jx5leotSeac3jr0RePOH1KdR9rISG91QIE4Q2PYTu4OymLTZfA3SrnURSLzKH48HmXVUru50b8nje4E79oQSQw==",
"dependencies": {
"@types/d3-color": "*"
}
},
"node_modules/@types/d3-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.0.0.tgz",
"integrity": "sha512-0g/A+mZXgFkQxN3HniRDbXMN79K3CdTpLsevj+PXiTcb2hVyvkZUBg37StmgCQkaD84cUJ4uaDAWq7UJOQy2Tg=="
},
"node_modules/@types/d3-scale": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.3.tgz",
"integrity": "sha512-PATBiMCpvHJSMtZAMEhc2WyL+hnzarKzI6wAHYjhsonjWJYGq5BXTzQjv4l8m2jO183/4wZ90rKvSeT7o72xNQ==",
"dependencies": {
"@types/d3-time": "*"
}
},
"node_modules/@types/d3-shape": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.1.tgz",
"integrity": "sha512-6Uh86YFF7LGg4PQkuO2oG6EMBRLuW9cbavUW46zkIO5kuS2PfTqo2o9SkgtQzguBHbLgNnU90UNsITpsX1My+A==",
"dependencies": {
"@types/d3-path": "*"
}
},
"node_modules/@types/d3-time": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.0.tgz",
"integrity": "sha512-sZLCdHvBUcNby1cB6Fd3ZBrABbjz3v1Vm90nysCQ6Vt7vd6e/h9Lt7SiJUoEX0l4Dzc7P5llKyhqSi1ycSf1Hg=="
},
"node_modules/@types/d3-timer": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.0.tgz",
"integrity": "sha512-HNB/9GHqu7Fo8AQiugyJbv6ZxYz58wef0esl4Mv828w1ZKpAshw/uFWVDUcIB9KKFeFKoxS3cHY07FFgtTRZ1g=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -1669,6 +1725,11 @@
"node": ">= 8"
}
},
"node_modules/css-unit-converter": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz",
"integrity": "sha512-IiJwMC8rdZE0+xiEZHeru6YoONC4rfPMqGm2W85jMIbkFvv5nFTwJVFHam2eFrN6txmoUYFAFXiv8ICVeTO0MA=="
},
"node_modules/cssesc": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
@ -1685,6 +1746,116 @@
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
"integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ=="
},
"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==",
"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==",
"engines": {
"node": ">=12"
}
},
"node_modules/d3-ease": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz",
"integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==",
"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==",
"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==",
"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==",
"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==",
"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==",
"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==",
"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==",
"dependencies": {
"d3-time": "1 - 3"
},
"engines": {
"node": ">=12"
}
},
"node_modules/d3-timer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz",
"integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==",
"engines": {
"node": ">=12"
}
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
@ -1706,6 +1877,11 @@
}
}
},
"node_modules/decimal.js-light": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz",
"integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg=="
},
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@ -2379,6 +2555,11 @@
"node": ">=0.10.0"
}
},
"node_modules/eventemitter3": {
"version": "4.0.7",
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
},
"node_modules/execa": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/execa/-/execa-7.2.0.tgz",
@ -2406,6 +2587,14 @@
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
"integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
},
"node_modules/fast-equals": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz",
"integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==",
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@ -2805,6 +2994,17 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/hex-rgb": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/hex-rgb/-/hex-rgb-5.0.0.tgz",
"integrity": "sha512-NQO+lgVUCtHxZ792FodgW0zflK+ozS9X9dwGp9XvvmPlH7pyxd588cn24TD3rmPm/N0AIRXF10Otah8yKqGw4w==",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/hoist-non-react-statics": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
@ -2879,6 +3079,14 @@
"node": ">= 0.4"
}
},
"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==",
"engines": {
"node": ">=12"
}
},
"node_modules/is-array-buffer": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz",
@ -3302,6 +3510,11 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.17.21",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@ -4123,6 +4336,60 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
"node_modules/react-lifecycles-compat": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz",
"integrity": "sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA=="
},
"node_modules/react-resize-detector": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-8.1.0.tgz",
"integrity": "sha512-S7szxlaIuiy5UqLhLL1KY3aoyGHbZzsTpYal9eYMwCyKqoqoVLCmIgAgNyIM1FhnP2KyBygASJxdhejrzjMb+w==",
"dependencies": {
"lodash": "^4.17.21"
},
"peerDependencies": {
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-smooth": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.3.tgz",
"integrity": "sha512-yl4y3XiMorss7ayF5QnBiSprig0+qFHui8uh7Hgg46QX5O+aRMRKlfGGNGLHno35JkQSvSYY8eCWkBfHfrSHfg==",
"dependencies": {
"fast-equals": "^5.0.0",
"react-transition-group": "2.9.0"
},
"peerDependencies": {
"prop-types": "^15.6.0",
"react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/react-smooth/node_modules/dom-helpers": {
"version": "3.4.0",
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-3.4.0.tgz",
"integrity": "sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==",
"dependencies": {
"@babel/runtime": "^7.1.2"
}
},
"node_modules/react-smooth/node_modules/react-transition-group": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-2.9.0.tgz",
"integrity": "sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==",
"dependencies": {
"dom-helpers": "^3.4.0",
"loose-envify": "^1.4.0",
"prop-types": "^15.6.2",
"react-lifecycles-compat": "^3.0.4"
},
"peerDependencies": {
"react": ">=15.0.0",
"react-dom": ">=15.0.0"
}
},
"node_modules/react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@ -4157,6 +4424,52 @@
"node": ">=8.10.0"
}
},
"node_modules/recharts": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.7.3.tgz",
"integrity": "sha512-cKoO9jUZRQavn06H6Ih2EcG82zUNdQH3OEGWVCmluSDyp3d7fIpDAsbMTd8hE8+T+MD8P76iicv/J4pJspDP7A==",
"dependencies": {
"classnames": "^2.2.5",
"eventemitter3": "^4.0.1",
"lodash": "^4.17.19",
"react-is": "^16.10.2",
"react-resize-detector": "^8.0.4",
"react-smooth": "^2.0.2",
"recharts-scale": "^0.4.4",
"reduce-css-calc": "^2.1.8",
"victory-vendor": "^36.6.8"
},
"engines": {
"node": ">=12"
},
"peerDependencies": {
"prop-types": "^15.6.0",
"react": "^16.0.0 || ^17.0.0 || ^18.0.0",
"react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/recharts-scale": {
"version": "0.4.5",
"resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz",
"integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==",
"dependencies": {
"decimal.js-light": "^2.4.1"
}
},
"node_modules/reduce-css-calc": {
"version": "2.1.8",
"resolved": "https://registry.npmjs.org/reduce-css-calc/-/reduce-css-calc-2.1.8.tgz",
"integrity": "sha512-8liAVezDmUcH+tdzoEGrhfbGcP7nOV4NkGE3a74+qqvE7nt9i4sKLGBuZNOnpI4WiGksiNPklZxva80061QiPg==",
"dependencies": {
"css-unit-converter": "^1.1.1",
"postcss-value-parser": "^3.3.0"
}
},
"node_modules/reduce-css-calc/node_modules/postcss-value-parser": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz",
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ=="
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
@ -4974,6 +5287,27 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/victory-vendor": {
"version": "36.6.11",
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.11.tgz",
"integrity": "sha512-nT8kCiJp8dQh8g991J/R5w5eE2KnO8EAIP0xocWlh9l2okngMWglOPoMZzJvek8Q1KUc4XE/mJxTZnvOB1sTYg==",
"dependencies": {
"@types/d3-array": "^3.0.3",
"@types/d3-ease": "^3.0.0",
"@types/d3-interpolate": "^3.0.1",
"@types/d3-scale": "^4.0.2",
"@types/d3-shape": "^3.1.0",
"@types/d3-time": "^3.0.0",
"@types/d3-timer": "^3.0.0",
"d3-array": "^3.1.6",
"d3-ease": "^3.0.1",
"d3-interpolate": "^3.0.1",
"d3-scale": "^4.0.2",
"d3-shape": "^3.1.0",
"d3-time": "^3.0.0",
"d3-timer": "^3.0.1"
}
},
"node_modules/watchpack": {
"version": "2.4.0",
"resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz",

View File

@ -23,10 +23,12 @@
"classnames": "^2.3.2",
"eslint": "8.46.0",
"eslint-config-next": "13.4.12",
"hex-rgb": "^5.0.0",
"next": "13.4.12",
"postcss": "8.4.27",
"react": "18.2.0",
"react-dom": "18.2.0",
"recharts": "^2.7.3",
"tailwindcss": "3.3.3",
"typescript": "5.1.6"
},

View File

@ -27,47 +27,53 @@ import Stack from '@mui/material/Stack/Stack';
import ModeIcon from '@mui/icons-material/Mode';
import ClearIcon from '@mui/icons-material/Clear';
import Fade from '@mui/material/Fade/Fade';
import NodePieChart, { PieData } from './NodePieChart';
import Grid2 from '@mui/material/Unstable_Grid2'; // Grid version 2
import { Card, CardContent, Container, FormGroup, useTheme } from '@mui/material';
import hexRgb from 'hex-rgb';
import useMediaQuery from '@mui/material/useMediaQuery';
interface Data {
export interface TableData {
name: string;
id: string;
status: boolean;
status: NodeStatus;
last_seen: number;
}
function createData(
name: string,
id: string,
status: boolean,
last_seen: number,
): Data {
if (status && last_seen > 0) {
console.error("Last seen should be 0 if status is true");
}
return {
name,
id,
status,
last_seen: last_seen,
};
export enum NodeStatus {
Online,
Offline,
Pending,
}
const rows = [
createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", true, 0),
createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", true, 0),
createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", false, 16.0),
createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", false, 6.0),
createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", false, 13),
createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", true, 0),
createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", true, 0),
createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", true, 0.0),
createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", false, 7.0),
createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", false, 2),
createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", false, 0),
createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", true, 0),
createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", true, 0),
interface HeadCell {
disablePadding: boolean;
id: keyof TableData;
label: string;
alignRight: boolean;
}
const headCells: readonly HeadCell[] = [
{
id: 'name',
alignRight: false,
disablePadding: false,
label: 'DISPLAY NAME & ID',
},
{
id: 'status',
alignRight: false,
disablePadding: false,
label: 'STATUS',
},
{
id: 'last_seen',
alignRight: false,
disablePadding: false,
label: 'LAST SEEN',
},
];
function descendingComparator<T>(a: T, b: T, orderBy: keyof T) {
@ -110,36 +116,10 @@ function stableSort<T>(array: readonly T[], comparator: (a: T, b: T) => number)
return stabilizedThis.map((el) => el[0]);
}
interface HeadCell {
disablePadding: boolean;
id: keyof Data;
label: string;
alignRight: boolean;
}
const headCells: readonly HeadCell[] = [
{
id: 'name',
alignRight: false,
disablePadding: false,
label: 'Display Name & ID',
},
{
id: 'status',
alignRight: false,
disablePadding: false,
label: 'Status',
},
{
id: 'last_seen',
alignRight: false,
disablePadding: false,
label: 'Last Seen',
},
];
interface EnhancedTableProps {
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof Data) => void;
onRequestSort: (event: React.MouseEvent<unknown>, property: keyof TableData) => void;
order: Order;
orderBy: string;
rowCount: number;
@ -149,7 +129,7 @@ function EnhancedTableHead(props: EnhancedTableProps) {
const { order, orderBy, onRequestSort } =
props;
const createSortHandler =
(property: keyof Data) => (event: React.MouseEvent<unknown>) => {
(property: keyof TableData) => (event: React.MouseEvent<unknown>) => {
onRequestSort(event, property);
};
@ -182,10 +162,161 @@ function EnhancedTableHead(props: EnhancedTableProps) {
);
}
interface EnhancedTableToolbarProps {
selected: string | undefined;
tableData: TableData[];
onClear: () => void;
}
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
const { selected, onClear, tableData } = props;
const theme = useTheme();
const matches = useMediaQuery(theme.breakpoints.down('lg'));
const isSelected = selected != undefined;
const [debug, setDebug] = React.useState<boolean>(false);
const debugSx = debug ? {
'--Grid-borderWidth': '1px',
borderTop: 'var(--Grid-borderWidth) solid',
borderLeft: 'var(--Grid-borderWidth) solid',
borderColor: 'divider',
'& > div': {
borderRight: 'var(--Grid-borderWidth) solid',
borderBottom: 'var(--Grid-borderWidth) solid',
borderColor: 'divider',
}
} : {};
const pieData = React.useMemo(() => {
const online = tableData.filter((row) => row.status === NodeStatus.Online).length;
const offline = tableData.filter((row) => row.status === NodeStatus.Offline).length;
const pending = tableData.filter((row) => row.status === NodeStatus.Pending).length;
return [
{ name: 'Online', value: online, color: '#2E7D32' },
{ name: 'Offline', value: offline, color: '#db3927' },
{ name: 'Pending', value: pending, color: '#FFBB28' },
];
}, [tableData]);
const cardData = React.useMemo(() => {
let copy = pieData.filter((pieItem) => pieItem.value > 0);
const elem = { name: 'Total', value: copy.reduce((a, b) => a + b.value, 0), color: '#000000' };
copy.push(elem);
return copy;
}, [pieData]);
const cardStack = (
<Stack
sx={{ ...debugSx, paddingTop: 6 }}
height={350}
id="cardBox"
display="flex"
flexDirection="column"
justifyContent="flex-start"
flexWrap="wrap">
{cardData.map((pieItem) => (
<Card key={pieItem.name} sx={{ marginBottom: 2, marginRight: 2, width: 110, height: 110, backgroundColor: hexRgb(pieItem.color, { format: 'css', alpha: 0.18 }) }}>
<CardContent >
<Typography variant="h4" component="div" gutterBottom={true} textAlign="center">
{pieItem.value}
</Typography>
<Typography sx={{ mb: 1.5 }} color="text.secondary" textAlign="center">
{pieItem.name}
</Typography>
</CardContent>
</Card>
))}
</Stack>
);
const selectedToolbar = (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
}}>
<Tooltip title="Clear">
<IconButton onClick={onClear}>
<ClearIcon />
</IconButton>
</Tooltip>
<Typography
sx={{ flex: '1 1 100%' }}
color="inherit"
style={{ fontSize: 18, marginBottom: 3, marginLeft: 3 }}
component="div"
>
{selected} selected
</Typography>
<Tooltip title="Edit">
<IconButton>
<ModeIcon />
</IconButton>
</Tooltip>
</Toolbar >
);
const unselectedToolbar = (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
}}
>
<Box sx={{ flex: '1 1 100%' }} ></Box>
<Tooltip title="Filter list">
<IconButton>
<FilterListIcon />
</IconButton>
</Tooltip>
</Toolbar >
);
return (
<Grid2 container spacing={1} sx={debugSx}>
<Grid2 xs={6}>
<Typography
sx={{ marginLeft: 3, marginTop: 1 }}
variant="h6"
id="tableTitle"
component="div"
>
NODES
</Typography>
</Grid2>
{/* Debug Controls */}
<Grid2 xs={6} justifyContent="right" display="flex">
<FormGroup>
<FormControlLabel control={<Switch onChange={() => { setDebug(!debug) }} checked={debug} />} label="Debug" />
</FormGroup>
</Grid2>
{/* Pie Chart Grid */}
<Grid2 lg={6} sm={12} display="flex" justifyContent="center" alignItems="center">
<Box height={350} width={400}>
<NodePieChart data={pieData} showLabels={matches} />
</Box>
</Grid2>
{/* Card Stack Grid */}
<Grid2 lg={6} display="flex" sx={{ display: { lg: 'flex', sm: 'none' } }} >
{cardStack}
</Grid2>
{/*Toolbar Grid */}
<Grid2 xs={12}>
{isSelected ? selectedToolbar : unselectedToolbar}
</Grid2>
</Grid2>
);
}
function renderLastSeen(last_seen: number) {
return (
@ -208,98 +339,47 @@ function renderName(name: string, id: string) {
);
}
function renderStatus(status: boolean) {
if (status) {
return (
<Stack direction="row" alignItems="center" gap={1}>
<CircleIcon color="success" style={{ fontSize: 15 }} />
<Typography component="div" align="left" variant="body1">
Online
</Typography>
</Stack>
);
function renderStatus(status: NodeStatus) {
switch (status) {
case NodeStatus.Online:
return (
<Stack direction="row" alignItems="center" gap={1}>
<CircleIcon color="success" style={{ fontSize: 15 }} />
<Typography component="div" align="left" variant="body1">
Online
</Typography>
</Stack>
);
case NodeStatus.Offline:
return (
<Stack direction="row" alignItems="center" gap={1}>
<CircleIcon color="error" style={{ fontSize: 15 }} />
<Typography component="div" align="left" variant="body1">
Offline
</Typography>
</Stack>
);
case NodeStatus.Pending:
return (
<Stack direction="row" alignItems="center" gap={1}>
<CircleIcon color="warning" style={{ fontSize: 15 }} />
<Typography component="div" align="left" variant="body1">
Pending
</Typography>
</Stack>
);
}
return (
<Stack direction="row" alignItems="center" gap={1}>
<CircleIcon color="error" style={{ fontSize: 15 }} />
<Typography component="div" align="left" variant="body1">
Offline
</Typography>
</Stack>
);
}
function EnhancedTableToolbar(props: EnhancedTableToolbarProps) {
const { selected, onClear } = props;
const somethingSelected = selected !== undefined;
const handleSomethingSelected = () => {
if (somethingSelected) {
return (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 },
bgcolor: (theme) =>
alpha(theme.palette.primary.main, theme.palette.action.activatedOpacity),
}}>
<Tooltip title="Clear">
<IconButton onClick={onClear}>
<ClearIcon />
</IconButton>
</Tooltip>
<Typography
sx={{ flex: '1 1 100%' }}
color="inherit"
style={{ fontSize: 18, marginBottom: 2.5, marginLeft: 3 }}
component="div"
>
{selected} selected
</Typography>
<Tooltip title="Edit">
<IconButton>
<ModeIcon />
</IconButton>
</Tooltip>
</Toolbar >
);
} else {
return (
<Toolbar
sx={{
pl: { sm: 2 },
pr: { xs: 1, sm: 1 }
}}>
<Typography
sx={{ flex: '1 1 100%' }}
variant="h6"
id="tableTitle"
component="div"
>
Nodes
</Typography>
<Tooltip title="Filter list">
<IconButton>
<FilterListIcon />
</IconButton>
</Tooltip>
</Toolbar >
);
}
};
return handleSomethingSelected();
export interface NodeTableProps {
tableData: TableData[]
}
export default function EnhancedTable() {
export default function NodeTable(props: NodeTableProps) {
let {tableData} = props;
const [order, setOrder] = React.useState<Order>('asc');
const [orderBy, setOrderBy] = React.useState<keyof Data>('status');
const [orderBy, setOrderBy] = React.useState<keyof TableData>('status');
const [selected, setSelected] = React.useState<string | undefined>(undefined);
const [page, setPage] = React.useState(0);
const [dense, setDense] = React.useState(false);
@ -307,7 +387,7 @@ export default function EnhancedTable() {
const handleRequestSort = (
event: React.MouseEvent<unknown>,
property: keyof Data,
property: keyof TableData,
) => {
const isAsc = orderBy === property && order === 'asc';
setOrder(isAsc ? 'desc' : 'asc');
@ -315,7 +395,8 @@ export default function EnhancedTable() {
};
const handleClick = (event: React.MouseEvent<unknown>, name: string) => {
if (selected === name) {
// Speed optimization. We compare string pointers here instead of the string content.
if (selected == name) {
setSelected(undefined);
} else {
setSelected(name);
@ -331,106 +412,93 @@ export default function EnhancedTable() {
setPage(0);
};
const handleChangeDense = (event: React.ChangeEvent<HTMLInputElement>) => {
setDense(event.target.checked);
};
// TODO: Make a number to increase comparison speed and ui performance
const isSelected = (name: string) => name === selected;
// Speed optimization. We compare string pointers here instead of the string content.
const isSelected = (name: string) => name == selected;
// Avoid a layout jump when reaching the last page with empty rows.
const emptyRows =
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - rows.length) : 0;
page > 0 ? Math.max(0, (1 + page) * rowsPerPage - tableData.length) : 0;
const visibleRows = React.useMemo(
() =>
stableSort(rows, getComparator(order, orderBy)).slice(
stableSort(tableData, getComparator(order, orderBy)).slice(
page * rowsPerPage,
page * rowsPerPage + rowsPerPage,
),
[order, orderBy, page, rowsPerPage],
[order, orderBy, page, rowsPerPage, tableData],
);
return (
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }} id="test">
<EnhancedTableToolbar selected={selected} onClear={() => setSelected(undefined)} />
<TableContainer>
<Table
sx={{ minWidth: 750 }}
aria-labelledby="tableTitle"
size={dense ? 'small' : 'medium'}
>
<EnhancedTableHead
order={order}
orderBy={orderBy}
onRequestSort={handleRequestSort}
rowCount={rows.length}
/>
<TableBody>
{visibleRows.map((row, index) => {
const isItemSelected = isSelected(row.name);
const labelId = `enhanced-table-checkbox-${index}`;
<Paper elevation={1} sx={{ margin: 5 }}>
<Box sx={{ width: '100%' }}>
<Paper sx={{ width: '100%', mb: 2 }}>
<EnhancedTableToolbar tableData={tableData} selected={selected} onClear={() => setSelected(undefined)} />
<TableContainer>
<Table
sx={{ minWidth: 750 }}
aria-labelledby="tableTitle"
size={dense ? 'small' : 'medium'}
>
<EnhancedTableHead
order={order}
orderBy={orderBy}
onRequestSort={handleRequestSort}
rowCount={tableData.length}
/>
<TableBody>
{visibleRows.map((row, index) => {
const isItemSelected = isSelected(row.name);
const labelId = `enhanced-table-checkbox-${index}`;
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.name}
selected={isItemSelected}
sx={{ cursor: 'pointer' }}
>
{/* <TableCell padding="checkbox">
<Checkbox
color="primary"
checked={isItemSelected}
inputProps={{
'aria-labelledby': labelId,
}}
/>
</TableCell> */}
<TableCell
component="th"
id={labelId}
scope="row"
return (
<TableRow
hover
onClick={(event) => handleClick(event, row.name)}
role="checkbox"
aria-checked={isItemSelected}
tabIndex={-1}
key={row.name}
selected={isItemSelected}
sx={{ cursor: 'pointer' }}
>
{renderName(row.name, row.id)}
</TableCell>
<TableCell align="right">{renderStatus(row.status)}</TableCell>
<TableCell align="right">{renderLastSeen(row.last_seen)}</TableCell>
<TableCell
component="th"
id={labelId}
scope="row"
>
{renderName(row.name, row.id)}
</TableCell>
<TableCell align="right">{renderStatus(row.status)}</TableCell>
<TableCell align="right">{renderLastSeen(row.last_seen)}</TableCell>
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow
style={{
height: (dense ? 33 : 53) * emptyRows,
}}
>
<TableCell colSpan={6} />
</TableRow>
);
})}
{emptyRows > 0 && (
<TableRow
style={{
height: (dense ? 33 : 53) * emptyRows,
}}
>
<TableCell colSpan={6} />
</TableRow>
)}
</TableBody>
</Table>
</TableContainer>
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={rows.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
<FormControlLabel
control={<Switch checked={dense} onChange={handleChangeDense} />}
label="Dense padding"
/>
</Box>
)}
</TableBody>
</Table>
</TableContainer>
{/* TODO: This creates the error Warning: Prop `id` did not match. Server: ":RspmmcqH1:" Client: ":R3j6qpj9H1:" */}
<TablePagination
rowsPerPageOptions={[5, 10, 25]}
component="div"
count={tableData.length}
rowsPerPage={rowsPerPage}
page={page}
onPageChange={handleChangePage}
onRowsPerPageChange={handleChangeRowsPerPage}
/>
</Paper>
</Box>
</Paper>
);
}

View File

@ -0,0 +1,45 @@
import React, { PureComponent } from 'react';
import { PieChart, Pie, Sector, Cell, ResponsiveContainer, Legend } from 'recharts';
import { useTheme } from '@mui/material/styles';
import { Box, Color } from '@mui/material';
export interface PieData {
name: string;
value: number;
color: string;
};
interface Props {
data: PieData[];
showLabels?: boolean;
};
export default function NodePieChart(props: Props ) {
const theme = useTheme();
const {data, showLabels} = props;
return (
<Box height={350}>
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
innerRadius={85}
outerRadius={120}
fill={theme.palette.primary.main}
dataKey="value"
nameKey="name"
label={showLabels}
>
{data.map((entry, index) => (
<Cell key={`cell-${index}`} fill={entry.color} />
))}
</Pie>
<Legend verticalAlign="bottom" />
</PieChart>
</ResponsiveContainer>
</Box>
);
};

View File

@ -1,36 +0,0 @@
export default function PieData() {
return [
{
"id": "scala",
"label": "scala",
"value": 317,
"color": "hsl(3, 70%, 50%)"
},
{
"id": "rust",
"label": "rust",
"value": 489,
"color": "hsl(113, 70%, 50%)"
},
{
"id": "css",
"label": "css",
"value": 456,
"color": "hsl(17, 70%, 50%)"
},
{
"id": "elixir",
"label": "elixir",
"value": 343,
"color": "hsl(232, 70%, 50%)"
},
{
"id": "haskell",
"label": "haskell",
"value": 167,
"color": "hsl(292, 70%, 50%)"
}
];
}

View File

@ -1,17 +1,47 @@
"use client"
import { StrictMode } from "react";
import NodeList from "./NodeList";
import NodeList, { NodeStatus, TableData } from "./NodeList";
import Box from "@mui/material/Box";
function createData(
name: string,
id: string,
status: NodeStatus,
last_seen: number,
): TableData {
return {
name,
id,
status,
last_seen: last_seen,
};
}
const tableData = [
createData('Matchbox', "42:0:f21:6916:e333:c47e:4b5c:e74c", NodeStatus.Pending, 0),
createData('Ahorn', "42:0:3c46:b51c:b34d:b7e1:3b02:8d24", NodeStatus.Online, 0),
createData('Yellow', "42:0:3c46:98ac:9c80:4f25:50e3:1d8f", NodeStatus.Offline, 16.0),
createData('Rauter', "42:0:61ea:b777:61ea:803:f885:3523", NodeStatus.Offline, 6.0),
createData('Porree', "42:0:e644:4499:d034:895e:34c8:6f9a", NodeStatus.Offline, 13),
createData('Helsinki', "42:0:3c46:fd4a:acf9:e971:6036:8047", NodeStatus.Online, 0),
createData('Kelle', "42:0:3c46:362d:a9aa:4996:c78e:839a", NodeStatus.Online, 0),
createData('Shodan', "42:0:3c46:6745:adf4:a844:26c4:bf91", NodeStatus.Online, 0.0),
createData('Qubasa', "42:0:3c46:123e:bbea:3529:db39:6764", NodeStatus.Offline, 7.0),
createData('Green', "42:0:a46e:5af:632c:d2fe:a71d:cde0", NodeStatus.Offline, 2),
createData('Gum', "42:0:e644:238d:3e46:c884:6ec5:16c", NodeStatus.Offline, 0),
createData('Xu', "42:0:ca48:c2c2:19fb:a0e9:95b9:794f", NodeStatus.Online, 0),
createData('Zaatar', "42:0:3c46:156e:10b6:3bd6:6e82:b2cd", NodeStatus.Online, 0),
];
export default function Page() {
return (
<Box>
<NodeList />
<Box sx={{ backgroundColor: "#e9ecf5", height: "100%", width: "100%" }} display="inline-block" id="rootBox">
<NodeList tableData={tableData} />
</Box>
);
}

View File

@ -1,13 +1,16 @@
import { createTheme } from "@mui/material/styles";
export const darkTheme = createTheme({
palette: {
mode: "dark",
},
});
export const lightTheme = createTheme({
palette: {
mode: "light",
},
});