generalized Genre stat card

Added Genre stat card to libary views #292
bug fixes on Genre chart view
This commit is contained in:
CyferShepard
2025-03-30 17:46:07 +02:00
parent 607b21c542
commit a8e4c9f00b
6 changed files with 153 additions and 8 deletions

View File

@@ -516,7 +516,7 @@ router.post("/getViewsByHour", async (req, res) => {
}
});
router.get("/getGenreStats", async (req, res) => {
router.get("/getGenreUserStats", async (req, res) => {
try {
const { size = 50, page = 1, userid } = req.query;
@@ -579,6 +579,69 @@ router.get("/getGenreStats", async (req, res) => {
}
});
router.get("/getGenreLibraryStats", async (req, res) => {
try {
const { size = 50, page = 1, libraryid } = req.query;
if (libraryid === undefined) {
res.status(400);
res.send("No Library ID provided");
return;
}
const values = [];
const query = {
select: ["COALESCE(g.genre, 'No Genre') AS genre", `SUM(a."PlaybackDuration") AS duration`, "COUNT(*) AS plays"],
table: "jf_playback_activity_with_metadata",
alias: "a",
joins: [
{
type: "inner",
table: "jf_library_items",
alias: "i",
conditions: [{ first: "a.NowPlayingItemId", operator: "=", second: "i.Id" }],
},
{
type: "left",
table: `
LATERAL (
SELECT
jsonb_array_elements_text(
CASE
WHEN jsonb_array_length(COALESCE(i."Genres", '[]'::jsonb)) = 0 THEN '["No Genre"]'::jsonb
ELSE i."Genres"
END
) AS genre
)
`,
alias: "g",
conditions: [{ first: 1, operator: "=", value: 1, wrap: false }],
},
],
where: [[{ column: "a.ParentId", operator: "=", value: `$${values.length + 1}` }]],
group_by: [`COALESCE(g.genre, 'No Genre')`],
order_by: "genre",
pageNumber: page,
pageSize: size,
};
values.push(libraryid);
query.values = values;
const result = await dbHelper.query(query);
const response = { current_page: page, pages: result.pages, size: size, results: result.results };
res.send(response);
} catch (error) {
console.log(error);
res.status(503);
res.send(error);
}
});
// Handle other routes
router.use((req, res) => {
res.status(404).send({ error: "Not Found" });

View File

@@ -16,6 +16,7 @@ import { Tabs, Tab, Button, ButtonGroup } from "react-bootstrap";
import { Trans } from "react-i18next";
import LibraryOptions from "./library/library-options";
import GlobalStats from "./general/globalStats";
import GenreLibraryStats from "./library/genre-library-stats.jsx";
function LibraryInfo() {
const { LibraryId } = useParams();
@@ -110,6 +111,8 @@ function LibraryInfo() {
title={<Trans i18nKey="LIBRARY_INFO.LIBRARY_STATS" />}
/>
<GenreLibraryStats LibraryId={LibraryId} />
{!data.archived && (
<ErrorBoundary>
<RecentlyAdded LibraryId={LibraryId} />

View File

@@ -0,0 +1,77 @@
import { useState, useEffect } from "react";
import { Row, Col } from "react-bootstrap";
import GenreStatCard from "../statCards/genre-stat-card.jsx";
import { Trans } from "react-i18next";
import "../../css/genres.css";
import Config from "../../../lib/config";
import axios from "../../../lib/axios_instance";
function GenreLibraryStats(props) {
const [data, setData] = useState();
const [config, setConfig] = useState();
useEffect(() => {
const fetchConfig = async () => {
try {
const newConfig = await Config.getConfig();
setConfig(newConfig);
} catch (error) {
console.log(error);
}
};
const fetchData = async () => {
if (config) {
try {
const itemData = await axios.get(`/stats/getGenreLibraryStats`, {
params: {
libraryid: props.LibraryId,
},
headers: {
Authorization: `Bearer ${config.token}`,
"Content-Type": "application/json",
},
});
const results = itemData.data.results || [];
setData(results);
} catch (error) {
console.log(error);
}
}
};
if (!data) {
fetchData();
}
if (!config) {
fetchConfig();
}
const intervalId = setInterval(fetchData, 60000 * 5);
return () => clearInterval(intervalId);
}, [data, config, props.LibraryId]);
if (!data || data.length == 0 || !config) {
return <></>;
}
return (
<div className="genre">
<h1 className="my-3">
<Trans i18nKey="GENRES" />
</h1>
<div className="genre-container d-flex flex-row justify-content-between">
<Row className="w-100 pb-5">
<Col className="p-0 auto">
<GenreStatCard data={data} dataKey="duration" />
</Col>
<Col className="p-0 auto">
<GenreStatCard data={data} dataKey="plays" />
</Col>
</Row>
</div>
</div>
);
}
export default GenreLibraryStats;

View File

@@ -24,7 +24,9 @@ function GenreStatCard(props) {
<div className="radial-tooltip">
<p className="tooltip-header">{payload[0].payload.genre}</p>
<p>
{props.dataKey == "duration" ? formatTotalWatchTime(payload[0].value) : payload[0].value} {i18next.t("UNITS.PLAYS")}
{props.dataKey == "duration"
? formatTotalWatchTime(payload[0].value)
: `${payload[0].value} ${i18next.t("UNITS.PLAYS")}`}
</p>
</div>
);

View File

@@ -12,7 +12,7 @@ import { Trans } from "react-i18next";
import baseUrl from "../../lib/baseurl";
import GlobalStats from "./general/globalStats";
import ActivityTimeline from "../activity_time_line";
import GenreStats from "./user-info/genre-stats.jsx";
import GenreUserStats from "./user-info/genre-user-stats.jsx";
function UserInfo() {
const { UserId } = useParams();
@@ -125,7 +125,7 @@ function UserInfo() {
endpoint={"getGlobalUserStats"}
title={<Trans i18nKey="USERS_PAGE.USER_STATS" />}
/>
<GenreStats UserId={UserId} />
<GenreUserStats UserId={UserId} />
<LastPlayed UserId={UserId} />
</Tab>
<Tab eventKey="tabActivity" className="bg-transparent">

View File

@@ -1,12 +1,12 @@
import { useState, useEffect } from "react";
import { Row, Col } from "react-bootstrap";
import GenreStatCard from "./genre-stat-card.jsx";
import GenreStatCard from "../statCards/genre-stat-card.jsx";
import { Trans } from "react-i18next";
import "../../css/genres.css";
import Config from "../../../lib/config";
import axios from "../../../lib/axios_instance";
function GenreStats(props) {
function GenreUserStats(props) {
const [data, setData] = useState();
const [config, setConfig] = useState();
@@ -23,7 +23,7 @@ function GenreStats(props) {
const fetchData = async () => {
if (config) {
try {
const itemData = await axios.get(`/stats/getGenreStats`, {
const itemData = await axios.get(`/stats/getGenreUserStats`, {
params: {
userid: props.UserId,
},
@@ -74,4 +74,4 @@ function GenreStats(props) {
);
}
export default GenreStats;
export default GenreUserStats;