mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Recharts Implementation
Removed nvio and added recharts to fix graph falling below 0 Other Fixes
This commit is contained in:
@@ -280,6 +280,7 @@ router.post("/getLibraryLastPlayed", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.post("/getViewsOverTime", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
@@ -287,32 +288,36 @@ router.post("/getViewsOverTime", async (req, res) => {
|
||||
if (days=== undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
const { rows:stats } = await db.query(
|
||||
`select * from fs_watch_stats_over_time('${_days}')`
|
||||
);
|
||||
|
||||
const { rows:libraries } = await db.query(
|
||||
`select distinct "Id","Name" from jf_libraries`
|
||||
);
|
||||
|
||||
|
||||
const reorganizedData = {};
|
||||
|
||||
rows.forEach((item) => {
|
||||
const id = item.Library;
|
||||
stats.forEach((item) => {
|
||||
const library = item.Library;
|
||||
const count = item.Count;
|
||||
const date = new Date(item.Date).toLocaleDateString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
month: 'short',
|
||||
day: '2-digit'
|
||||
});
|
||||
|
||||
if (!reorganizedData[id]) {
|
||||
reorganizedData[id] = {
|
||||
id,
|
||||
data: []
|
||||
|
||||
|
||||
if (!reorganizedData[date]) {
|
||||
reorganizedData[date] = {
|
||||
Key:date
|
||||
};
|
||||
}
|
||||
|
||||
reorganizedData[id].data.push({ x: date, y: count });
|
||||
reorganizedData[date]= { ...reorganizedData[date], [library]: count};
|
||||
});
|
||||
const finalData = Object.values(reorganizedData);
|
||||
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
|
||||
res.send(finalData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -327,29 +332,32 @@ router.post("/getViewsByDays", async (req, res) => {
|
||||
if (days=== undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
const { rows:stats } = await db.query(
|
||||
`select * from fs_watch_stats_popular_days_of_week('${_days}')`
|
||||
);
|
||||
|
||||
const { rows:libraries } = await db.query(
|
||||
`select distinct "Id","Name" from jf_libraries`
|
||||
);
|
||||
|
||||
|
||||
const reorganizedData = {};
|
||||
|
||||
rows.forEach((item) => {
|
||||
|
||||
const id = item.Library;
|
||||
stats.forEach((item) => {
|
||||
const library = item.Library;
|
||||
const count = item.Count;
|
||||
const day = item.Day;
|
||||
|
||||
if (!reorganizedData[id]) {
|
||||
reorganizedData[id] = {
|
||||
id,
|
||||
data: []
|
||||
|
||||
|
||||
if (!reorganizedData[day]) {
|
||||
reorganizedData[day] = {
|
||||
Key:day
|
||||
};
|
||||
}
|
||||
|
||||
reorganizedData[id].data.push({ x: day, y: count });
|
||||
|
||||
reorganizedData[day]= { ...reorganizedData[day], [library]: count};
|
||||
});
|
||||
const finalData = Object.values(reorganizedData);
|
||||
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
|
||||
res.send(finalData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -365,29 +373,32 @@ router.post("/getViewsByHour", async (req, res) => {
|
||||
if (days=== undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
const { rows:stats } = await db.query(
|
||||
`select * from fs_watch_stats_popular_hour_of_day('${_days}')`
|
||||
);
|
||||
|
||||
const { rows:libraries } = await db.query(
|
||||
`select distinct "Id","Name" from jf_libraries`
|
||||
);
|
||||
|
||||
|
||||
const reorganizedData = {};
|
||||
|
||||
rows.forEach((item) => {
|
||||
|
||||
const id = item.Library;
|
||||
stats.forEach((item) => {
|
||||
const library = item.Library;
|
||||
const count = item.Count;
|
||||
const hour = item.Hour;
|
||||
|
||||
if (!reorganizedData[id]) {
|
||||
reorganizedData[id] = {
|
||||
id,
|
||||
data: []
|
||||
|
||||
|
||||
if (!reorganizedData[hour]) {
|
||||
reorganizedData[hour] = {
|
||||
Key:hour
|
||||
};
|
||||
}
|
||||
|
||||
reorganizedData[id].data.push({ x: hour, y: count });
|
||||
|
||||
reorganizedData[hour]= { ...reorganizedData[hour], [library]: count};
|
||||
});
|
||||
const finalData = Object.values(reorganizedData);
|
||||
const finalData = {libraries:libraries,stats:Object.values(reorganizedData)};
|
||||
res.send(finalData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
@@ -397,4 +408,5 @@ const finalData = Object.values(reorganizedData);
|
||||
|
||||
|
||||
|
||||
|
||||
module.exports = router;
|
||||
|
||||
274
package-lock.json
generated
274
package-lock.json
generated
@@ -37,6 +37,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^2.5.0",
|
||||
"remixicon-react": "^1.0.0",
|
||||
"sequelize": "^6.29.0",
|
||||
"web-vitals": "^2.1.4",
|
||||
@@ -4991,6 +4992,60 @@
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/d3-array": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.0.4.tgz",
|
||||
"integrity": "sha512-nwvEkG9vYOc0Ic7G7kwgviY4AQlTfYGIZ0fqB7CQHXGyYM6nO7kJh5EguSNA3jfh4rq7Sb7eMVq8isuvg2/miQ=="
|
||||
},
|
||||
"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/debug": {
|
||||
"version": "4.1.7",
|
||||
"license": "MIT",
|
||||
@@ -7538,6 +7593,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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/css-what": {
|
||||
"version": "6.1.0",
|
||||
"license": "BSD-2-Clause",
|
||||
@@ -7737,6 +7797,14 @@
|
||||
"delaunator": "4"
|
||||
}
|
||||
},
|
||||
"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": "1.4.5",
|
||||
"resolved": "https://registry.npmjs.org/d3-format/-/d3-format-1.4.5.tgz",
|
||||
@@ -7824,6 +7892,14 @@
|
||||
"d3-time": "1 - 2"
|
||||
}
|
||||
},
|
||||
"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",
|
||||
"license": "BSD-2-Clause"
|
||||
@@ -7874,6 +7950,11 @@
|
||||
"version": "10.4.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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/dedent": {
|
||||
"version": "0.7.0",
|
||||
"license": "MIT"
|
||||
@@ -9298,6 +9379,11 @@
|
||||
"version": "3.1.3",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-equals": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-4.0.3.tgz",
|
||||
"integrity": "sha512-G3BSX9cfKttjr+2o1O22tYMLq0DPluZnYtq1rXumE1SpL/F/SLIfHx08WYQoWSIpeMYf8sRbJ8++71+v6Pnxfg=="
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.2.12",
|
||||
"license": "MIT",
|
||||
@@ -16201,6 +16287,18 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"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-router": {
|
||||
"version": "6.8.1",
|
||||
"license": "MIT",
|
||||
@@ -16300,6 +16398,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-smooth": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-2.0.2.tgz",
|
||||
"integrity": "sha512-pgqSp1q8rAGtF1bXQE0m3CHGLNfZZh5oA5o1tsPLXRHnKtkujMIJ8Ws5nO1mTySZf1c4vgwlEk+pHi3Ln6eYLw==",
|
||||
"dependencies": {
|
||||
"fast-equals": "^4.0.3",
|
||||
"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",
|
||||
"license": "BSD-3-Clause",
|
||||
@@ -16343,6 +16478,43 @@
|
||||
"node": ">=8.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.5.0.tgz",
|
||||
"integrity": "sha512-0EQYz3iA18r1Uq8VqGZ4dABW52AKBnio37kJgnztIqprELJXpOEsa0SzkqU1vjAhpCXCv52Dx1hiL9119xsqsQ==",
|
||||
"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/recharts/node_modules/react-is": {
|
||||
"version": "16.13.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
|
||||
},
|
||||
"node_modules/rechoir": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz",
|
||||
@@ -16375,6 +16547,20 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"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/regenerate": {
|
||||
"version": "1.4.2",
|
||||
"license": "MIT"
|
||||
@@ -18183,6 +18369,94 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor": {
|
||||
"version": "36.6.8",
|
||||
"resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.6.8.tgz",
|
||||
"integrity": "sha512-H3kyQ+2zgjMPvbPqAl7Vwm2FD5dU7/4bCTQakFQnpIsfDljeOMDojRsrmJfwh4oAlNnWhpAf+mbAoLh8u7dwyQ==",
|
||||
"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/victory-vendor/node_modules/d3-array": {
|
||||
"version": "3.2.3",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.3.tgz",
|
||||
"integrity": "sha512-JRHwbQQ84XuAESWhvIPaUV4/1UYTBOLiOPGWqgFDHZS1D5QN9c57FbH3QpEnQMYiOXNzKUQyGTZf+EVO7RT5TQ==",
|
||||
"dependencies": {
|
||||
"internmap": "1 - 2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/victory-vendor/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/victory-vendor/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/victory-vendor/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/victory-vendor/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/victory-vendor/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/w3c-hr-time": {
|
||||
"version": "1.0.2",
|
||||
"license": "MIT",
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
"react-dom": "^18.2.0",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-scripts": "5.0.1",
|
||||
"recharts": "^2.5.0",
|
||||
"remixicon-react": "^1.0.0",
|
||||
"sequelize": "^6.29.0",
|
||||
"web-vitals": "^2.1.4",
|
||||
|
||||
@@ -41,7 +41,7 @@ function Sessions() {
|
||||
|
||||
const intervalId = setInterval(fetchData, 1000);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [base_url]);
|
||||
}, [data,base_url]);
|
||||
|
||||
if (!data) {
|
||||
return <Loading />;
|
||||
|
||||
86
src/pages/components/statistics/chart.js
Normal file
86
src/pages/components/statistics/chart.js
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { ResponsiveContainer, AreaChart, Area, XAxis, YAxis, Tooltip, Legend } from 'recharts';
|
||||
|
||||
function Chart({ stats, libraries }) {
|
||||
const wordToColor = (word) => {
|
||||
let number = 0;
|
||||
for (let i = 0; i < word.length; i++) {
|
||||
const charCode = word.charCodeAt(i);
|
||||
const letterValue = charCode - 65;
|
||||
number += letterValue;
|
||||
}
|
||||
|
||||
let hash = number;
|
||||
const str = number.toString();
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = ((hash << 5) + hash) + str.charCodeAt(i);
|
||||
}
|
||||
// Map the hash to an RGB color, with each channel restricted to a certain range
|
||||
const red = (hash % 256) * 4;
|
||||
const green = ((hash >> 8) % 256)*2;
|
||||
const blue = ((hash >> 16) % 256);
|
||||
// Return an RGB color string
|
||||
return `rgb(${red}, ${green}, ${blue})`;
|
||||
};
|
||||
|
||||
const CustomTooltip = ({ payload, label, active }) => {
|
||||
if (active) {
|
||||
return (
|
||||
<div style={{ backgroundColor: "rgba(0,0,0,0.8)", color: "white" }} className="p-2 rounded-2 border-0">
|
||||
<p className='text-center fs-5'>{label}</p>
|
||||
{libraries.map((library, index) => (
|
||||
<p key={library.Id} style={{ color: `${wordToColor(library.Name)}` }}>{`${library.Name} : ${payload[index].value} Views`}</p>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
|
||||
|
||||
const getMaxValue = () => {
|
||||
let max = 0;
|
||||
if(stats)
|
||||
{
|
||||
stats.forEach(datum => {
|
||||
Object.keys(datum).forEach(key => {
|
||||
if (key !== "Key") {
|
||||
max = Math.max(max, parseInt(datum[key]));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
return max;
|
||||
}
|
||||
|
||||
const max = getMaxValue()+10;
|
||||
|
||||
|
||||
return (
|
||||
<ResponsiveContainer width="100%">
|
||||
<AreaChart data={stats} margin={{ top: 10, right: 30, left: 0, bottom: 0 }}>
|
||||
<defs>
|
||||
{libraries.map((library) => (
|
||||
<linearGradient key={library.Id} id={library.Id} x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="5%" stopColor={wordToColor(library.Name)} stopOpacity={0.8}/>
|
||||
<stop offset="95%" stopColor={wordToColor(library.Name)} stopOpacity={0}/>
|
||||
</linearGradient>
|
||||
))}
|
||||
</defs>
|
||||
<XAxis dataKey="Key" interval={0} angle={-60} textAnchor="end" height={100}/>
|
||||
<YAxis domain={[0, max]} />
|
||||
<Tooltip content={<CustomTooltip />} />
|
||||
<Legend verticalAlign="bottom" />
|
||||
{libraries.map((library) => (
|
||||
<Area key={library.Id} type="monotone" dataKey={library.Name} stroke={wordToColor(library.Name)} fillOpacity={1} fill={"url(#" + library.Id + ")"} />
|
||||
))}
|
||||
</AreaChart>
|
||||
</ResponsiveContainer>
|
||||
);
|
||||
}
|
||||
|
||||
export default Chart;
|
||||
@@ -1,14 +1,20 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
|
||||
import Chart from "./chart";
|
||||
|
||||
import "../../css/stats.css";
|
||||
|
||||
function DailyPlayStats(props) {
|
||||
const [data, setData] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
const [stats, setStats] = useState();
|
||||
const [libraries, setLibraries] = useState();
|
||||
const [days, setDays] = useState(15);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsOverTime`;
|
||||
@@ -25,14 +31,15 @@ function DailyPlayStats(props) {
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setStats(data.data.stats);
|
||||
setLibraries(data.data.libraries);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
fetchLibraries();
|
||||
}
|
||||
if (days !== props.days) {
|
||||
@@ -42,13 +49,13 @@ function DailyPlayStats(props) {
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days, props.days, token]);
|
||||
}, [stats,libraries, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
if (stats.length === 0) {
|
||||
return (
|
||||
<div className="main-widget">
|
||||
<h1>Daily Play Count Per Library - {days} Days</h1>
|
||||
@@ -57,101 +64,12 @@ function DailyPlayStats(props) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-widget">
|
||||
<h1>Daily Play Count Per Library - Last {days} Days</h1>
|
||||
|
||||
<div className="graph">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: 0,
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false,
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="cardinal"
|
||||
enableArea={true}
|
||||
theme={{
|
||||
axis: {
|
||||
ticks: {
|
||||
line: {
|
||||
stroke: "white",
|
||||
},
|
||||
text: {
|
||||
fill: "white",
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: "rgba(255,255,255,0.2)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: "1 40",
|
||||
},
|
||||
},
|
||||
}}
|
||||
axisBottom={{
|
||||
orient: "bottom",
|
||||
tickSize: 5,
|
||||
tickPadding: 10,
|
||||
tickRotation: -45,
|
||||
legend: "Days",
|
||||
legendOffset: 36,
|
||||
legendPosition: "middle",
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: "left",
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: "Plays",
|
||||
legendOffset: -40,
|
||||
legendPosition: "middle",
|
||||
itemTextColor: "#fff",
|
||||
theme: "white",
|
||||
}}
|
||||
colors={{ scheme: "category10" }}
|
||||
pointSize={10}
|
||||
pointColor={{ theme: "background" }}
|
||||
pointBorderWidth={2}
|
||||
pointBorderColor={{ from: "serieColor" }}
|
||||
pointLabelYOffset={-12}
|
||||
legends={[
|
||||
{
|
||||
itemTextColor: "#fff",
|
||||
anchor: "bottom",
|
||||
direction: "row",
|
||||
justify: false,
|
||||
translateX: 0,
|
||||
translateY: 100,
|
||||
itemsSpacing: 0,
|
||||
itemDirection: "left-to-right",
|
||||
itemWidth: 100,
|
||||
itemHeight: 20,
|
||||
itemOpacity: 0.75,
|
||||
symbolSize: 12,
|
||||
symbolShape: "circle",
|
||||
symbolBorderColor: "rgba(0, 0, 0, .5)",
|
||||
effects: [
|
||||
{
|
||||
on: "hover",
|
||||
style: {
|
||||
itemBackground: "rgba(0, 0, 0, .03)",
|
||||
itemOpacity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Chart libraries={libraries} stats={stats} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
import Chart from "./chart";
|
||||
|
||||
import "../../css/stats.css";
|
||||
|
||||
function PlayStatsByDay(props) {
|
||||
const [data, setData] = useState();
|
||||
const [stats, setStats] = useState();
|
||||
const [libraries, setLibraries] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
@@ -25,14 +26,15 @@ function PlayStatsByDay(props) {
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setStats(data.data.stats);
|
||||
setLibraries(data.data.libraries);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
fetchLibraries();
|
||||
}
|
||||
if (days !== props.days) {
|
||||
@@ -42,13 +44,13 @@ function PlayStatsByDay(props) {
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days, props.days, token]);
|
||||
}, [stats, libraries, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
if (stats.length === 0) {
|
||||
return (
|
||||
<div className="statistics-widget small">
|
||||
<h1>Play Count By Day - Last {days} Days</h1>
|
||||
@@ -62,95 +64,7 @@ function PlayStatsByDay(props) {
|
||||
<div className="statistics-widget">
|
||||
<h1>Play Count By Day - Last {days} Days</h1>
|
||||
<div className="graph small">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: 0,
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false,
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="cardinal"
|
||||
enableArea={true}
|
||||
theme={{
|
||||
axis: {
|
||||
ticks: {
|
||||
line: {
|
||||
stroke: "white",
|
||||
},
|
||||
text: {
|
||||
fill: "white",
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: "rgba(255,255,255,0.2)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: "1 40",
|
||||
},
|
||||
},
|
||||
}}
|
||||
axisBottom={{
|
||||
orient: "bottom",
|
||||
tickSize: 5,
|
||||
tickPadding: 10,
|
||||
tickRotation: 0,
|
||||
legend: "Days",
|
||||
legendOffset: 36,
|
||||
legendPosition: "middle",
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: "left",
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: "Plays",
|
||||
legendOffset: -40,
|
||||
legendPosition: "middle",
|
||||
itemTextColor: "#fff",
|
||||
theme: "white",
|
||||
}}
|
||||
colors={{ scheme: "category10" }}
|
||||
pointSize={5}
|
||||
pointColor={{ theme: "background" }}
|
||||
pointBorderWidth={2}
|
||||
pointBorderColor={{ from: "serieColor" }}
|
||||
pointLabelYOffset={-12}
|
||||
legends={[
|
||||
{
|
||||
itemTextColor: "#fff",
|
||||
anchor: "bottom",
|
||||
direction: "row",
|
||||
justify: false,
|
||||
translateX: 0,
|
||||
translateY: 100,
|
||||
itemsSpacing: 0,
|
||||
itemDirection: "left-to-right",
|
||||
itemWidth: 100,
|
||||
itemHeight: 20,
|
||||
itemOpacity: 0.75,
|
||||
symbolSize: 12,
|
||||
symbolShape: "circle",
|
||||
symbolBorderColor: "rgba(0, 0, 0, .5)",
|
||||
effects: [
|
||||
{
|
||||
on: "hover",
|
||||
style: {
|
||||
itemBackground: "rgba(0, 0, 0, .03)",
|
||||
itemOpacity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Chart libraries={libraries} stats={stats} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
|
||||
import Chart from "./chart";
|
||||
import "../../css/stats.css";
|
||||
|
||||
function PlayStatsByHour(props) {
|
||||
const [data, setData] = useState();
|
||||
const [stats, setStats] = useState();
|
||||
const [libraries, setLibraries] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
@@ -25,14 +25,15 @@ function PlayStatsByHour(props) {
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
setStats(data.data.stats);
|
||||
setLibraries(data.data.libraries);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
fetchLibraries();
|
||||
}
|
||||
if (days !== props.days) {
|
||||
@@ -42,13 +43,13 @@ function PlayStatsByHour(props) {
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days, props.days, token]);
|
||||
}, [stats, libraries, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
if (!stats) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
if (stats.length === 0) {
|
||||
return (
|
||||
<div className="statistics-widget small">
|
||||
<h1>Play Count By Hour - Last {days} Days</h1>
|
||||
@@ -58,101 +59,12 @@ function PlayStatsByHour(props) {
|
||||
);
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
return (
|
||||
<div className="statistics-widget">
|
||||
<h1>Play Count By Hour - Last {days} Days</h1>
|
||||
<div className="graph small">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 40, bottom: 100, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: 0,
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false,
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="cardinal"
|
||||
enableArea={true}
|
||||
theme={{
|
||||
axis: {
|
||||
ticks: {
|
||||
line: {
|
||||
stroke: "white",
|
||||
},
|
||||
text: {
|
||||
fill: "white",
|
||||
},
|
||||
},
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: "rgba(255,255,255,0.2)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: "1 40",
|
||||
},
|
||||
},
|
||||
}}
|
||||
axisBottom={{
|
||||
orient: "bottom",
|
||||
tickSize: 5,
|
||||
tickPadding: 10,
|
||||
tickRotation: 0,
|
||||
legend: "Days",
|
||||
legendOffset: 36,
|
||||
legendPosition: "middle",
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: "left",
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: "Plays",
|
||||
legendOffset: -40,
|
||||
legendPosition: "middle",
|
||||
itemTextColor: "#fff",
|
||||
theme: "white",
|
||||
}}
|
||||
colors={{ scheme: "category10" }}
|
||||
pointSize={5}
|
||||
pointColor={{ theme: "background" }}
|
||||
pointBorderWidth={2}
|
||||
pointBorderColor={{ from: "serieColor" }}
|
||||
pointLabelYOffset={-12}
|
||||
legends={[
|
||||
{
|
||||
itemTextColor: "#fff",
|
||||
anchor: "bottom",
|
||||
direction: "row",
|
||||
justify: false,
|
||||
translateX: 0,
|
||||
translateY: 100,
|
||||
itemsSpacing: 0,
|
||||
itemDirection: "left-to-right",
|
||||
itemWidth: 100,
|
||||
itemHeight: 20,
|
||||
itemOpacity: 0.75,
|
||||
symbolSize: 12,
|
||||
symbolShape: "circle",
|
||||
symbolBorderColor: "rgba(0, 0, 0, .5)",
|
||||
effects: [
|
||||
{
|
||||
on: "hover",
|
||||
style: {
|
||||
itemBackground: "rgba(0, 0, 0, .03)",
|
||||
itemOpacity: 1,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Chart libraries={libraries} stats={stats} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -8,7 +8,8 @@ import './css/library/libraries.css';
|
||||
|
||||
// import LibraryOverView from './components/libraryOverview';
|
||||
// import HomeStatisticCards from './components/HomeStatisticCards';
|
||||
import Sessions from './components/sessions/sessions';
|
||||
// import Sessions from './components/sessions/sessions';
|
||||
import DailyPlayStats from './components/statistics/daily-play-count';
|
||||
|
||||
|
||||
|
||||
@@ -51,7 +52,7 @@ function Testing() {
|
||||
return (
|
||||
<div className='Activity'>
|
||||
|
||||
<Sessions/>
|
||||
<DailyPlayStats/>
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user