mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
Stats Changes
Added more stuff to statistics page Playback per hour page has a bug
This commit is contained in:
@@ -16,11 +16,11 @@ if([_POSTGRES_USER,_POSTGRES_PASSWORD,_POSTGRES_IP,_POSTGRES_PORT].includes(unde
|
||||
|
||||
}
|
||||
|
||||
const development=true;
|
||||
const _DEV_USER='jfstat';
|
||||
const development=false;
|
||||
const _DEV_USER='postgress';
|
||||
const _DEV_PASSWORD = '123456';
|
||||
const _DEV_IP='10.0.0.99';
|
||||
const _DEV_PORT = 32778;
|
||||
const _DEV_IP='localhost';
|
||||
const _DEV_PORT = 5432;
|
||||
|
||||
const pool = new Pool({
|
||||
user: (development ? _DEV_USER: _POSTGRES_USER),
|
||||
|
||||
111
backend/init.sql
111
backend/init.sql
@@ -5,7 +5,7 @@
|
||||
-- Dumped from database version 15.2 (Debian 15.2-1.pgdg110+1)
|
||||
-- Dumped by pg_dump version 15.1
|
||||
|
||||
-- Started on 2023-04-01 09:50:04 UTC
|
||||
-- Started on 2023-04-02 20:11:41 UTC
|
||||
|
||||
SET statement_timeout = 0;
|
||||
SET lock_timeout = 0;
|
||||
@@ -19,7 +19,7 @@ SET client_min_messages = warning;
|
||||
SET row_security = off;
|
||||
|
||||
--
|
||||
-- TOC entry 252 (class 1255 OID 49412)
|
||||
-- TOC entry 254 (class 1255 OID 49412)
|
||||
-- Name: fs_last_library_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -57,7 +57,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_last_library_activity(libraryid text) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 248 (class 1255 OID 49383)
|
||||
-- TOC entry 250 (class 1255 OID 49383)
|
||||
-- Name: fs_last_user_activity(text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -93,7 +93,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_last_user_activity(userid text) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 246 (class 1255 OID 49411)
|
||||
-- TOC entry 248 (class 1255 OID 49411)
|
||||
-- Name: fs_library_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -145,7 +145,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_most_active_user(days integer) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 250 (class 1255 OID 49386)
|
||||
-- TOC entry 252 (class 1255 OID 49386)
|
||||
-- Name: fs_most_played_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -186,7 +186,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_most_played_items(days integer, itemtype text) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 251 (class 1255 OID 49394)
|
||||
-- TOC entry 253 (class 1255 OID 49394)
|
||||
-- Name: fs_most_popular_items(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -256,7 +256,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_most_used_clients(days integer) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 249 (class 1255 OID 49385)
|
||||
-- TOC entry 251 (class 1255 OID 49385)
|
||||
-- Name: fs_most_viewed_libraries(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -300,7 +300,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_most_viewed_libraries(days integer) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 247 (class 1255 OID 49364)
|
||||
-- TOC entry 249 (class 1255 OID 49364)
|
||||
-- Name: fs_user_stats(integer, text); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -325,7 +325,7 @@ $$;
|
||||
ALTER FUNCTION public.fs_user_stats(hours integer, userid text) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 245 (class 1255 OID 49418)
|
||||
-- TOC entry 246 (class 1255 OID 49418)
|
||||
-- Name: fs_watch_stats_over_time(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -350,6 +350,77 @@ $$;
|
||||
|
||||
ALTER FUNCTION public.fs_watch_stats_over_time(days integer) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 247 (class 1255 OID 57644)
|
||||
-- Name: fs_watch_stats_popular_days_of_week(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE FUNCTION public.fs_watch_stats_popular_days_of_week(days integer) RETURNS TABLE("Day" text, "Count" bigint, "Library" text)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
TO_CHAR(d."Day", 'Day') AS "Day",
|
||||
COUNT(a."NowPlayingItemId") AS "Count",
|
||||
COALESCE(l."Name", 'Unknown') AS "Library"
|
||||
FROM (
|
||||
SELECT
|
||||
DATE_TRUNC('week', NOW()) + n * INTERVAL '1 day' AS "Day"
|
||||
FROM generate_series(0, 6) n
|
||||
) d
|
||||
CROSS JOIN jf_libraries l
|
||||
LEFT JOIN (
|
||||
SELECT
|
||||
DATE_TRUNC('day', "ActivityDateInserted") AS "Day",
|
||||
"NowPlayingItemId",
|
||||
i."ParentId"
|
||||
FROM jf_playback_activity a
|
||||
JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
WHERE a."ActivityDateInserted" BETWEEN NOW() - CAST(days || ' days' as INTERVAL) AND NOW()
|
||||
) a ON d."Day" = a."Day" AND l."Id" = a."ParentId"
|
||||
GROUP BY d."Day", l."Name"
|
||||
ORDER BY d."Day";
|
||||
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
ALTER FUNCTION public.fs_watch_stats_popular_days_of_week(days integer) OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 245 (class 1255 OID 57646)
|
||||
-- Name: fs_watch_stats_popular_hour_of_day(integer); Type: FUNCTION; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
CREATE FUNCTION public.fs_watch_stats_popular_hour_of_day(days integer) RETURNS TABLE("Hour" integer, "Count" integer, "Library" text)
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
BEGIN
|
||||
RETURN QUERY
|
||||
SELECT
|
||||
DATE_PART('hour', hl."Hour")::INTEGER AS "Hour",
|
||||
COALESCE(CAST(COUNT(a."NowPlayingItemId") AS INTEGER), 0) AS "Count",
|
||||
COALESCE(l."Name", hl."Name") AS "Library"
|
||||
FROM (
|
||||
SELECT
|
||||
DATE_TRUNC('week', NOW()) + INTERVAL '1 hour' * n AS "Hour",
|
||||
l."Name"
|
||||
FROM generate_series(0, 167) n
|
||||
CROSS JOIN jf_libraries l
|
||||
) hl
|
||||
LEFT JOIN jf_playback_activity a ON DATE_TRUNC('hour', a."ActivityDateInserted") = hl."Hour"
|
||||
LEFT JOIN jf_library_items i ON i."Id" = a."NowPlayingItemId"
|
||||
LEFT JOIN jf_libraries l ON i."ParentId" = l."Id"
|
||||
WHERE hl."Hour" BETWEEN NOW() - CAST(days || ' days' as INTERVAL) AND NOW()
|
||||
GROUP BY DATE_PART('hour', hl."Hour"), COALESCE(l."Name", hl."Name")
|
||||
ORDER BY DATE_PART('hour', hl."Hour"), COALESCE(l."Name", hl."Name");
|
||||
END;
|
||||
$$;
|
||||
|
||||
|
||||
ALTER FUNCTION public.fs_watch_stats_popular_hour_of_day(days integer) OWNER TO postgres;
|
||||
|
||||
SET default_tablespace = '';
|
||||
|
||||
SET default_table_access_method = heap;
|
||||
@@ -688,7 +759,7 @@ CREATE VIEW public.js_library_stats_overview AS
|
||||
ALTER TABLE public.js_library_stats_overview OWNER TO postgres;
|
||||
|
||||
--
|
||||
-- TOC entry 3237 (class 2606 OID 16401)
|
||||
-- TOC entry 3239 (class 2606 OID 16401)
|
||||
-- Name: app_config app_config_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -697,7 +768,7 @@ ALTER TABLE ONLY public.app_config
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3239 (class 2606 OID 16419)
|
||||
-- TOC entry 3241 (class 2606 OID 16419)
|
||||
-- Name: jf_libraries jf_libraries_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -706,7 +777,7 @@ ALTER TABLE ONLY public.jf_libraries
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3245 (class 2606 OID 24912)
|
||||
-- TOC entry 3247 (class 2606 OID 24912)
|
||||
-- Name: jf_library_episodes jf_library_episodes_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -715,7 +786,7 @@ ALTER TABLE ONLY public.jf_library_episodes
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3241 (class 2606 OID 24605)
|
||||
-- TOC entry 3243 (class 2606 OID 24605)
|
||||
-- Name: jf_library_items jf_library_items_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -724,7 +795,7 @@ ALTER TABLE ONLY public.jf_library_items
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3243 (class 2606 OID 24737)
|
||||
-- TOC entry 3245 (class 2606 OID 24737)
|
||||
-- Name: jf_library_seasons jf_library_seasons_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -733,7 +804,7 @@ ALTER TABLE ONLY public.jf_library_seasons
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3247 (class 2606 OID 41737)
|
||||
-- TOC entry 3249 (class 2606 OID 41737)
|
||||
-- Name: jf_users jf_users_pkey; Type: CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -742,7 +813,7 @@ ALTER TABLE ONLY public.jf_users
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3391 (class 2618 OID 25163)
|
||||
-- TOC entry 3393 (class 2618 OID 25163)
|
||||
-- Name: jf_library_count_view _RETURN; Type: RULE; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -762,7 +833,7 @@ CREATE OR REPLACE VIEW public.jf_library_count_view AS
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3248 (class 2606 OID 24617)
|
||||
-- TOC entry 3250 (class 2606 OID 24617)
|
||||
-- Name: jf_library_items jf_library_items_fkey; Type: FK CONSTRAINT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
@@ -771,15 +842,15 @@ ALTER TABLE ONLY public.jf_library_items
|
||||
|
||||
|
||||
--
|
||||
-- TOC entry 3399 (class 0 OID 0)
|
||||
-- Dependencies: 3248
|
||||
-- TOC entry 3401 (class 0 OID 0)
|
||||
-- Dependencies: 3250
|
||||
-- Name: CONSTRAINT jf_library_items_fkey ON jf_library_items; Type: COMMENT; Schema: public; Owner: postgres
|
||||
--
|
||||
|
||||
COMMENT ON CONSTRAINT jf_library_items_fkey ON public.jf_library_items IS 'jf_library';
|
||||
|
||||
|
||||
-- Completed on 2023-04-01 09:50:05 UTC
|
||||
-- Completed on 2023-04-02 20:11:41 UTC
|
||||
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
|
||||
@@ -12,7 +12,7 @@ const db = require('./db');
|
||||
const app = express();
|
||||
const PORT = process.env.PORT || 3003;
|
||||
const LISTEN_IP = '127.0.0.1';
|
||||
const JWT_SECRET = process.env.JWT_SECRET ||'my-secret-jwt-key';
|
||||
const JWT_SECRET = process.env.JWT_SECRET;
|
||||
|
||||
if (JWT_SECRET === undefined) {
|
||||
console.log('JWT Secret cannot be undefined');
|
||||
|
||||
@@ -320,6 +320,80 @@ const finalData = Object.values(reorganizedData);
|
||||
}
|
||||
});
|
||||
|
||||
router.post("/getViewsByDays", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days=== undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_watch_stats_popular_days_of_week('${_days}')`
|
||||
);
|
||||
|
||||
|
||||
const reorganizedData = {};
|
||||
|
||||
rows.forEach((item) => {
|
||||
|
||||
const id = item.Library;
|
||||
const count = item.Count;
|
||||
const day = item.Day;
|
||||
|
||||
if (!reorganizedData[id]) {
|
||||
reorganizedData[id] = {
|
||||
id,
|
||||
data: []
|
||||
};
|
||||
}
|
||||
|
||||
reorganizedData[id].data.push({ x: day, y: count });
|
||||
});
|
||||
const finalData = Object.values(reorganizedData);
|
||||
res.send(finalData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
router.post("/getViewsByHour", async (req, res) => {
|
||||
try {
|
||||
const { days } = req.body;
|
||||
let _days = days;
|
||||
if (days=== undefined) {
|
||||
_days = 30;
|
||||
}
|
||||
const { rows } = await db.query(
|
||||
`select * from fs_watch_stats_popular_hour_of_day('${_days}')`
|
||||
);
|
||||
|
||||
|
||||
const reorganizedData = {};
|
||||
|
||||
rows.forEach((item) => {
|
||||
|
||||
const id = item.Library;
|
||||
const count = item.Count;
|
||||
const hour = item.Hour;
|
||||
|
||||
if (!reorganizedData[id]) {
|
||||
reorganizedData[id] = {
|
||||
id,
|
||||
data: []
|
||||
};
|
||||
}
|
||||
|
||||
reorganizedData[id].data.push({ x: hour, y: count });
|
||||
});
|
||||
const finalData = Object.values(reorganizedData);
|
||||
res.send(finalData);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
res.send(error);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
POSTGRES_PASSWORD: mypassword
|
||||
POSTGRES_IP: jellystat-db
|
||||
POSTGRES_PORT: 5432
|
||||
JWT_SECRET: 'my-secret-jwt-key'
|
||||
ports:
|
||||
- "3000:3000"
|
||||
depends_on:
|
||||
|
||||
@@ -13,6 +13,7 @@ export default function Navbar() {
|
||||
|
||||
return (
|
||||
<div className={"navbar"}>
|
||||
<h1 style={{marginRight:"20px"}}>Jellystat</h1>
|
||||
{navData.map((item) => {
|
||||
return (
|
||||
<NavLink key={item.id} className={"navitem"} to={item.link}>
|
||||
|
||||
@@ -1,41 +1,37 @@
|
||||
import React,{useState,useEffect} from 'react';
|
||||
import axios from 'axios';
|
||||
import { ResponsiveLine } from '@nivo/line';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
|
||||
import '../../css/stats.css';
|
||||
import "../../css/stats.css";
|
||||
|
||||
function DailyPlayStats(props) {
|
||||
const [data, setData] = useState();
|
||||
const [data, setData] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsOverTime`;
|
||||
|
||||
const url = `/stats/getViewsOverTime`;
|
||||
|
||||
axios
|
||||
.post(url, {days:props.days}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
axios
|
||||
.post(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
})
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (!data) {
|
||||
fetchLibraries();
|
||||
}
|
||||
@@ -44,130 +40,120 @@ function DailyPlayStats(props) {
|
||||
fetchLibraries();
|
||||
}
|
||||
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days,props.days ,token]);
|
||||
}, [data, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
return <></>;
|
||||
return <></>;
|
||||
}
|
||||
|
||||
|
||||
if (data.length === 0) {
|
||||
return (<div className="statistics-widget">
|
||||
return (
|
||||
<div className="statistics-widget">
|
||||
<h1>Daily Play Count Per Library - {days} Days</h1>
|
||||
|
||||
<h1>Daily Play Count Per Library - {days} Days</h1>
|
||||
|
||||
|
||||
<h1>No Stats to display</h1>
|
||||
|
||||
</div>
|
||||
<h1>No Stats to display</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="statistics-widget">
|
||||
<h1>Daily Play Count Per Library - {days} Days</h1>
|
||||
|
||||
return (
|
||||
<div className="statistics-widget" >
|
||||
<h1>Daily Play Count Per Library - {days} Days</h1>
|
||||
<div className="graph">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
|
||||
xScale={{ type: 'point' }}
|
||||
yScale={{
|
||||
type: 'linear',
|
||||
min: 'auto',
|
||||
max: 'auto',
|
||||
data={data}
|
||||
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: "auto",
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="natural"
|
||||
theme={{
|
||||
axis: {
|
||||
ticks: {
|
||||
line: {
|
||||
stroke: "white"
|
||||
},
|
||||
text: {
|
||||
fill: "white"
|
||||
}
|
||||
}
|
||||
},
|
||||
grid: {
|
||||
line: {
|
||||
stroke: "rgba(255,255,255,0.2)",
|
||||
strokeWidth: 2,
|
||||
strokeDasharray: "1 40"
|
||||
}
|
||||
}
|
||||
reverse: false,
|
||||
}}
|
||||
|
||||
axisBottom={{
|
||||
orient: 'bottom',
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="natural"
|
||||
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',
|
||||
legend: "Days",
|
||||
legendOffset: 36,
|
||||
legendPosition: 'middle'
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: 'left',
|
||||
legendPosition: "middle",
|
||||
}}
|
||||
axisLeft={{
|
||||
orient: "left",
|
||||
tickSize: 5,
|
||||
tickPadding: 5,
|
||||
tickRotation: 0,
|
||||
legend: 'Plays',
|
||||
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={[
|
||||
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: 80,
|
||||
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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default DailyPlayStats;
|
||||
|
||||
|
||||
|
||||
159
src/pages/components/statistics/play-stats-by-day.js
Normal file
159
src/pages/components/statistics/play-stats-by-day.js
Normal file
@@ -0,0 +1,159 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
|
||||
import "../../css/stats.css";
|
||||
|
||||
function PlayStatsByDay(props) {
|
||||
const [data, setData] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsByDays`;
|
||||
|
||||
axios
|
||||
.post(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
fetchLibraries();
|
||||
}
|
||||
if (days !== props.days) {
|
||||
setDays(props.days);
|
||||
fetchLibraries();
|
||||
}
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="statistics-widget small">
|
||||
<h1>Play Count By Day - {days} Days</h1>
|
||||
|
||||
<h1>No Stats to display</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="statistics-widget">
|
||||
<h1>Play Count By Day - {days} Days</h1>
|
||||
<div className="graph small">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: "auto",
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false,
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="natural"
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlayStatsByDay;
|
||||
161
src/pages/components/statistics/play-stats-by-hour.js
Normal file
161
src/pages/components/statistics/play-stats-by-hour.js
Normal file
@@ -0,0 +1,161 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import { ResponsiveLine } from "@nivo/line";
|
||||
|
||||
import "../../css/stats.css";
|
||||
|
||||
function PlayStatsByHour(props) {
|
||||
const [data, setData] = useState();
|
||||
const [days, setDays] = useState(60);
|
||||
const token = localStorage.getItem("token");
|
||||
|
||||
useEffect(() => {
|
||||
const fetchLibraries = () => {
|
||||
const url = `/stats/getViewsByHour`;
|
||||
|
||||
axios
|
||||
.post(
|
||||
url,
|
||||
{ days: props.days },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
}
|
||||
)
|
||||
.then((data) => {
|
||||
setData(data.data);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error);
|
||||
});
|
||||
};
|
||||
|
||||
if (!data) {
|
||||
fetchLibraries();
|
||||
}
|
||||
if (days !== props.days) {
|
||||
setDays(props.days);
|
||||
fetchLibraries();
|
||||
}
|
||||
|
||||
const intervalId = setInterval(fetchLibraries, 60000 * 5);
|
||||
return () => clearInterval(intervalId);
|
||||
}, [data, days, props.days, token]);
|
||||
|
||||
if (!data) {
|
||||
return <></>;
|
||||
}
|
||||
|
||||
if (data.length === 0) {
|
||||
return (
|
||||
<div className="statistics-widget small">
|
||||
<h1>Play Count By Hour - {days} Days</h1>
|
||||
|
||||
<h1>No Stats to display</h1>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
console.log(data);
|
||||
|
||||
return (
|
||||
<div className="statistics-widget">
|
||||
<h1>Play Count By Hour - {days} Days</h1>
|
||||
<div className="graph small">
|
||||
<ResponsiveLine
|
||||
data={data}
|
||||
margin={{ top: 50, right: 100, bottom: 200, left: 50 }}
|
||||
xScale={{ type: "point" }}
|
||||
yScale={{
|
||||
type: "linear",
|
||||
min: "auto",
|
||||
max: "auto",
|
||||
stacked: false,
|
||||
reverse: false,
|
||||
}}
|
||||
enableGridX={false}
|
||||
enableSlices={"x"}
|
||||
yFormat=" >-.0f"
|
||||
curve="linear"
|
||||
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,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default PlayStatsByHour;
|
||||
@@ -16,7 +16,7 @@ div a
|
||||
|
||||
.activity-table
|
||||
{
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(100,100, 100, 0.2);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,32 @@
|
||||
.statistics-widget
|
||||
.graph
|
||||
{
|
||||
|
||||
height: 700px;
|
||||
color:black !important;
|
||||
background-color:rgba(0,0,0,0.2);
|
||||
background-color:rgba(100,100,100,0.2);
|
||||
padding:20px;
|
||||
|
||||
border-radius:4px;
|
||||
/* text-align: center; */
|
||||
}
|
||||
|
||||
|
||||
|
||||
.small
|
||||
{
|
||||
height: 500px;
|
||||
|
||||
}
|
||||
|
||||
.statistics-graphs
|
||||
{
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
}
|
||||
|
||||
.statistics-widget
|
||||
{
|
||||
flex: 1;
|
||||
/* margin-right: 20px; */
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 0 10px 5px rgba(0,0,0,0.2);
|
||||
box-shadow: 0 0 10px 5px rgba(100,100,100,0.2);
|
||||
}
|
||||
|
||||
.user-image-container
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
font-family: sans-serif;
|
||||
min-width: 400px;
|
||||
/* box-shadow: 0 0 20px rgba(255, 255, 255, 0.15); */
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
background-color: rgba(100,100, 100, 0.2);
|
||||
color: white;
|
||||
width: 100%;
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import React,{useState} from 'react';
|
||||
import React, { useState } from "react";
|
||||
|
||||
// import './css/library/libraries.css';
|
||||
import "./css/statCard.css";
|
||||
import "./css/stats.css";
|
||||
|
||||
import DailyPlayStats from './components/statistics/daily-play-count';
|
||||
import DailyPlayStats from "./components/statistics/daily-play-count";
|
||||
import PlayStatsByDay from "./components/statistics/play-stats-by-day";
|
||||
import PlayStatsByHour from "./components/statistics/play-stats-by-hour";
|
||||
|
||||
function Statistics() {
|
||||
|
||||
const [days, setDays] = useState(60);
|
||||
const [input, setInput] = useState(60);
|
||||
|
||||
@@ -23,40 +24,34 @@ function Statistics() {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
return (
|
||||
<div className="watch-stats">
|
||||
<div className="Heading">
|
||||
<h1>Statistics</h1>
|
||||
<div className="date-range">
|
||||
<div className="header">Last</div>
|
||||
<div className="days">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={input}
|
||||
onChange={(event) => setInput(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
<div className="trailer">Days</div>
|
||||
return (
|
||||
<div className="watch-stats">
|
||||
<div className="Heading">
|
||||
<h1>Statistics</h1>
|
||||
<div className="date-range">
|
||||
<div className="header">Last</div>
|
||||
<div className="days">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
value={input}
|
||||
onChange={(event) => setInput(event.target.value)}
|
||||
onKeyDown={handleKeyDown}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
<div >
|
||||
<DailyPlayStats days={days}/>
|
||||
|
||||
|
||||
<div className="trailer">Days</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
);
|
||||
|
||||
<div>
|
||||
<DailyPlayStats days={days} />
|
||||
|
||||
<div className="statistics-graphs">
|
||||
<PlayStatsByDay days={days} />
|
||||
<PlayStatsByHour days={days} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default Statistics;
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user