mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Merge branch 'CyferShepard:main' into main
This commit is contained in:
@@ -104,8 +104,8 @@ class EmbyAPI {
|
||||
|
||||
//Functions
|
||||
|
||||
async getUsers() {
|
||||
if (!this.configReady) {
|
||||
async getUsers(refreshConfig = false) {
|
||||
if (!this.configReady || refreshConfig) {
|
||||
const success = await this.#fetchConfig();
|
||||
if (!success) {
|
||||
return [];
|
||||
@@ -133,9 +133,9 @@ class EmbyAPI {
|
||||
}
|
||||
}
|
||||
|
||||
async getAdmins() {
|
||||
async getAdmins(refreshConfig = false) {
|
||||
try {
|
||||
const users = await this.getUsers();
|
||||
const users = await this.getUsers(refreshConfig);
|
||||
return users?.filter((user) => user.Policy.IsAdministrator) || [];
|
||||
} catch (error) {
|
||||
this.#errorHandler(error);
|
||||
|
||||
@@ -105,8 +105,8 @@ class JellyfinAPI {
|
||||
|
||||
//Functions
|
||||
|
||||
async getUsers() {
|
||||
if (!this.configReady) {
|
||||
async getUsers(refreshConfig = false) {
|
||||
if (!this.configReady || refreshConfig) {
|
||||
const success = await this.#fetchConfig();
|
||||
if (!success) {
|
||||
return [];
|
||||
@@ -133,9 +133,9 @@ class JellyfinAPI {
|
||||
}
|
||||
}
|
||||
|
||||
async getAdmins() {
|
||||
async getAdmins(refreshConfig = false) {
|
||||
try {
|
||||
const users = await this.getUsers();
|
||||
const users = await this.getUsers(refreshConfig);
|
||||
return users?.filter((user) => user.Policy.IsAdministrator) || [];
|
||||
} catch (error) {
|
||||
this.#errorHandler(error);
|
||||
|
||||
@@ -45,7 +45,7 @@ class TaskManager {
|
||||
if (code !== 0) {
|
||||
console.error(`Worker ${task.name} stopped with exit code ${code}`);
|
||||
}
|
||||
if (onExit) {
|
||||
if (code !== 0 && onExit) {
|
||||
onExit();
|
||||
}
|
||||
delete this.tasks[task.name];
|
||||
|
||||
@@ -50,7 +50,7 @@ const jf_library_items_mapping = (item) => ({
|
||||
? item.ImageBlurHashes.Primary[item.ImageTags["Primary"]]
|
||||
: null,
|
||||
archived: false,
|
||||
Genres: item.Genres && Array.isArray(item.Genres) ? JSON.stringify(filterInvalidGenres(item.Genres.map(titleCase))) : [],
|
||||
Genres: item.Genres && Array.isArray(item.Genres) ? JSON.stringify(item.Genres.map(titleCase)) : [],
|
||||
});
|
||||
|
||||
// Utility function to title-case a string
|
||||
@@ -62,53 +62,6 @@ function titleCase(str) {
|
||||
.join(" ");
|
||||
}
|
||||
|
||||
function filterInvalidGenres(genres) {
|
||||
const validGenres = [
|
||||
"Action",
|
||||
"Adventure",
|
||||
"Animated",
|
||||
"Biography",
|
||||
"Comedy",
|
||||
"Crime",
|
||||
"Dance",
|
||||
"Disaster",
|
||||
"Documentary",
|
||||
"Drama",
|
||||
"Erotic",
|
||||
"Family",
|
||||
"Fantasy",
|
||||
"Found Footage",
|
||||
"Historical",
|
||||
"Horror",
|
||||
"Independent",
|
||||
"Legal",
|
||||
"Live Action",
|
||||
"Martial Arts",
|
||||
"Musical",
|
||||
"Mystery",
|
||||
"Noir",
|
||||
"Performance",
|
||||
"Political",
|
||||
"Romance",
|
||||
"Satire",
|
||||
"Science Fiction",
|
||||
"Short",
|
||||
"Silent",
|
||||
"Slasher",
|
||||
"Sports",
|
||||
"Spy",
|
||||
"Superhero",
|
||||
"Supernatural",
|
||||
"Suspense",
|
||||
"Teen",
|
||||
"Thriller",
|
||||
"War",
|
||||
"Western",
|
||||
];
|
||||
|
||||
return genres.filter((genre) => validGenres.map((g) => g.toLowerCase()).includes(genre.toLowerCase()));
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
jf_library_items_columns,
|
||||
jf_library_items_mapping,
|
||||
|
||||
@@ -463,7 +463,24 @@ router.post("/setconfig", async (req, res) => {
|
||||
|
||||
settings.ServerID = systemInfo?.Id || null;
|
||||
|
||||
let query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
const query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
|
||||
await db.query(query, [settings]);
|
||||
}
|
||||
}
|
||||
|
||||
const admins = await API.getAdmins(true);
|
||||
const preferredAdmin = await new configClass().getPreferedAdmin();
|
||||
if (admins && admins.length > 0 && preferredAdmin && !admins.map((item) => item.Id).includes(preferredAdmin)) {
|
||||
const newAdmin = admins[0];
|
||||
const settingsjson = await db.query('SELECT settings FROM app_config where "ID"=1').then((res) => res.rows);
|
||||
|
||||
if (settingsjson.length > 0) {
|
||||
const settings = settingsjson[0].settings || {};
|
||||
|
||||
settings.preferred_admin = { userid: newAdmin.Id, username: newAdmin.Name };
|
||||
|
||||
const query = 'UPDATE app_config SET settings=$1 where "ID"=1';
|
||||
|
||||
await db.query(query, [settings]);
|
||||
}
|
||||
|
||||
@@ -20,16 +20,23 @@ router.post("/login", async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
if (!username || !password || password === CryptoJS.SHA3("").toString()) {
|
||||
const query = "SELECT * FROM app_config";
|
||||
const { rows: login } = await db.query(query);
|
||||
|
||||
if (
|
||||
(!username || !password || password === CryptoJS.SHA3("").toString()) &&
|
||||
login.length > 0 &&
|
||||
login[0].REQUIRE_LOGIN == true
|
||||
) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
|
||||
const query = 'SELECT * FROM app_config WHERE ("APP_USER" = $1 AND "APP_PASSWORD" = $2) OR "REQUIRE_LOGIN" = false';
|
||||
const values = [username, password];
|
||||
const { rows: login } = await db.query(query, values);
|
||||
const loginUser = login.filter(
|
||||
(user) => (user.APP_USER === username && user.APP_PASSWORD === password) || user.REQUIRE_LOGIN == false
|
||||
);
|
||||
|
||||
if (login.length > 0 || (username === JS_USER && password === CryptoJS.SHA3(JS_PASSWORD).toString())) {
|
||||
if (loginUser.length > 0 || (username === JS_USER && password === CryptoJS.SHA3(JS_PASSWORD).toString())) {
|
||||
const user = { id: 1, username: username };
|
||||
|
||||
jwt.sign({ user }, JWT_SECRET, (err, token) => {
|
||||
|
||||
@@ -148,7 +148,7 @@ router.get("/getSessions", async (req, res) => {
|
||||
|
||||
router.get("/getAdminUsers", async (req, res) => {
|
||||
try {
|
||||
const adminUser = await API.getAdmins();
|
||||
const adminUser = await API.getAdmins(true);
|
||||
res.send(adminUser);
|
||||
} catch (error) {
|
||||
res.status(503);
|
||||
|
||||
@@ -407,9 +407,9 @@ router.post("/getLibraryLastPlayed", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getViewsOverTime", async (req, res) => {
|
||||
router.get("/getViewsOverTime", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
const { days } = req.query;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
@@ -446,9 +446,9 @@ router.post("/getViewsOverTime", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getViewsByDays", async (req, res) => {
|
||||
router.get("/getViewsByDays", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
const { days } = req.query;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
@@ -481,9 +481,9 @@ router.post("/getViewsByDays", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getViewsByHour", async (req, res) => {
|
||||
router.get("/getViewsByHour", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
const { days } = req.query;
|
||||
let _days = days;
|
||||
if (days === undefined) {
|
||||
_days = 30;
|
||||
@@ -516,6 +516,41 @@ router.post("/getViewsByHour", async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getViewsByLibraryType", async (req, res) => {
|
||||
try {
|
||||
const { days = 30 } = req.query;
|
||||
|
||||
const { rows } = await db.query(`
|
||||
SELECT COALESCE(i."Type", 'Other') AS type, COUNT(a."NowPlayingItemId") AS count
|
||||
FROM jf_playback_activity a LEFT JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
WHERE a."ActivityDateInserted" BETWEEN NOW() - CAST($1 || ' days' as INTERVAL) AND NOW()
|
||||
GROUP BY i."Type"
|
||||
`, [days]);
|
||||
|
||||
const supportedTypes = new Set(["Audio", "Movie", "Series", "Other"]);
|
||||
/** @type {Map<string, number>} */
|
||||
const reorganizedData = new Map();
|
||||
|
||||
rows.forEach((item) => {
|
||||
const { type, count } = item;
|
||||
|
||||
if (!supportedTypes.has(type)) return;
|
||||
reorganizedData.set(type, count);
|
||||
});
|
||||
|
||||
supportedTypes.forEach((type) => {
|
||||
if (reorganizedData.has(type)) return;
|
||||
reorganizedData.set(type, 0);
|
||||
});
|
||||
|
||||
res.send(Object.fromEntries(reorganizedData));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.status(503);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/getGenreUserStats", async (req, res) => {
|
||||
try {
|
||||
const { size = 50, page = 1, userid } = req.query;
|
||||
|
||||
@@ -395,12 +395,13 @@ async function removeOrphanedData() {
|
||||
syncTask.loggedData.push({ color: "yellow", Message: "Removing Orphaned FileInfo/Episode/Season Records" });
|
||||
|
||||
await db.query("CALL jd_remove_orphaned_data()");
|
||||
const archived_items = await db
|
||||
.query(`select "Id" from jf_library_items where archived=true and "Type"='Series'`)
|
||||
.then((res) => res.rows.map((row) => row.Id));
|
||||
const archived_seasons = await db
|
||||
.query(`select "Id" from jf_library_seasons where archived=true`)
|
||||
.then((res) => res.rows.map((row) => row.Id));
|
||||
const archived_items_query = `select i."Id" from jf_library_items i join jf_library_seasons s on s."SeriesId"=i."Id" and s.archived=false where i.archived=true and i."Type"='Series'
|
||||
union
|
||||
select i."Id" from jf_library_items i join jf_library_episodes e on e."SeriesId"=i."Id" and e.archived=false where i.archived=true and i."Type"='Series'
|
||||
`;
|
||||
const archived_items = await db.query(archived_items_query).then((res) => res.rows.map((row) => row.Id));
|
||||
const archived_seasons_query = `select s."Id" from jf_library_seasons s join jf_library_episodes e on e."SeasonId"=s."Id" and e.archived=false where s.archived=true`;
|
||||
const archived_seasons = await db.query(archived_seasons_query).then((res) => res.rows.map((row) => row.Id));
|
||||
if (!(await _sync.updateSingleFieldOnDB("jf_library_seasons", archived_items, "archived", true, "SeriesId"))) {
|
||||
syncTask.loggedData.push({ color: "red", Message: "Error archiving library seasons" });
|
||||
await logging.updateLog(syncTask.uuid, syncTask.loggedData, taskstate.FAILED);
|
||||
|
||||
@@ -3504,7 +3504,7 @@
|
||||
}
|
||||
},
|
||||
"/stats/getViewsOverTime": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stats"
|
||||
],
|
||||
@@ -3526,16 +3526,9 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "days",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3558,7 +3551,7 @@
|
||||
}
|
||||
},
|
||||
"/stats/getViewsByDays": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stats"
|
||||
],
|
||||
@@ -3580,16 +3573,9 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "days",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -3612,7 +3598,7 @@
|
||||
}
|
||||
},
|
||||
"/stats/getViewsByHour": {
|
||||
"post": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stats"
|
||||
],
|
||||
@@ -3634,16 +3620,56 @@
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "body",
|
||||
"in": "body",
|
||||
"schema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"days": {
|
||||
"example": "any"
|
||||
}
|
||||
}
|
||||
}
|
||||
"name": "days",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK"
|
||||
},
|
||||
"401": {
|
||||
"description": "Unauthorized"
|
||||
},
|
||||
"403": {
|
||||
"description": "Forbidden"
|
||||
},
|
||||
"404": {
|
||||
"description": "Not Found"
|
||||
},
|
||||
"503": {
|
||||
"description": "Service Unavailable"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/stats/getViewsByLibraryType": {
|
||||
"get": {
|
||||
"tags": [
|
||||
"Stats"
|
||||
],
|
||||
"description": "",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "authorization",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "x-api-token",
|
||||
"in": "header",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "req",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"name": "days",
|
||||
"in": "query",
|
||||
"type": "string"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
|
||||
@@ -42,8 +42,9 @@ async function runBackupTask(triggerType = triggertype.Automatic) {
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.on("message", (message) => {
|
||||
parentPort.on("message", async (message) => {
|
||||
if (message.command === "start") {
|
||||
runBackupTask(message.triggertype);
|
||||
await runBackupTask(message.triggertype);
|
||||
process.exit(0); // Exit the worker after the task is done
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,8 +28,9 @@ async function runFullSyncTask(triggerType = triggertype.Automatic) {
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.on("message", (message) => {
|
||||
parentPort.on("message", async (message) => {
|
||||
if (message.command === "start") {
|
||||
runFullSyncTask(message.triggertype);
|
||||
await runFullSyncTask(message.triggertype);
|
||||
process.exit(0); // Exit the worker after the task is done
|
||||
}
|
||||
});
|
||||
|
||||
@@ -27,8 +27,9 @@ async function runPlaybackReportingPluginSyncTask() {
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.on("message", (message) => {
|
||||
parentPort.on("message", async (message) => {
|
||||
if (message.command === "start") {
|
||||
runPlaybackReportingPluginSyncTask();
|
||||
await runPlaybackReportingPluginSyncTask();
|
||||
process.exit(0); // Exit the worker after the task is done
|
||||
}
|
||||
});
|
||||
|
||||
@@ -28,8 +28,9 @@ async function runPartialSyncTask(triggerType = triggertype.Automatic) {
|
||||
}
|
||||
}
|
||||
|
||||
parentPort.on("message", (message) => {
|
||||
parentPort.on("message", async (message) => {
|
||||
if (message.command === "start") {
|
||||
runPartialSyncTask(message.triggertype);
|
||||
await runPartialSyncTask(message.triggertype);
|
||||
process.exit(0); // Exit the worker after the task is done
|
||||
}
|
||||
});
|
||||
|
||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.6",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "jfstat",
|
||||
"version": "1.1.5",
|
||||
"version": "1.1.6",
|
||||
"private": true,
|
||||
"main": "src/index.jsx",
|
||||
"scripts": {
|
||||
|
||||
@@ -23,8 +23,6 @@ function LibraryItems(props) {
|
||||
localStorage.getItem("PREF_sortAsc") != undefined ? localStorage.getItem("PREF_sortAsc") == "true" : true
|
||||
);
|
||||
|
||||
console.log(sortOrder);
|
||||
|
||||
const archive = {
|
||||
all: "all",
|
||||
archived: "true",
|
||||
@@ -212,7 +210,11 @@ function LibraryItems(props) {
|
||||
}
|
||||
})
|
||||
.map((item) => (
|
||||
<MoreItemCards data={item} base_url={config.settings?.EXTERNAL_URL ?? config.hostUrl} key={item.Id + item.SeasonNumber + item.EpisodeNumber} />
|
||||
<MoreItemCards
|
||||
data={item}
|
||||
base_url={config.settings?.EXTERNAL_URL ?? config.hostUrl}
|
||||
key={item.Id + item.SeasonNumber + item.EpisodeNumber}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,6 +44,20 @@ export default function SettingsConfig() {
|
||||
set12hr(Boolean(storage_12hr));
|
||||
}
|
||||
|
||||
const fetchAdmins = async () => {
|
||||
try {
|
||||
const adminData = await axios.get(`/proxy/getAdminUsers`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setAdmins(adminData.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
Config.getConfig()
|
||||
.then((config) => {
|
||||
@@ -59,20 +73,6 @@ export default function SettingsConfig() {
|
||||
setsubmissionMessage("Error Retrieving Configuration. Unable to contact Backend Server");
|
||||
});
|
||||
|
||||
const fetchAdmins = async () => {
|
||||
try {
|
||||
const adminData = await axios.get(`/proxy/getAdminUsers`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setAdmins(adminData.data);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAdmins();
|
||||
}, [token]);
|
||||
|
||||
@@ -91,6 +91,8 @@ export default function SettingsConfig() {
|
||||
console.log("Config updated successfully:", response.data);
|
||||
setisSubmitted("Success");
|
||||
setsubmissionMessage("Successfully updated configuration");
|
||||
Config.setConfig();
|
||||
fetchAdmins();
|
||||
})
|
||||
.catch((error) => {
|
||||
let errorMessage = error.response.data.errorMessage;
|
||||
@@ -98,7 +100,6 @@ export default function SettingsConfig() {
|
||||
setisSubmitted("Failed");
|
||||
setsubmissionMessage(`Error Updating Configuration: ${errorMessage}`);
|
||||
});
|
||||
Config.setConfig();
|
||||
}
|
||||
|
||||
async function handleFormSubmitExternal(event) {
|
||||
@@ -233,9 +234,13 @@ export default function SettingsConfig() {
|
||||
</Form.Group>
|
||||
{isSubmitted !== "" ? (
|
||||
isSubmitted === "Failed" ? (
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="danger">{submissionMessage}</Alert>
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="danger">
|
||||
{submissionMessage}
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="success">{submissionMessage}</Alert>
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="success">
|
||||
{submissionMessage}
|
||||
</Alert>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
@@ -265,9 +270,13 @@ export default function SettingsConfig() {
|
||||
|
||||
{isSubmittedExternal !== "" ? (
|
||||
isSubmittedExternal === "Failed" ? (
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="danger">{submissionMessageExternal}</Alert>
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="danger">
|
||||
{submissionMessageExternal}
|
||||
</Alert>
|
||||
) : (
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="success">{submissionMessageExternal}</Alert>
|
||||
<Alert bg="dark" data-bs-theme="dark" variant="success">
|
||||
{submissionMessageExternal}
|
||||
</Alert>
|
||||
)
|
||||
) : (
|
||||
<></>
|
||||
|
||||
@@ -10,12 +10,26 @@ import i18next from "i18next";
|
||||
|
||||
function GenreStatCard(props) {
|
||||
const [maxRange, setMaxRange] = useState(100);
|
||||
const [data, setData] = useState(props.data);
|
||||
|
||||
useEffect(() => {
|
||||
const maxDuration = props.data.reduce((max, item) => {
|
||||
return Math.max(max, parseFloat((props.dataKey == "duration" ? item.duration : item.plays) || 0));
|
||||
}, 0);
|
||||
setMaxRange(maxDuration);
|
||||
|
||||
let sorted = [...props.data]
|
||||
.sort((a, b) => {
|
||||
const valueA = parseFloat(props.dataKey === "duration" ? a.duration : a.plays) || 0;
|
||||
const valueB = parseFloat(props.dataKey === "duration" ? b.duration : b.plays) || 0;
|
||||
return valueB - valueA; // Descending order
|
||||
})
|
||||
.slice(0, 15); // Take only the top 10
|
||||
|
||||
// Sort top 10 genres alphabetically
|
||||
sorted = sorted.sort((a, b) => a.genre.localeCompare(b.genre));
|
||||
|
||||
setData(sorted);
|
||||
}, [props.data, props.dataKey]);
|
||||
|
||||
const CustomTooltip = ({ active, payload }) => {
|
||||
@@ -67,7 +81,7 @@ function GenreStatCard(props) {
|
||||
</h1>
|
||||
<ErrorBoundary>
|
||||
<ResponsiveContainer width="100%" height="100%">
|
||||
<RadarChart cx="50%" cy="50%" outerRadius="80%" data={props.data}>
|
||||
<RadarChart cx="50%" cy="50%" outerRadius="80%" data={data}>
|
||||
<PolarGrid gridType="circle" />
|
||||
<PolarAngleAxis dataKey="genre" />
|
||||
<PolarRadiusAxis domain={[0, maxRange]} tick={false} axisLine={false} />
|
||||
|
||||
@@ -17,12 +17,11 @@ function DailyPlayStats(props) {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsOverTime`;
|
||||
const url = `/stats/getViewsOverTime?days=${props.days}`;
|
||||
|
||||
axios
|
||||
.post(
|
||||
.get(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
||||
@@ -13,12 +13,11 @@ function PlayStatsByDay(props) {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsByDays`;
|
||||
const url = `/stats/getViewsByDays?days=${props.days}`;
|
||||
|
||||
axios
|
||||
.post(
|
||||
.get(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
||||
@@ -12,12 +12,11 @@ function PlayStatsByHour(props) {
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsByHour`;
|
||||
const url = `/stats/getViewsByHour?days=${props.days}`;
|
||||
|
||||
axios
|
||||
.post(
|
||||
.get(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
|
||||
Reference in New Issue
Block a user