Compare commits
11 Commits
3e1ce6904f
...
master
Author | SHA1 | Date | |
---|---|---|---|
327885f968 | |||
1df2506b8c | |||
952d1804ad | |||
f1b7f28fe3 | |||
c91831594f | |||
4955e72d34 | |||
b07d8a0839 | |||
521e53fadd | |||
2f523a9219 | |||
339a98d8ab | |||
cd65f06c5f |
630
package-lock.json
generated
630
package-lock.json
generated
@ -11,7 +11,10 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/joy": "^5.0.0-beta.52",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-charts": "^8.4.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
@ -1171,6 +1174,31 @@
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-7.1.0.tgz",
|
||||
"integrity": "sha512-1mUPMAZ+Qk3jfgL5ftRR06ATH/Esi0izHl1z56H+df6cwIlCWG66RXciUqeJCttbOXOQ5y2DCjLZI/4t3Yg3LA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^7.1.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/joy": {
|
||||
"version": "5.0.0-beta.52",
|
||||
"resolved": "https://registry.npmjs.org/@mui/joy/-/joy-5.0.0-beta.52.tgz",
|
||||
@ -1211,6 +1239,206 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-7.1.0.tgz",
|
||||
"integrity": "sha512-ahUJdrhEv+mCp4XHW+tHIEYzZMSRLg8z4AjUOsj44QpD1ZaMxQoVOG2xiHvLFdcsIPbgSRx1bg1eQSheHBgvtg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/core-downloads-tracker": "^7.1.0",
|
||||
"@mui/system": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
"@types/react-transition-group": "^4.4.12",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0",
|
||||
"react-transition-group": "^4.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^7.1.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@mui/material-pigment-css": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-7.1.0.tgz",
|
||||
"integrity": "sha512-E0OqhZv548Qdc0PwWhLVA2zmjJZSTvaL4ZhoswmI8NJEC1tpW2js6LLP827jrW9MEiXYdz3QS6+hask83w74yQ==",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/private-theming": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-7.1.0.tgz",
|
||||
"integrity": "sha512-4Kck4jxhqF6YxNwJdSae1WgDfXVg0lIH6JVJ7gtuFfuKcQCgomJxPvUEOySTFRPz1IZzwz5OAcToskRdffElDA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/styled-engine": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-7.1.0.tgz",
|
||||
"integrity": "sha512-m0mJ0c6iRC+f9hMeRe0W7zZX1wme3oUX0+XTVHjPG7DJz6OdQ6K/ggEOq7ZdwilcpdsDUwwMfOmvO71qDkYd2w==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@emotion/cache": "^11.13.5",
|
||||
"@emotion/serialize": "^1.3.3",
|
||||
"@emotion/sheet": "^1.4.0",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.4.1",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/system": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-7.1.0.tgz",
|
||||
"integrity": "sha512-iedAWgRJMCxeMHvkEhsDlbvkK+qKf9me6ofsf7twk/jfT4P1ImVf7Rwb5VubEA0sikrVL+1SkoZM41M4+LNAVA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/private-theming": "^7.1.0",
|
||||
"@mui/styled-engine": "^7.1.0",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@mui/utils": "^7.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"csstype": "^3.1.3",
|
||||
"prop-types": "^15.8.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
},
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/types": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
|
||||
"integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material/node_modules/@mui/utils": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/private-theming": {
|
||||
"version": "5.17.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.17.1.tgz",
|
||||
@ -1349,6 +1577,174 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-charts": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-8.4.0.tgz",
|
||||
"integrity": "sha512-XXXt6cHgpTTkLWIImBy0OPD0FwuOdux4AprP/0Zvs0PXuM9D9eeN1piZvo5gjZbPHSsCzIyZzwx9PGncFScgEQ==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2",
|
||||
"@mui/x-charts-vendor": "8.4.0",
|
||||
"@mui/x-internals": "8.4.0",
|
||||
"bezier-easing": "^2.1.0",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"reselect": "^5.1.1",
|
||||
"use-sync-external-store": "^1.5.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.9.0",
|
||||
"@emotion/styled": "^11.8.1",
|
||||
"@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@emotion/react": {
|
||||
"optional": true
|
||||
},
|
||||
"@emotion/styled": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-charts-vendor": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-8.4.0.tgz",
|
||||
"integrity": "sha512-0VvwJSeFezJTnjoKg5YUbbI82a60+VZfH2RyqaosmKH516lKYKSCuAFmkj4vUBP6+ZJPZDW5mWI3VhwD4DN6hg==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@types/d3-color": "^3.1.3",
|
||||
"@types/d3-delaunay": "^6.0.4",
|
||||
"@types/d3-interpolate": "^3.0.4",
|
||||
"@types/d3-scale": "^4.0.9",
|
||||
"@types/d3-shape": "^3.1.7",
|
||||
"@types/d3-time": "^3.0.4",
|
||||
"@types/d3-timer": "^3.0.2",
|
||||
"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",
|
||||
"d3-timer": "^3.0.1",
|
||||
"delaunator": "^5.0.1",
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-charts/node_modules/@mui/types": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
|
||||
"integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-charts/node_modules/@mui/utils": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals": {
|
||||
"version": "8.4.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.4.0.tgz",
|
||||
"integrity": "sha512-Z7FCahC4MLfTVzEwnKOB7P1fiR9DzFuMzHOPRNaMXc/rsS7unbtBKAG94yvsRzReCyjzZUVA7h37lnQ1DoPKJw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/utils": "^7.0.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals/node_modules/@mui/types": {
|
||||
"version": "7.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.2.tgz",
|
||||
"integrity": "sha512-edRc5JcLPsrlNFYyTPxds+d5oUovuUxnnDtpJUbP6WMeV4+6eaX/mqai1ZIWT62lCOe0nlrON0s9HDiv5en5bA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-internals/node_modules/@mui/utils": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.1.0.tgz",
|
||||
"integrity": "sha512-/OM3S8kSHHmWNOP+NH9xEtpYSG10upXeQ0wLZnfDgmgadTAk5F4MQfFLyZ5FCRJENB3eRzltMmaNl6UtDnPovw==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.27.1",
|
||||
"@mui/types": "^7.4.2",
|
||||
"@types/prop-types": "^15.7.14",
|
||||
"clsx": "^2.1.1",
|
||||
"prop-types": "^15.8.1",
|
||||
"react-is": "^19.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
@ -1700,6 +2096,55 @@
|
||||
"@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=="
|
||||
},
|
||||
"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=="
|
||||
},
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"@types/d3-color": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-path": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz",
|
||||
"integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg=="
|
||||
},
|
||||
"node_modules/@types/d3-scale": {
|
||||
"version": "4.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz",
|
||||
"integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==",
|
||||
"dependencies": {
|
||||
"@types/d3-time": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-shape": {
|
||||
"version": "3.1.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.7.tgz",
|
||||
"integrity": "sha512-VLvUQ33C+3J+8p+Daf+nYSOsjB4GXp19/S/aGo60m9h1v6XaxjiT82lKVWJCfzhtuZ3yD7i/TPeC/fuKLLOSmg==",
|
||||
"dependencies": {
|
||||
"@types/d3-path": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-time": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz",
|
||||
"integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g=="
|
||||
},
|
||||
"node_modules/@types/d3-timer": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz",
|
||||
"integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw=="
|
||||
},
|
||||
"node_modules/@types/estree": {
|
||||
"version": "1.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
|
||||
@ -1726,7 +2171,6 @@
|
||||
"version": "19.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.5.tgz",
|
||||
"integrity": "sha512-piErsCVVbpMMT2r7wbawdZsq4xMvIAhQuac2gedQHysu1TZYEigE6pnFfgZT+/jQnrRuF5r+SHzuehFjfRjr4g==",
|
||||
"devOptional": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@ -1740,6 +2184,14 @@
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-transition-group": {
|
||||
"version": "4.4.12",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
|
||||
"integrity": "sha512-8TV6R3h2j7a91c+1DXdJi3Syo69zzIZbz7Lg5tORM5LEJG7X/E6a1V3drRyBRZq7/utz7A+c4OgYLiLcYGHG6w==",
|
||||
"peerDependencies": {
|
||||
"@types/react": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.32.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.32.1.tgz",
|
||||
@ -2055,6 +2507,11 @@
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/bezier-easing": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz",
|
||||
"integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@ -2233,6 +2690,119 @@
|
||||
"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==",
|
||||
"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-delaunay": {
|
||||
"version": "6.0.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-delaunay/-/d3-delaunay-6.0.4.tgz",
|
||||
"integrity": "sha512-mdjtIZ1XLAM8bm/hx3WwjfHt6Sggek7qH043O8KEjDXN40xi3vx/6pYSVTwLjEgiXQTbvaouWKynLBiUZ6SK6A==",
|
||||
"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==",
|
||||
"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/debug": {
|
||||
"version": "4.4.1",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||
@ -2255,6 +2825,23 @@
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"robust-predicates": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-helpers": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz",
|
||||
"integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.8.7",
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/electron-to-chromium": {
|
||||
"version": "1.5.157",
|
||||
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.157.tgz",
|
||||
@ -2743,6 +3330,14 @@
|
||||
"node": ">=0.8.19"
|
||||
}
|
||||
},
|
||||
"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-arrayish": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
|
||||
@ -3243,6 +3838,26 @@
|
||||
"node": ">=0.10.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",
|
||||
"integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.5.5",
|
||||
"dom-helpers": "^5.0.1",
|
||||
"loose-envify": "^1.4.0",
|
||||
"prop-types": "^15.6.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=16.6.0",
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/reselect": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
|
||||
"integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w=="
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.22.10",
|
||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||
@ -3280,6 +3895,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/robust-predicates": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/robust-predicates/-/robust-predicates-3.0.2.tgz",
|
||||
"integrity": "sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg=="
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "4.41.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.41.1.tgz",
|
||||
@ -3586,6 +4206,14 @@
|
||||
"punycode": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/use-sync-external-store": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.5.0.tgz",
|
||||
"integrity": "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A==",
|
||||
"peerDependencies": {
|
||||
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "6.3.5",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-6.3.5.tgz",
|
||||
|
@ -13,7 +13,10 @@
|
||||
"@emotion/react": "^11.14.0",
|
||||
"@emotion/styled": "^11.14.0",
|
||||
"@fontsource/inter": "^5.2.5",
|
||||
"@mui/icons-material": "^7.1.0",
|
||||
"@mui/joy": "^5.0.0-beta.52",
|
||||
"@mui/material": "^7.1.0",
|
||||
"@mui/x-charts": "^8.4.0",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0"
|
||||
},
|
||||
|
119
src/App.tsx
119
src/App.tsx
@ -1,9 +1,124 @@
|
||||
import './App.css'
|
||||
import {PlayersContext} from "./api/contexts/PlayersContext.tsx";
|
||||
import type IPlayer from "./api/types/IPlayer.ts";
|
||||
import {useEffect, useState} from "react";
|
||||
import type IGame from "./api/types/IGame.ts";
|
||||
import {GamesContext} from "./api/contexts/GamesContext.tsx";
|
||||
import {ApiUriContext, getData} from "./api/fetchApi.tsx";
|
||||
import {GetGames, GetPlayers} from "./api/endpoints/Data.tsx";
|
||||
import {
|
||||
AccordionGroup,
|
||||
Box, Button, CircularProgress,
|
||||
Divider,
|
||||
Input,
|
||||
Stack,
|
||||
Typography
|
||||
} from "@mui/joy";
|
||||
import GameAccordionItem from "./components/GameAccordionItem.tsx";
|
||||
import PlayerAccordionItem from "./components/PlayerAccordionItem.tsx";
|
||||
import PlayerGameStatsDrawer from "./components/PlayerGameStatsDrawer.tsx";
|
||||
import {Cancel, CheckCircleOutline} from '@mui/icons-material';
|
||||
import {PlayerStatsDrawer} from "./components/PlayerStatsDrawer.tsx";
|
||||
import {AddPlayer} from "./api/endpoints/Actions.tsx";
|
||||
|
||||
export default function App() {
|
||||
const [apiUri, setApiUri] = useState<string>("http://127.0.0.1:5239");
|
||||
const [connected, setConnected] = useState<boolean>(false);
|
||||
const [checkingConnection, setCheckingConnection] = useState<boolean>(false);
|
||||
|
||||
const [players, setPlayers] = useState<IPlayer[]>([]);
|
||||
const [games, setGames] = useState<IGame[]>([]);
|
||||
|
||||
const [selectedPlayer, setSelectedPlayer] = useState<IPlayer | null>(null);
|
||||
const [selectedGame, setSelectedGame] = useState<IGame | null>(null);
|
||||
const [openPlayerGameStats, setOpenPlayerGameStats] = useState<boolean>(false);
|
||||
const [openPlayerStats, setOpenPlayerStats] = useState<boolean>(false);
|
||||
|
||||
const OpenPlayerGameStatsDrawer = (player: IPlayer, game: IGame) => {
|
||||
setSelectedPlayer(player);
|
||||
setSelectedGame(game);
|
||||
setOpenPlayerGameStats(true);
|
||||
}
|
||||
|
||||
const OpenPlayerStatsDrawer = (player: IPlayer) => {
|
||||
setSelectedPlayer(player);
|
||||
setOpenPlayerStats(true);
|
||||
}
|
||||
|
||||
const checkConnection = () => {
|
||||
setCheckingConnection(true);
|
||||
return getData(`${apiUri}/swagger/v1/swagger.json`)
|
||||
.then(() => {
|
||||
setConnected(true);
|
||||
GetPlayers(apiUri).then(setPlayers);
|
||||
GetGames(apiUri).then(setGames);
|
||||
return Promise.resolve();
|
||||
})
|
||||
.catch(() => {
|
||||
setConnected(false)
|
||||
setPlayers([]);
|
||||
setGames([]);
|
||||
return Promise.reject();
|
||||
})
|
||||
.finally(() => {
|
||||
setCheckingConnection(false)
|
||||
});
|
||||
}
|
||||
|
||||
const [connectionTimer, setConnectionTimer] = useState<number | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if(connectionTimer)
|
||||
clearInterval(connectionTimer);
|
||||
checkConnection()
|
||||
.then(() => setConnectionTimer(setInterval(checkConnection, 5000)));
|
||||
}, [apiUri]);
|
||||
|
||||
const [addPlayerStr, setAddPlayerStr] = useState("");
|
||||
const addPlayer = () => {
|
||||
const steamId = BigInt(addPlayerStr);
|
||||
AddPlayer(apiUri, steamId);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
</>
|
||||
<ApiUriContext value={apiUri}>
|
||||
<PlayersContext.Provider value={{players: players}}>
|
||||
<GamesContext value={{games: games}}>
|
||||
<Stack direction="row" spacing={2} position={"fixed"} top={5}>
|
||||
<Input type={"text"}
|
||||
placeholder={"Api Uri"}
|
||||
value={apiUri}
|
||||
onChange={(e) => setApiUri(e.target.value)}
|
||||
endDecorator={checkingConnection
|
||||
? <CircularProgress sx={{height: "100%"}} />
|
||||
: connected
|
||||
? <CheckCircleOutline color={"success"} />
|
||||
: <Cancel color={"error"} />}
|
||||
/>
|
||||
<Input type={"text"}
|
||||
placeholder={"Add SteamId"}
|
||||
onChange={(e) => setAddPlayerStr(e.target.value)}
|
||||
endDecorator={<Button onClick={addPlayer}>Add</Button>}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack direction={"row"} spacing={2} overflow={"scroll"} position={"fixed"} top={50} width={"100%"} height={"calc(100% - 50px)"}>
|
||||
<Box sx={{width:'50%'}}>
|
||||
<Typography level={"h2"}>Players</Typography>
|
||||
<AccordionGroup>
|
||||
{players?.map((player) => <PlayerAccordionItem key={player.steamId} player={player} OpenPlayerGameStatsDrawer={OpenPlayerGameStatsDrawer} OpenPlayerStatsDrawer={OpenPlayerStatsDrawer} />)}
|
||||
</AccordionGroup>
|
||||
</Box>
|
||||
<Divider />
|
||||
<Box sx={{width:'50%'}}>
|
||||
<Typography level={"h2"}>Games</Typography>
|
||||
<AccordionGroup>
|
||||
{games?.map((game) => <GameAccordionItem key={game.appId} game={game} OpenPlayerGameStatsDrawer={OpenPlayerGameStatsDrawer} />)}
|
||||
</AccordionGroup>
|
||||
</Box>
|
||||
</Stack>
|
||||
<PlayerGameStatsDrawer player={selectedPlayer} game={selectedGame} open={openPlayerGameStats} setOpen={setOpenPlayerGameStats} />
|
||||
<PlayerStatsDrawer player={selectedPlayer} open={openPlayerStats} setOpen={setOpenPlayerStats} />
|
||||
</GamesContext>
|
||||
</PlayersContext.Provider>
|
||||
</ApiUriContext>
|
||||
)
|
||||
}
|
||||
|
8
src/api/contexts/GamesContext.tsx
Normal file
8
src/api/contexts/GamesContext.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import {createContext} from "react";
|
||||
import type IGame from "../types/IGame.ts";
|
||||
|
||||
export const GamesContext = createContext<{games: IGame[]}>(
|
||||
{
|
||||
games: []
|
||||
}
|
||||
);
|
8
src/api/contexts/PlayersContext.tsx
Normal file
8
src/api/contexts/PlayersContext.tsx
Normal file
@ -0,0 +1,8 @@
|
||||
import {createContext} from "react";
|
||||
import type IPlayer from "../types/IPlayer.ts";
|
||||
|
||||
export const PlayersContext = createContext<{players: IPlayer[]}>(
|
||||
{
|
||||
players: []
|
||||
}
|
||||
);
|
30
src/api/endpoints/Actions.tsx
Normal file
30
src/api/endpoints/Actions.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
import {deleteData, postData, putData} from "../fetchApi.tsx";
|
||||
import type IPlayer from "../types/IPlayer.ts";
|
||||
|
||||
export function AddPlayer(apiUri: string, steamId: bigint){
|
||||
return putData(`${apiUri}/Actions/Player/${steamId}`) as Promise<IPlayer>;
|
||||
}
|
||||
|
||||
export function DeletePlayer(apiUri: string, steamId: bigint){
|
||||
return deleteData(`${apiUri}/Actions/Player/${steamId}`) as Promise<void>;
|
||||
}
|
||||
|
||||
export function UpdatePlayerData(apiUri: string, steamId: bigint){
|
||||
return postData(`${apiUri}/Actions/Update/Player/${steamId}/All`) as Promise<void>;
|
||||
}
|
||||
|
||||
export function UpdatePlayerInfo(apiUri: string, steamId: bigint){
|
||||
return postData(`${apiUri}/Actions/Update/Player/${steamId}/Info`) as Promise<void>;
|
||||
}
|
||||
|
||||
export function UpdatePlayerOwnedGames(apiUri: string, steamId: bigint){
|
||||
return postData(`${apiUri}/Actions/Update/Player/${steamId}/OwnedGames`) as Promise<void>;
|
||||
}
|
||||
|
||||
export function UpdatePlayerTimeTracked(apiUri: string, steamId: bigint){
|
||||
return postData(`${apiUri}/Actions/Update/Player/${steamId}/TimeTracked`) as Promise<void>;
|
||||
}
|
||||
|
||||
export function UpdateAll(apiUri: string){
|
||||
return postData(`${apiUri}/Actions/All`) as Promise<void>;
|
||||
}
|
27
src/api/endpoints/Data.tsx
Normal file
27
src/api/endpoints/Data.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import {getData} from "../fetchApi.tsx";
|
||||
import type IPlayer from "../types/IPlayer.ts";
|
||||
import type IGame from "../types/IGame.ts";
|
||||
|
||||
export function GetPlayers(apiUri: string){
|
||||
return getData(`${apiUri}/Data/Players`) as Promise<IPlayer[]>;
|
||||
}
|
||||
|
||||
export function GetPlayer(apiUri: string, steamdId: bigint){
|
||||
return getData(`${apiUri}/Data/Player/${steamdId}`) as Promise<IPlayer>;
|
||||
}
|
||||
|
||||
export function GetGamesOfPlayer(apiUri: string, steamdId: bigint){
|
||||
return getData(`${apiUri}/Data/Player/${steamdId}/Games`) as Promise<IGame[]>;
|
||||
}
|
||||
|
||||
export function GetGames(apiUri: string){
|
||||
return getData(`${apiUri}/Data/Games`) as Promise<IGame[]>;
|
||||
}
|
||||
|
||||
export function GetGame(apiUri: string, appId: bigint){
|
||||
return getData(`${apiUri}/Data/Game/${appId}`) as Promise<IGame>;
|
||||
}
|
||||
|
||||
export function GetPlayersOfGame(apiUri: string, appId: bigint){
|
||||
return getData(`${apiUri}/Data/Game/${appId}/Players`) as Promise<IPlayer[]>;
|
||||
}
|
18
src/api/endpoints/TimeTrack.tsx
Normal file
18
src/api/endpoints/TimeTrack.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import {getData} from "../fetchApi.tsx";
|
||||
import type ITrackedTime from "../types/ITrackedTime.ts";
|
||||
|
||||
export function GetTimelines(apiUri: string, steamId: bigint){
|
||||
return getData(`${apiUri}/TimeTrack/${steamId}`) as Promise<{key: bigint, value: ITrackedTime[]}[]>;
|
||||
}
|
||||
|
||||
export function GetTimelineGame(apiUri: string, steamId: bigint, appId: bigint){
|
||||
return getData(`${apiUri}/TimeTrack/${steamId}/${appId}`) as Promise<ITrackedTime[]>;
|
||||
}
|
||||
|
||||
export function GetTotal(apiUri: string, steamId: bigint){
|
||||
return getData(`${apiUri}/TimeTrack/${steamId}/Total`) as unknown as Promise<bigint>;
|
||||
}
|
||||
|
||||
export function GetTotalPerGame(apiUri: string, steamId: bigint){
|
||||
return getData(`${apiUri}/TimeTrack/${steamId}/Total/PerGame`) as Promise<{key: bigint, value: bigint}[]>;
|
||||
}
|
121
src/api/fetchApi.tsx
Normal file
121
src/api/fetchApi.tsx
Normal file
@ -0,0 +1,121 @@
|
||||
import {createContext} from "react";
|
||||
|
||||
export const ApiUriContext = createContext<string>("");
|
||||
|
||||
export function getData(uri: string) : Promise<object | undefined> {
|
||||
return makeRequestWrapper("GET", uri, null);
|
||||
}
|
||||
|
||||
export function postData(uri: string, content?: object | string | number | boolean | null) : Promise<object | undefined> {
|
||||
return makeRequestWrapper("POST", uri, content);
|
||||
}
|
||||
|
||||
export function deleteData(uri: string) : Promise<void> {
|
||||
return makeRequestWrapper("DELETE", uri, null) as Promise<void>;
|
||||
}
|
||||
|
||||
export function patchData(uri: string, content: object | string | number | boolean) : Promise<object | undefined> {
|
||||
return makeRequestWrapper("patch", uri, content);
|
||||
}
|
||||
|
||||
export function putData(uri: string, content?: object | string | number | boolean | null) : Promise<object | undefined> {
|
||||
return makeRequestWrapper("PUT", uri, content);
|
||||
}
|
||||
|
||||
function makeRequestWrapper(method: string, uri: string, content?: object | string | number | null | boolean) : Promise<object | undefined>{
|
||||
return makeRequest(method, uri, content)
|
||||
.then((result) => result as Promise<object>)
|
||||
.catch((e) => {
|
||||
console.warn(e);
|
||||
return Promise.resolve(undefined);
|
||||
});
|
||||
}
|
||||
|
||||
let currentlyRequestedEndpoints: string[] = [];
|
||||
function makeRequest(method: string, uri: string, content?: object | string | number | null | boolean) : Promise<object | void> {
|
||||
const id = method + uri;
|
||||
if(currentlyRequestedEndpoints.find(x => x == id) != undefined)
|
||||
return Promise.reject(`Already requested: ${method} ${uri}`);
|
||||
currentlyRequestedEndpoints.push(id);
|
||||
return fetch(uri,
|
||||
{
|
||||
method: method,
|
||||
headers : {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/json'
|
||||
},
|
||||
body: content ? JSON.stringify(content) : null
|
||||
})
|
||||
.then(function(response){
|
||||
if(!response.ok){
|
||||
if(response.status === 503){
|
||||
currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1)
|
||||
let retryHeaderVal = response.headers.get("Retry-After");
|
||||
let seconds = 10;
|
||||
if(retryHeaderVal === null){
|
||||
return response.text().then(text => {
|
||||
seconds = parseInt(text);
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
||||
.then(() => {
|
||||
return makeRequest(method, uri, content);
|
||||
});
|
||||
});
|
||||
}else {
|
||||
seconds = parseInt(retryHeaderVal);
|
||||
return new Promise(resolve => setTimeout(resolve, seconds * 1000))
|
||||
.then(() => {
|
||||
return makeRequest(method, uri, content);
|
||||
});
|
||||
}
|
||||
}else
|
||||
throw new Error(response.statusText);
|
||||
}
|
||||
return response.text().then(text => JSON.parseBigInt(text) as object);
|
||||
})
|
||||
.catch(function(err : Error){
|
||||
console.error(`Error ${method}ing Data ${uri}\n${err}`);
|
||||
return Promise.reject();
|
||||
}).finally(() => currentlyRequestedEndpoints.splice(currentlyRequestedEndpoints.indexOf(id), 1));
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface JSON {
|
||||
parseBigInt: (jsonStr: string, options?: { minDigits?: number; fallbackToString?: boolean; }) => unknown;
|
||||
}
|
||||
}
|
||||
|
||||
function quoteLargeNumbers(jsonStr: string, minDigits: number): string {
|
||||
const regex = new RegExp(`(-?\\d{${minDigits},})(?=\\s*[,}\\]])`, 'g');
|
||||
return jsonStr.replace(regex, '"$1"');
|
||||
}
|
||||
|
||||
JSON.parseBigInt = function (jsonStr: string, options?: { minDigits?: number; fallbackToString?: boolean;}): unknown {
|
||||
const minDigits = options?.minDigits ?? 15;
|
||||
const fallbackToString = options?.fallbackToString ?? true;
|
||||
|
||||
const safeStr = quoteLargeNumbers(jsonStr, minDigits);
|
||||
|
||||
return JSON.parse(safeStr, (_key, value) => {
|
||||
if (typeof value === 'string') {
|
||||
const bigintRegex = new RegExp(`^-?\\d{${minDigits},}$`);
|
||||
if (bigintRegex.test(value)) {
|
||||
try {
|
||||
if (typeof BigInt !== 'undefined') return BigInt(value);
|
||||
else if (fallbackToString) return value;
|
||||
} catch {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return value;
|
||||
});
|
||||
};
|
||||
|
||||
export function isValidUri(uri: string) : boolean{
|
||||
try {
|
||||
new URL(uri);
|
||||
return true;
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
6
src/api/types/IGame.ts
Normal file
6
src/api/types/IGame.ts
Normal file
@ -0,0 +1,6 @@
|
||||
export default interface IGame {
|
||||
appId: bigint,
|
||||
name: string,
|
||||
iconUrl?: string | null,
|
||||
logoUrl?: string | null,
|
||||
}
|
7
src/api/types/IPlayer.ts
Normal file
7
src/api/types/IPlayer.ts
Normal file
@ -0,0 +1,7 @@
|
||||
export default interface IPlayer {
|
||||
steamId: bigint,
|
||||
name: string,
|
||||
profileUrl: string,
|
||||
avatarUrl: string,
|
||||
updatedAt: Date
|
||||
}
|
4
src/api/types/ITrackedTime.ts
Normal file
4
src/api/types/ITrackedTime.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export default interface ITrackedTime {
|
||||
timeStamp: Date,
|
||||
timePlayed: bigint
|
||||
}
|
47
src/components/GameAccordionItem.tsx
Normal file
47
src/components/GameAccordionItem.tsx
Normal file
@ -0,0 +1,47 @@
|
||||
import type IGame from "../api/types/IGame.ts";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary, CircularProgress,
|
||||
Stack, Tooltip, Typography
|
||||
} from "@mui/joy";
|
||||
import type IPlayer from "../api/types/IPlayer.ts";
|
||||
import {useContext, useEffect, useState} from "react";
|
||||
import {GetPlayersOfGame} from "../api/endpoints/Data.tsx";
|
||||
import {ApiUriContext} from "../api/fetchApi.tsx";
|
||||
import PlayerCard from "./PlayerCard.tsx";
|
||||
|
||||
export default function GameAccordionItem({game, OpenPlayerGameStatsDrawer} : {game: IGame, OpenPlayerGameStatsDrawer : (player: IPlayer, game: IGame) => void}) {
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
|
||||
const [players, setPlayers] = useState<IPlayer[]>([]);
|
||||
const [expanded, setExpanded] = useState<boolean>(false);
|
||||
const [loading, setLoading] = useState<boolean>(false);
|
||||
|
||||
useEffect(() => {
|
||||
if(!expanded)
|
||||
return;
|
||||
setLoading(true);
|
||||
GetPlayersOfGame(apiUri, game.appId)
|
||||
.then(setPlayers)
|
||||
.finally(() => setLoading(false));
|
||||
}, [expanded]);
|
||||
|
||||
return (
|
||||
<Accordion key={game.appId} onChange={(_, expanded) => setExpanded(expanded)}>
|
||||
<AccordionSummary>
|
||||
<img src={game.iconUrl??"favicon.ico"} />
|
||||
<Tooltip title={game.appId}>
|
||||
<Typography level={"h4"}>{game.name}</Typography>
|
||||
</Tooltip>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack flexWrap={"wrap"} direction={"row"} spacing={1} useFlexGap>
|
||||
{loading
|
||||
? <CircularProgress />
|
||||
: players?.map((player) => <PlayerCard key={player.steamId} player={player} onClick={() => OpenPlayerGameStatsDrawer(player, game)} />)}
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
19
src/components/GameCard.tsx
Normal file
19
src/components/GameCard.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import {AspectRatio, Card, CardContent, Stack, Typography} from "@mui/joy";
|
||||
import type IGame from "../api/types/IGame.ts";
|
||||
|
||||
export default function GameCard({game, onClick} : {game: IGame | null, onClick?: React.MouseEventHandler<HTMLDivElement> | undefined}) {
|
||||
return (
|
||||
<AspectRatio ratio={4} sx={{width: '256px'}}>
|
||||
<Card onClick={onClick}>
|
||||
<CardContent>
|
||||
<Stack direction="row" spacing={1} sx={{minWidth: "100%", width: "fit-content"}} alignContent={"center"}>
|
||||
<AspectRatio ratio={1} sx={{width: '64px'}}>
|
||||
<img src={game?.iconUrl??"favicon.ico"} />
|
||||
</AspectRatio>
|
||||
<Typography level={"title-lg"} alignContent={"center"} overflow={"hidden"} noWrap>{game?.name}</Typography>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AspectRatio>
|
||||
);
|
||||
}
|
55
src/components/PlayerAccordionItem.tsx
Normal file
55
src/components/PlayerAccordionItem.tsx
Normal file
@ -0,0 +1,55 @@
|
||||
import type IGame from "../api/types/IGame.ts";
|
||||
import {
|
||||
Accordion,
|
||||
AccordionDetails,
|
||||
AccordionSummary, Button, CircularProgress,
|
||||
Stack, Tooltip, Typography
|
||||
} from "@mui/joy";
|
||||
import type IPlayer from "../api/types/IPlayer.ts";
|
||||
import {useContext, useEffect, useState} from "react";
|
||||
import {ApiUriContext} from "../api/fetchApi.tsx";
|
||||
import {GetGamesOfPlayer} from "../api/endpoints/Data.tsx";
|
||||
import GameCard from "./GameCard.tsx";
|
||||
import {GetTotal} from "../api/endpoints/TimeTrack.tsx";
|
||||
|
||||
export default function PlayerAccordionItem({player, OpenPlayerGameStatsDrawer, OpenPlayerStatsDrawer} : {player: IPlayer, OpenPlayerGameStatsDrawer : (player: IPlayer, game: IGame) => void, OpenPlayerStatsDrawer : (player: IPlayer) => void}) {
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
|
||||
const [games, setGames] = useState<IGame[]>([]);
|
||||
const [expanded, setExpanded] = useState<boolean>(false);
|
||||
const [loadingGames, setLoadingGames] = useState<boolean>(false);
|
||||
const [totalTime, setTotalTime] = useState<bigint>(BigInt(0));
|
||||
|
||||
useEffect(() => {
|
||||
if(!expanded)
|
||||
return;
|
||||
setLoadingGames(true);
|
||||
GetGamesOfPlayer(apiUri, player.steamId)
|
||||
.then(setGames)
|
||||
.finally(() => setLoadingGames(false));
|
||||
GetTotal(apiUri, player.steamId)
|
||||
.then(setTotalTime)
|
||||
}, [expanded]);
|
||||
|
||||
return (
|
||||
<Accordion key={player.steamId} onChange={(_, expanded) => setExpanded(expanded)}>
|
||||
<AccordionSummary>
|
||||
<img src={player.avatarUrl} />
|
||||
<Tooltip title={player.steamId}>
|
||||
<Typography level={"h4"}>{player.name}</Typography>
|
||||
</Tooltip>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Stack flexWrap={"nowrap"} direction={"row"} alignContent={"center"} spacing={2} useFlexGap marginBottom={"10px"}>
|
||||
<Button onClick={() => OpenPlayerStatsDrawer(player)}>All Games</Button>
|
||||
<Typography level={"h4"} >Total Time: {totalTime}</Typography>
|
||||
</Stack>
|
||||
<Stack flexWrap={"wrap"} direction={"row"} spacing={1} useFlexGap>
|
||||
{loadingGames
|
||||
? <CircularProgress />
|
||||
: games?.map((game) => <GameCard key={game.appId} game={game} onClick={() => OpenPlayerGameStatsDrawer(player, game)} />)}
|
||||
</Stack>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
);
|
||||
}
|
19
src/components/PlayerCard.tsx
Normal file
19
src/components/PlayerCard.tsx
Normal file
@ -0,0 +1,19 @@
|
||||
import {AspectRatio, Card, CardContent, Stack, Typography} from "@mui/joy";
|
||||
import type IPlayer from "../api/types/IPlayer.ts";
|
||||
|
||||
export default function PlayerCard({player, onClick} : {player: IPlayer | null, onClick?: React.MouseEventHandler<HTMLDivElement> | undefined}) {
|
||||
return (
|
||||
<AspectRatio ratio={4} sx={{width: '256px'}}>
|
||||
<Card onClick={onClick}>
|
||||
<CardContent>
|
||||
<Stack direction="row" spacing={1} sx={{minWidth: "100%", width: "fit-content"}} alignContent={"center"}>
|
||||
<AspectRatio ratio={1} sx={{width: '64px'}}>
|
||||
<img src={player?.avatarUrl} />
|
||||
</AspectRatio>
|
||||
<Typography level={"title-lg"} alignContent={"center"} overflow={"hidden"} noWrap>{player?.name}</Typography>
|
||||
</Stack>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AspectRatio>
|
||||
);
|
||||
}
|
39
src/components/PlayerGameStatsDrawer.tsx
Normal file
39
src/components/PlayerGameStatsDrawer.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import type IPlayer from "../api/types/IPlayer.ts";
|
||||
import type IGame from "../api/types/IGame.ts";
|
||||
import {DialogContent, DialogTitle, Drawer, ModalClose} from "@mui/joy";
|
||||
import {type Dispatch, useContext, useEffect, useState} from "react";
|
||||
import {ApiUriContext} from "../api/fetchApi.tsx";
|
||||
import {GetTimelineGame} from "../api/endpoints/TimeTrack.tsx";
|
||||
import type ITrackedTime from "../api/types/ITrackedTime.ts";
|
||||
import {LineChart} from "@mui/x-charts";
|
||||
import PlayerCard from "./PlayerCard.tsx";
|
||||
import GameCard from "./GameCard.tsx";
|
||||
|
||||
export default function PlayerGameStatsDrawer({player, game, open, setOpen} : {player: IPlayer | null, game: IGame | null, open: boolean, setOpen: Dispatch<boolean>}) {
|
||||
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
|
||||
const [trackedTime, setTrackedTime] = useState<ITrackedTime[]>();
|
||||
|
||||
useEffect(() => {
|
||||
if(!open || !game || !player)
|
||||
return;
|
||||
GetTimelineGame(apiUri, player.steamId, game.appId).then(setTrackedTime);
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Drawer anchor={"bottom"} size={"lg"} open={open} onClose={() => setOpen(false)}>
|
||||
<ModalClose />
|
||||
<DialogTitle>
|
||||
<GameCard game={game} />
|
||||
<PlayerCard player={player} />
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<LineChart xAxis={[{data : trackedTime?.map(t => new Date(t.timeStamp))??[], scaleType: "utc", label: "Date"}]}
|
||||
yAxis={[{label: "Minutes Played", scaleType: "linear", min: 0}]}
|
||||
series={[{data: trackedTime?.map(t => Number(t.timePlayed))??[], label: game?.name}]}
|
||||
sx={{height: "80%"}}/>
|
||||
</DialogContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
88
src/components/PlayerStatsDrawer.tsx
Normal file
88
src/components/PlayerStatsDrawer.tsx
Normal file
@ -0,0 +1,88 @@
|
||||
import type IPlayer from "../api/types/IPlayer.ts";
|
||||
import {Autocomplete, DialogContent, DialogTitle, Drawer, ModalClose, Stack} from "@mui/joy";
|
||||
import {type Dispatch, useContext, useEffect, useState} from "react";
|
||||
import {ApiUriContext} from "../api/fetchApi.tsx";
|
||||
import {LineChart, type LineSeriesType} from "@mui/x-charts";
|
||||
import PlayerCard from "./PlayerCard.tsx";
|
||||
import {GamesContext} from "../api/contexts/GamesContext.tsx";
|
||||
import {GetTimelines} from "../api/endpoints/TimeTrack.tsx";
|
||||
import type {DatasetElementType} from "@mui/x-charts/internals";
|
||||
|
||||
export function PlayerStatsDrawer({player, open, setOpen}: {
|
||||
player: IPlayer | null,
|
||||
open: boolean,
|
||||
setOpen: Dispatch<boolean>
|
||||
}) {
|
||||
|
||||
const apiUri = useContext(ApiUriContext);
|
||||
const games = useContext(GamesContext);
|
||||
|
||||
const [trackedTimes, setTrackedTimes] = useState<DatasetElementType<string | number | Date | null | undefined>[]>([]);
|
||||
const [availableSeries, setAvailableSeries] = useState<LineSeriesType[]>([]);
|
||||
const [selectedSeries, setSelectedSeries] = useState<LineSeriesType[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open || !player)
|
||||
return;
|
||||
GetTimelines(apiUri, player.steamId)
|
||||
.then((tt) => {
|
||||
const times: DatasetElementType<string | number | Date | null | undefined>[] = [];
|
||||
const g: Set<bigint> = new Set<bigint>();
|
||||
tt.forEach((app) => {
|
||||
app.value.forEach((time) => {
|
||||
let o = {date: new Date(time.timeStamp)};
|
||||
// @ts-ignore
|
||||
o[`${app.key.toString()}`] = Number(time.timePlayed / 60)
|
||||
times.push(o);
|
||||
g.add(app.key);
|
||||
});
|
||||
});
|
||||
|
||||
const seriesTypes: LineSeriesType[] = [];
|
||||
const iterator = g.keys();
|
||||
let value = iterator.next();
|
||||
while (!value.done) {
|
||||
const appId = value.value;
|
||||
seriesTypes.push({
|
||||
type: "line",
|
||||
dataKey: appId.toString(),
|
||||
connectNulls: true,
|
||||
label: games.games.find(g => g.appId == appId)?.name ?? appId?.toString() ?? ""
|
||||
})
|
||||
value = iterator.next();
|
||||
}
|
||||
|
||||
setAvailableSeries(seriesTypes);
|
||||
setTrackedTimes(times);
|
||||
});
|
||||
}, [open]);
|
||||
|
||||
return (
|
||||
<Drawer anchor={"bottom"} size={"lg"} open={open} onClose={() => setOpen(false)}>
|
||||
<ModalClose/>
|
||||
<DialogTitle>
|
||||
<PlayerCard player={player}/>
|
||||
</DialogTitle>
|
||||
<DialogContent>
|
||||
<Stack direction={"column"} height={"100%"}>
|
||||
<Autocomplete multiple
|
||||
options={availableSeries.sort((a, b) => (a.label as string).localeCompare(b.label as string))}
|
||||
placeholder={"Games"}
|
||||
getOptionLabel={(opt) => opt.label as string}
|
||||
isOptionEqualToValue={(a, b) => a.dataKey == b.dataKey}
|
||||
onChange={(_, value, reason) => {
|
||||
if(reason == "selectOption" || reason == "removeOption" || reason == "clear")
|
||||
setSelectedSeries(value);
|
||||
console.log(value);
|
||||
}}
|
||||
/>
|
||||
<LineChart dataset={trackedTimes}
|
||||
xAxis={[{dataKey: "date", scaleType: "utc"}]}
|
||||
yAxis={[{dataKey: "time", scaleType: "linear", min: 0}]}
|
||||
series={selectedSeries}
|
||||
/>
|
||||
</Stack>
|
||||
</DialogContent>
|
||||
</Drawer>
|
||||
);
|
||||
}
|
@ -2,9 +2,13 @@ import { StrictMode } from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import './index.css'
|
||||
import App from './App.tsx'
|
||||
import { CssVarsProvider } from '@mui/joy'
|
||||
import theme from "./theme.ts";
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<StrictMode>
|
||||
<CssVarsProvider theme={theme}>
|
||||
<App />
|
||||
</StrictMode>,
|
||||
</CssVarsProvider>
|
||||
</StrictMode>
|
||||
)
|
||||
|
20
src/theme.ts
Normal file
20
src/theme.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { extendTheme } from '@mui/joy/styles';
|
||||
|
||||
|
||||
declare module '@mui/joy/styles' {
|
||||
// No custom tokens found, you can skip the theme augmentation.
|
||||
}
|
||||
|
||||
|
||||
const theme = extendTheme({
|
||||
"colorSchemes": {
|
||||
"light": {
|
||||
"palette": {}
|
||||
},
|
||||
"dark": {
|
||||
"palette": {}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
export default theme;
|
Reference in New Issue
Block a user