mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
More pages converted to use localization
This commit is contained in:
@@ -41,32 +41,111 @@
|
||||
"TOTAL_TIME": "Total Time",
|
||||
"TOTAL_FILES": "Total Files",
|
||||
"LIBRARY_SIZE": "Library Size",
|
||||
"TOTAL_PLAYS": "Total Plays",
|
||||
"TOTAL_PLAYBACK": "Total Playback",
|
||||
"LAST_PLAYED": "Last Played",
|
||||
"LAST_ACTIVITY": "Last Activity"
|
||||
},
|
||||
"GLOBAL_STATS":{
|
||||
"LAST_24_HRS": "Last 24 Hours",
|
||||
"LAST_7_DAYS": "Last 7 Days",
|
||||
"LAST_30_DAYS": "Last 30 Days",
|
||||
"ALL_TIME": "All Time",
|
||||
"ITEM_STATS": "Item Stats"
|
||||
},
|
||||
"ITEM_INFO": {
|
||||
"FILE_NAME": "File Name",
|
||||
"FILE_PATH": "File Path",
|
||||
"FILE_SIZE": "File Size",
|
||||
"RUNTIME": "Runtime",
|
||||
"AVERAGE_RUNTIME": "Average Runtime",
|
||||
"OPEN_IN_JELLYFIN": "Open in Jellyfin",
|
||||
"ARCHIVED_DATA_OPTIONS":"Archived Data Options",
|
||||
"PURGE":"Purge",
|
||||
"CONFIRM_ACTION":"Confirm Action",
|
||||
"CONFIRM_ACTION_MESSAGE":"Are you sure you want to Purge this item",
|
||||
"CONFIRM_ACTION_MESSAGE_2":"and Associated Playback Activity"
|
||||
},
|
||||
"LIBRARY_INFO":{
|
||||
"LIBRARY_STATS": "Library Stats",
|
||||
"LIBRARY_ACTIVITY": "Library Activity"
|
||||
},
|
||||
"TAB_CONTROLS":{
|
||||
"OVERVIEW": "Overview",
|
||||
"ACTIVITY": "Activity",
|
||||
"OPTIONS": "Options"
|
||||
},
|
||||
"ITEM_ACTIVITY":"Item Activity",
|
||||
"ACTIVITY_TABLE":{
|
||||
"MODAL":{
|
||||
"HEADER":"Stream Info"
|
||||
},
|
||||
"IP_ADDRESS":"IP Address",
|
||||
"CLIENT":"Client",
|
||||
"DATE":"Date",
|
||||
"PLAYBACK_DURATION":"Playback Duration",
|
||||
"TOTAL_PLAYBACK":"Total Playback"
|
||||
|
||||
},
|
||||
"TABLE_NAV_BUTTONS":{
|
||||
"FIRST":"First",
|
||||
"LAST":"Last",
|
||||
"NEXT":"Next",
|
||||
"PREVIOUS":"Previous"
|
||||
},
|
||||
"PURGE_OPTIONS":{
|
||||
"PURGE_CACHE":"Purge Cached Item",
|
||||
"PURGE_CACHE_WITH_ACTIVITY":"Purge Cached Item and Playback Activity"
|
||||
},
|
||||
"ERROR_MESSAGES":{
|
||||
"FETCH_THIS_ITEM":"Fetch this item from Jellyfin",
|
||||
"NO_ACTIVITY":"No Activity Found",
|
||||
"NEVER":"Never",
|
||||
"N/A":"N/A"
|
||||
},
|
||||
"SHOW_ARCHIVED_LIBRARIES":"Show Archived Libraries",
|
||||
"HIDE_ARCHIVED_LIBRARIES":"Hide Archived Libraries",
|
||||
"UNITS":{
|
||||
"DAY": "Day",
|
||||
"DAYS": "Days",
|
||||
"HOUR": "Hour",
|
||||
"HOURS": "Hours",
|
||||
"MINUTE": "Minute",
|
||||
"MINUTES": "Minutes",
|
||||
"SECOND": "Second",
|
||||
"SECONDS": "Seconds",
|
||||
"PLAYS": "Plays",
|
||||
"ITEMS": "Items"
|
||||
},
|
||||
"USERS_PAGE":{
|
||||
"ALL_USERS": "All Users",
|
||||
"LAST_CLIENT": "Last Client",
|
||||
"LAST_SEEN": "Last Seen",
|
||||
"AGO": "Ago",
|
||||
"USER_STATS": "User Stats",
|
||||
"USER_ACTIVITY": "User Activity"
|
||||
},
|
||||
"TOTAL": "Total",
|
||||
"LAST": "Last",
|
||||
"SERIES": "Series",
|
||||
"SEASON": "Season",
|
||||
"SEASONS": "Seasons",
|
||||
"EPISODE": "Episode",
|
||||
"EPISODES": "Episodes",
|
||||
"MOVIES": "Movies",
|
||||
"SONGS": "Songs",
|
||||
"FILES": "Files",
|
||||
"LIBRARIES": "Libraries",
|
||||
"USER":"User",
|
||||
"USERS": "Users",
|
||||
"TYPE": "Type",
|
||||
"NEW_VERSION_AVAILABLE":"New version available"
|
||||
"NEW_VERSION_AVAILABLE":"New version available",
|
||||
"ARCHIVED":"Archived",
|
||||
"CLOSE":"Close",
|
||||
"TOTAL_PLAYS": "Total Plays",
|
||||
"TITLE":"Title",
|
||||
"VIEWS": "Views",
|
||||
"WATCH_TIME": "Watch Time",
|
||||
"LAST_WATCHED": "Last Watched",
|
||||
"MEDIA": "Media"
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import Config from "../lib/config";
|
||||
|
||||
import ActivityTable from "./components/activity/activity-table";
|
||||
import Loading from "./components/general/loading";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function Activity() {
|
||||
const [data, setData] = useState();
|
||||
@@ -71,10 +72,10 @@ function Activity() {
|
||||
if (data.length === 0) {
|
||||
return (<div>
|
||||
<div className="Heading">
|
||||
<h1>Activity</h1>
|
||||
<h1><Trans i18nKey="MENU_TABS.ACTIVITY"/></h1>
|
||||
</div>
|
||||
<div className="Activity">
|
||||
<h1>No Activity to display</h1>
|
||||
<h1><Trans i18nKey="ERROR_MESSAGES.NO_ACTIVITY"/></h1>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
@@ -83,9 +84,9 @@ function Activity() {
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading">
|
||||
<h1>Activity</h1>
|
||||
<h1><Trans i18nKey="MENU_TABS.ACTIVITY"/></h1>
|
||||
<div className="pagination-range">
|
||||
<div className="header">Items</div>
|
||||
<div className="header"><Trans i18nKey="UNITS.ITEMS"/></div>
|
||||
<select value={itemCount} onChange={(event) => {setItemCount(event.target.value);}}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
||||
@@ -22,6 +22,8 @@ import IndeterminateCircleFillIcon from 'remixicon-react/IndeterminateCircleFill
|
||||
import StreamInfo from './stream_info';
|
||||
|
||||
import '../../css/activity/activity-table.css';
|
||||
import { Trans } from 'react-i18next';
|
||||
import i18next from 'i18next';
|
||||
|
||||
// localStorage.setItem('hour12',true);
|
||||
|
||||
@@ -42,7 +44,7 @@ function formatTotalWatchTime(seconds) {
|
||||
}
|
||||
|
||||
if (remainingSeconds > 0) {
|
||||
timeString += `${remainingSeconds} ${remainingSeconds === 1 ? 'second' : 'seconds'}`;
|
||||
timeString += `${remainingSeconds} ${remainingSeconds === 1 ? i18next.t("UNITS.SECOND").toLowerCase() : i18next.t("UNITS.SECONDS").toLowerCase()}`;
|
||||
}
|
||||
|
||||
return timeString.trim();
|
||||
@@ -84,12 +86,12 @@ function Row(data) {
|
||||
|
||||
<Modal show={modalState} onHide={()=>setModalState(false)} >
|
||||
<Modal.Header>
|
||||
<Modal.Title>Stream Info: {!row.SeriesName ? row.NowPlayingItemName : row.SeriesName+' - '+ row.NowPlayingItemName} ({row.UserName})</Modal.Title>
|
||||
<Modal.Title><Trans i18nKey="ACTIVITY_TABLE.MODAL.HEADER"/>: {!row.SeriesName ? row.NowPlayingItemName : row.SeriesName+' - '+ row.NowPlayingItemName} ({row.UserName})</Modal.Title>
|
||||
</Modal.Header>
|
||||
<StreamInfo data={modalData}/>
|
||||
<Modal.Footer>
|
||||
<Button variant="outline-primary" onClick={()=>setModalState(false)}>
|
||||
Close
|
||||
<Trans i18nKey="CLOSE"/>
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
@@ -120,13 +122,13 @@ function Row(data) {
|
||||
<Table aria-label="sub-activity" className='rounded-2'>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>User</TableCell>
|
||||
<TableCell>IP Address</TableCell>
|
||||
<TableCell>Title</TableCell>
|
||||
<TableCell>Client</TableCell>
|
||||
<TableCell>Date</TableCell>
|
||||
<TableCell>Playback Duration</TableCell>
|
||||
<TableCell>Plays</TableCell>
|
||||
<TableCell><Trans i18nKey="USER"/></TableCell>
|
||||
<TableCell><Trans i18nKey="ACTIVITY_TABLE.IP_ADDRESS"/></TableCell>
|
||||
<TableCell><Trans i18nKey="TITLE"/></TableCell>
|
||||
<TableCell><Trans i18nKey="ACTIVITY_TABLE.CLIENT"/></TableCell>
|
||||
<TableCell><Trans i18nKey="ACTIVITY_TABLE.DATE"/></TableCell>
|
||||
<TableCell><Trans i18nKey="ACTIVITY_TABLE.PLAYBACK_DURATION"/></TableCell>
|
||||
<TableCell><Trans i18nKey="UNITS.PLAYS"/></TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
@@ -163,43 +165,43 @@ function EnhancedTableHead(props) {
|
||||
id: 'UserName',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'User',
|
||||
label: i18next.t("USER"),
|
||||
},
|
||||
{
|
||||
id: 'RemoteEndPoint',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'IP Address',
|
||||
label: i18next.t("ACTIVITY_TABLE.IP_ADDRESS"),
|
||||
},
|
||||
{
|
||||
id: 'NowPlayingItemName',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Title',
|
||||
label: i18next.t("TITLE"),
|
||||
},
|
||||
{
|
||||
id: 'Client',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Client',
|
||||
label: i18next.t("ACTIVITY_TABLE.CLIENT"),
|
||||
},
|
||||
{
|
||||
id: 'ActivityDateInserted',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Date',
|
||||
label: i18next.t("ACTIVITY_TABLE.DATE"),
|
||||
},
|
||||
{
|
||||
id: 'PlaybackDuration',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Total Playback',
|
||||
label: i18next.t("ACTIVITY_TABLE.TOTAL_PLAYBACK"),
|
||||
},
|
||||
{
|
||||
id: 'TotalPlays',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Total Plays',
|
||||
label: i18next.t("TOTAL_PLAYS"),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -326,7 +328,7 @@ export default function ActivityTable(props) {
|
||||
{visibleRows.map((row) => (
|
||||
<Row key={row.Id+row.NowPlayingItemId+row.EpisodeId} row={row} />
|
||||
))}
|
||||
{props.data.length===0 ? <tr><td colSpan="8" style={{ textAlign: "center", fontStyle: "italic" ,color:"grey"}} className='py-2'>No Activity Found</td></tr> :''}
|
||||
{props.data.length===0 ? <tr><td colSpan="8" style={{ textAlign: "center", fontStyle: "italic" ,color:"grey"}} className='py-2'><Trans i18nKey="ERROR_MESSAGES.NO_ACTIVITY"/></td></tr> :''}
|
||||
|
||||
</TableBody>
|
||||
</Table>
|
||||
@@ -335,21 +337,21 @@ export default function ActivityTable(props) {
|
||||
<div className='d-flex justify-content-end my-2'>
|
||||
<ButtonGroup className="pagination-buttons">
|
||||
<Button className="page-btn" onClick={()=>setPage(0)} disabled={page === 0}>
|
||||
First
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.FIRST"/>
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={handlePreviousPageClick} disabled={page === 0}>
|
||||
Previous
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.PREVIOUS"/>
|
||||
</Button>
|
||||
|
||||
<div className="page-number d-flex align-items-center justify-content-center">{`${(page *rowsPerPage) + 1}-${Math.min(((page * rowsPerPage)+ 1 ) + (rowsPerPage - 1),props.data.length)} of ${props.data.length}`}</div>
|
||||
|
||||
<Button className="page-btn" onClick={handleNextPageClick} disabled={page >= Math.ceil(props.data.length / rowsPerPage) - 1}>
|
||||
Next
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.NEXT"/>
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={()=>setPage(Math.ceil(props.data.length / rowsPerPage) - 1)} disabled={page >= Math.ceil(props.data.length / rowsPerPage) - 1}>
|
||||
Last
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.LAST"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
import React, {useState} from "react";
|
||||
import {useState} from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Blurhash } from 'react-blurhash';
|
||||
import ArchiveDrawerFillIcon from 'remixicon-react/ArchiveDrawerFillIcon';
|
||||
|
||||
import "../../css/lastplayed.css";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
function formatTime(time) {
|
||||
|
||||
const units = {
|
||||
days: ['Day', 'Days'],
|
||||
hours: ['Hour', 'Hours'],
|
||||
minutes: ['Minute', 'Minutes'],
|
||||
seconds: ['Second', 'Seconds']
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")],
|
||||
seconds: [i18next.t("UNITS.SECOND"), i18next.t("UNITS.SECONDS")]
|
||||
};
|
||||
|
||||
let formattedTime = '';
|
||||
@@ -26,7 +28,7 @@ function formatTime(time) {
|
||||
formattedTime = `${time.seconds} ${units.seconds[time.seconds > 1 ? 1 : 0]}`;
|
||||
}
|
||||
|
||||
return `${formattedTime} ago`;
|
||||
return `${formattedTime+' '+i18next.t("AGO").toLowerCase()}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +61,7 @@ function LastWatchedCard(props) {
|
||||
}
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-absolute">
|
||||
<ArchiveDrawerFillIcon className="w-100 h-100 mb-2"/>
|
||||
<span>Archived</span>
|
||||
<span><Trans i18nKey="ARCHIVED"/></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import ItemNotFound from "./item-info/item-not-found";
|
||||
import Config from "../../lib/config";
|
||||
import Loading from "./general/loading";
|
||||
import ItemOptions from "./item-info/item-options";
|
||||
import { Trans } from "react-i18next";
|
||||
import i18next from "i18next";
|
||||
|
||||
|
||||
|
||||
@@ -165,7 +167,7 @@ const cardBgStyle = {
|
||||
}
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-absolute">
|
||||
<ArchiveDrawerFillIcon className="w-100 h-100 mb-2"/>
|
||||
<span>Archived</span>
|
||||
<span><Trans i18nKey="ARCHIVED"/></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
@@ -182,23 +184,23 @@ const cardBgStyle = {
|
||||
}
|
||||
|
||||
</h1>
|
||||
<Link className="px-2" to={ config.hostUrl+"/web/index.html#!/details?id="+ (data.EpisodeId ||data.Id)} title="Open in Jellyfin" target="_blank"><ExternalLinkFillIcon/></Link>
|
||||
<Link className="px-2" to={ config.hostUrl+"/web/index.html#!/details?id="+ (data.EpisodeId ||data.Id)} title={i18next.t("ITEM_INFO.OPEN_IN_JELLYFIN")} target="_blank"><ExternalLinkFillIcon/></Link>
|
||||
</div>
|
||||
|
||||
<div className="my-3">
|
||||
{data.Type==="Episode"? <p><Link to={`/libraries/item/${data.SeasonId}`} className="fw-bold">{data.SeasonName}</Link> Episode {data.IndexNumber} - {data.Name}</p> : <></> }
|
||||
{data.Type==="Episode"? <p><Link to={`/libraries/item/${data.SeasonId}`} className="fw-bold">{data.SeasonName}</Link> <Trans i18nKey="EPISODE"/> {data.IndexNumber} - {data.Name}</p> : <></> }
|
||||
{data.Type==="Season"? <p>{data.Name}</p> : <></> }
|
||||
{data.FileName ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">File Name: {data.FileName}</p> :<></>}
|
||||
{data.Path ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">File Path: {data.Path}</p> :<></>}
|
||||
{data.RunTimeTicks ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">{data.Type==="Series"?"Average Runtime" : "Runtime"}: {ticksToTimeString(data.RunTimeTicks)}</p> :<></>}
|
||||
{data.Size ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">File Size: {formatFileSize(data.Size)}</p> :<></>}
|
||||
{data.FileName ? <p style={{color:"lightgrey"}} className="fst-italic fs-6"><Trans i18nKey="ITEM_INFO.FILE_NAME"/>: {data.FileName}</p> :<></>}
|
||||
{data.Path ? <p style={{color:"lightgrey"}} className="fst-italic fs-6"><Trans i18nKey="ITEM_INFO.FILE_PATH"/>: {data.Path}</p> :<></>}
|
||||
{data.RunTimeTicks ? <p style={{color:"lightgrey"}} className="fst-italic fs-6">{data.Type==="Series"? i18next.t("ITEM_INFO.AVERAGE_RUNTIME") : i18next.t("ITEM_INFO.RUNTIME")}: {ticksToTimeString(data.RunTimeTicks)}</p> :<></>}
|
||||
{data.Size ? <p style={{color:"lightgrey"}} className="fst-italic fs-6"><Trans i18nKey="ITEM_INFO.FILE_SIZE"/>: {formatFileSize(data.Size)}</p> :<></>}
|
||||
|
||||
</div>
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'>Overview</Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'>Activity</Button>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'><Trans i18nKey="ITEM_INFO.OVERVIEW"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'><Trans i18nKey="ITEM_INFO.ACTIVITY"/></Button>
|
||||
|
||||
{data.archived && (<Button onClick={() => setActiveTab('tabOptions')} active={activeTab==='tabOptions'} variant='outline-primary' type='button'>Options</Button>)}
|
||||
{data.archived && (<Button onClick={() => setActiveTab('tabOptions')} active={activeTab==='tabOptions'} variant='outline-primary' type='button'><Trans i18nKey="ITEM_INFO.OPTIONS"/></Button>)}
|
||||
</ButtonGroup>
|
||||
|
||||
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import "../../css/globalstats.css";
|
||||
|
||||
import WatchTimeStats from "./globalstats/watchtimestats";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function GlobalStats(props) {
|
||||
const [dayStats, setDayStats] = useState({});
|
||||
@@ -67,16 +68,15 @@ function GlobalStats(props) {
|
||||
return () => clearInterval(intervalId);
|
||||
}, [props.ItemId,token]);
|
||||
|
||||
// console.log(dayStats);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="py-3">Item Stats</h1>
|
||||
<h1 className="py-3"><Trans i18nKey="GLOBAL_STATS.ITEM_STATS"/></h1>
|
||||
<div className="global-stats-container">
|
||||
<WatchTimeStats data={dayStats} heading={"Last 24 Hours"} />
|
||||
<WatchTimeStats data={weekStats} heading={"Last 7 Days"} />
|
||||
<WatchTimeStats data={monthStats} heading={"Last 30 Days"} />
|
||||
<WatchTimeStats data={allStats} heading={"All Time"} />
|
||||
<WatchTimeStats data={dayStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_24_HRS"/>} />
|
||||
<WatchTimeStats data={weekStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_7_DAYS"/>} />
|
||||
<WatchTimeStats data={monthStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_30_DAYS"/>} />
|
||||
<WatchTimeStats data={allStats} heading={<Trans i18nKey="GLOBAL_STATS.ALL_TIME"/>} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import "../../../css/globalstats.css";
|
||||
import i18next from "i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function WatchTimeStats(props) {
|
||||
|
||||
function formatTime(totalSeconds, numberClassName, labelClassName) {
|
||||
const units = [
|
||||
{ label: 'Day', seconds: 86400 },
|
||||
{ label: 'Hour', seconds: 3600 },
|
||||
{ label: 'Minute', seconds: 60 },
|
||||
{ label: i18next.t("UNITS.DAY"), seconds: 86400 },
|
||||
{ label: i18next.t("UNITS.HOUR"), seconds: 3600 },
|
||||
{ label: i18next.t("UNITS.MINUTE"), seconds: 60 },
|
||||
];
|
||||
|
||||
const parts = units.reduce((result, { label, seconds }) => {
|
||||
@@ -17,8 +17,7 @@ function WatchTimeStats(props) {
|
||||
const formattedValue = <p className={numberClassName}>{value}</p>;
|
||||
const formattedLabel = (
|
||||
<span className={labelClassName}>
|
||||
{label}
|
||||
{value === 1 ? '' : 's'}
|
||||
{value === 1 ? label : i18next.t(`UNITS.${label.toUpperCase()}S`) }
|
||||
</span>
|
||||
);
|
||||
result.push(
|
||||
@@ -35,7 +34,7 @@ function WatchTimeStats(props) {
|
||||
return (
|
||||
<>
|
||||
<p className={numberClassName}>0</p>{' '}
|
||||
<p className={labelClassName}>Minutes</p>
|
||||
<p className={labelClassName}><Trans i18nKey="UNITS.MINUTES"/></p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -53,7 +52,7 @@ function WatchTimeStats(props) {
|
||||
|
||||
<div className="play-duration-stats" key={props.data.ItemId}>
|
||||
<p className="stat-value"> {props.data.Plays || 0}</p>
|
||||
<p className="stat-unit" >Plays /</p>
|
||||
<p className="stat-unit" ><Trans i18nKey="UNITS.PLAYS"/> /</p>
|
||||
|
||||
<>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')}</>
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import ActivityTable from "../activity/activity-table";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function ItemActivity(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -41,9 +42,9 @@ function ItemActivity(props) {
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading">
|
||||
<h1>Item Activity</h1>
|
||||
<h1><Trans i18nKey="ITEM_ACTIVITY"/></h1>
|
||||
<div className="pagination-range">
|
||||
<div className="header">Items</div>
|
||||
<div className="header"><Trans i18nKey="UNITS.ITEMS"/></div>
|
||||
<select value={itemCount} onChange={(event) => {setItemCount(event.target.value);}}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, {useState} from "react";
|
||||
import {useState} from "react";
|
||||
import axios from "axios";
|
||||
import "../../css/error.css";
|
||||
import { Button } from "react-bootstrap";
|
||||
import Loading from "../general/loading";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function ItemNotFound(props) {
|
||||
const [itemId] = useState(props.itemId);
|
||||
@@ -47,7 +48,7 @@ function ItemNotFound(props) {
|
||||
<div className="error">
|
||||
<h1 className="error-title">{props.message}</h1>
|
||||
|
||||
<Button variant="primary" className="mt-3" onClick={()=> fetchItem()}>Fetch this item from Jellyfin</Button>
|
||||
<Button variant="primary" className="mt-3" onClick={()=> fetchItem()}><Trans i18nKey="ERROR_MESSAGES.FETCH_THIS_ITEM"/></Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
|
||||
import axios from "axios";
|
||||
import i18next from "i18next";
|
||||
import { useState } from "react";
|
||||
import { Container, Row,Col, Modal } from "react-bootstrap";
|
||||
import { Trans } from "react-i18next";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
|
||||
|
||||
@@ -9,7 +11,16 @@ function ItemOptions(props) {
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
const [show, setShow] = useState(false);
|
||||
const options=[{description:"Purge Cached Item",withActivity:false},{description:"Purge Cached Item and Playback Activity",withActivity:true}];
|
||||
const options=[
|
||||
{
|
||||
description:i18next.t("PURGE_OPTIONS.PURGE_CACHE"),
|
||||
withActivity:false
|
||||
},
|
||||
{
|
||||
description: i18next.t("PURGE_OPTIONS.PURGE_CACHE_WITH_ACTIVITY"),
|
||||
withActivity:true
|
||||
}
|
||||
];
|
||||
const [selectedOption, setSelectedOption] = useState(options[0]);
|
||||
const navigate = useNavigate();
|
||||
|
||||
@@ -43,7 +54,7 @@ function ItemOptions(props) {
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading mb-3">
|
||||
<h1>Archived Data Options</h1>
|
||||
<h1><Trans i18nKey="ITEM_INFO.ARCHIVED_DATA_OPTIONS"/></h1>
|
||||
</div>
|
||||
<Container className="p-0 m-0">
|
||||
{options.map((option, index) => (
|
||||
@@ -53,7 +64,7 @@ function ItemOptions(props) {
|
||||
</Col>
|
||||
|
||||
<Col>
|
||||
<button className="btn btn-danger w-25" onClick={()=>{setSelectedOption(option);setShow(true);}}>Purge</button>
|
||||
<button className="btn btn-danger w-25" onClick={()=>{setSelectedOption(option);setShow(true);}}><Trans i18nKey="ITEM_INFO.PURGE"/></button>
|
||||
</Col>
|
||||
|
||||
</Row>
|
||||
@@ -62,17 +73,17 @@ function ItemOptions(props) {
|
||||
|
||||
<Modal show={show} onHide={() =>{setShow(false);}}>
|
||||
<Modal.Header closeButton>
|
||||
<Modal.Title>Confirm Action</Modal.Title>
|
||||
<Modal.Title><Trans i18nKey="ITEM_INFO.CONFIRM_ACTION"/></Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<p>{"Are you sure you want to Purge this item"+(selectedOption.withActivity ? " and Associated Playback Activity?" : "?")}</p>
|
||||
<p>{i18next.t("ITEM_INFO.CONFIRM_ACTION_MESSAGE")+(selectedOption.withActivity ? ` ${i18next.t("ITEM_INFO.CONFIRM_ACTION_MESSAGE_2")}?` : "?")}</p>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<button className="btn btn-danger" onClick={() => {execPurge(selectedOption.withActivity);}}>
|
||||
Purge
|
||||
<Trans i18nKey="ITEM_INFO.PURGE"/>
|
||||
</button>
|
||||
<button className="btn btn-primary" onClick={()=>{setShow(false);}}>
|
||||
Close
|
||||
<Trans i18nKey="CLOSE"/>
|
||||
</button>
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import MoreItemCards from "./more-items/more-items-card";
|
||||
|
||||
import Config from "../../../lib/config";
|
||||
import "../../css/users/user-details.css";
|
||||
import i18next from "i18next";
|
||||
|
||||
function MoreItems(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -66,7 +67,7 @@ function MoreItems(props) {
|
||||
|
||||
return (
|
||||
<div className="last-played">
|
||||
<h1 className="my-3">{props.data.Type==="Season" ? "Episodes" : "Seasons"}</h1>
|
||||
<h1 className="my-3">{props.data.Type==="Season" ? i18next.t("EPISODES") : i18next.t("SEASONS")}</h1>
|
||||
<div className="last-played-container">
|
||||
|
||||
{data.sort((a,b) => a.IndexNumber-b.IndexNumber).map((item) => (
|
||||
|
||||
@@ -4,6 +4,7 @@ import { Link } from "react-router-dom";
|
||||
import { useParams } from 'react-router-dom';
|
||||
import ArchiveDrawerFillIcon from 'remixicon-react/ArchiveDrawerFillIcon';
|
||||
import "../../../css/lastplayed.css";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
|
||||
|
||||
@@ -55,7 +56,7 @@ function MoreItemCards(props) {
|
||||
}
|
||||
<div className="d-flex flex-column justify-content-center align-items-center position-absolute">
|
||||
<ArchiveDrawerFillIcon className="w-100 h-100 mb-2"/>
|
||||
<span>Archived</span>
|
||||
<span><Trans i18nKey="ARCHIVED"/></span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import LibraryItems from './library/library-items';
|
||||
import ErrorBoundary from './general/ErrorBoundary';
|
||||
|
||||
import { Tabs, Tab, Button, ButtonGroup } from 'react-bootstrap';
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
|
||||
|
||||
@@ -73,9 +74,9 @@ function LibraryInfo() {
|
||||
<div>
|
||||
<p className="user-name">{data.Name}</p>
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'>Overview</Button>
|
||||
<Button onClick={() => setActiveTab('tabItems')} active={activeTab==='tabItems'} variant='outline-primary' type='button'>Media</Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'>Activity</Button>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.OVERVIEW"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabItems')} active={activeTab==='tabItems'} variant='outline-primary' type='button'><Trans i18nKey="MEDIA"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.ACTIVITY"/></Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
@@ -91,12 +92,12 @@ function LibraryInfo() {
|
||||
|
||||
<LibraryLastWatched LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
<Tab eventKey="tabActivity" className='bg-transparent'>
|
||||
<LibraryActivity LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
<Tab eventKey="tabItems" className='bg-transparent'>
|
||||
<LibraryItems LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
<Tab eventKey="tabActivity" className='bg-transparent'>
|
||||
<LibraryActivity LibraryId={LibraryId}/>
|
||||
</Tab>
|
||||
</Tabs>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
import React from "react";
|
||||
|
||||
import "../../../css/globalstats.css";
|
||||
import i18next from "i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function WatchTimeStats(props) {
|
||||
|
||||
function formatTime(totalSeconds, numberClassName, labelClassName) {
|
||||
const units = [
|
||||
{ label: 'Day', seconds: 86400 },
|
||||
{ label: 'Hour', seconds: 3600 },
|
||||
{ label: 'Minute', seconds: 60 },
|
||||
{ label: i18next.t("UNITS.DAY"), seconds: 86400 },
|
||||
{ label: i18next.t("UNITS.HOUR"), seconds: 3600 },
|
||||
{ label: i18next.t("UNITS.MINUTE"), seconds: 60 },
|
||||
];
|
||||
|
||||
const parts = units.reduce((result, { label, seconds }) => {
|
||||
@@ -17,8 +17,7 @@ function WatchTimeStats(props) {
|
||||
const formattedValue = <p className={numberClassName}>{value}</p>;
|
||||
const formattedLabel = (
|
||||
<span className={labelClassName}>
|
||||
{label}
|
||||
{value === 1 ? '' : 's'}
|
||||
{value === 1 ? label : i18next.t(`UNITS.${label.toUpperCase()}S`) }
|
||||
</span>
|
||||
);
|
||||
result.push(
|
||||
@@ -35,7 +34,7 @@ function WatchTimeStats(props) {
|
||||
return (
|
||||
<>
|
||||
<p className={numberClassName}>0</p>{' '}
|
||||
<p className={labelClassName}>Minutes</p>
|
||||
<p className={labelClassName}><Trans i18nKey="UNITS.MINUTES"/></p>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -53,7 +52,7 @@ function WatchTimeStats(props) {
|
||||
|
||||
<div className="play-duration-stats" key={props.data.UserId}>
|
||||
<p className="stat-value"> {props.data.Plays || 0}</p>
|
||||
<p className="stat-unit" >Plays /</p>
|
||||
<p className="stat-unit" ><Trans i18nKey="UNITS.PLAYS"/> /</p>
|
||||
|
||||
<>{formatTime(props.data.total_playback_duration || 0,'stat-value','stat-unit')}</>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
// import ItemCardInfo from "./LastWatched/last-watched-card";
|
||||
@@ -8,6 +8,7 @@ import LastWatchedCard from "../general/last-watched-card";
|
||||
|
||||
import Config from "../../../lib/config";
|
||||
import "../../css/users/user-details.css";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function LibraryLastWatched(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -61,7 +62,7 @@ function LibraryLastWatched(props) {
|
||||
|
||||
return (
|
||||
<div className="last-played">
|
||||
<h1 className="my-3">Last Watched</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="LAST_WATCHED"/></h1>
|
||||
<div className="last-played-container">
|
||||
{data.map((item) => (
|
||||
<LastWatchedCard data={item} base_url={config.hostUrl} key={item.Id+item.SeasonNumber+item.EpisodeNumber}/>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import ActivityTable from "../activity/activity-table";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function LibraryActivity(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -42,9 +43,9 @@ function LibraryActivity(props) {
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading">
|
||||
<h1>Library Activity</h1>
|
||||
<h1><Trans i18nKey={"LIBRARY_INFO.LIBRARY_ACTIVITY"}/></h1>
|
||||
<div className="pagination-range">
|
||||
<div className="header">Items</div>
|
||||
<div className="header"><Trans i18nKey={"UNITS.ITEMS"}/></div>
|
||||
<select value={itemCount} onChange={(event) => {setItemCount(event.target.value);}}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
||||
@@ -106,9 +106,9 @@ function LibraryCard(props) {
|
||||
|
||||
function formatLastActivityTime(time) {
|
||||
const units = {
|
||||
days: ['Day', 'Days'],
|
||||
hours: ['Hour', 'Hours'],
|
||||
minutes: ['Minute', 'Minutes']
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")]
|
||||
};
|
||||
|
||||
let formattedTime = '';
|
||||
@@ -120,7 +120,7 @@ function LibraryCard(props) {
|
||||
}
|
||||
}
|
||||
|
||||
return `${formattedTime}ago`;
|
||||
return `${formattedTime+i18next.t("AGO").toLowerCase()}`;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -170,7 +170,7 @@ function LibraryCard(props) {
|
||||
</Row>
|
||||
|
||||
<Row className="space-between-end card-row">
|
||||
<Col className="card-label"><Trans i18nKey="LIBRARY_CARD.TOTAL_PLAYS" /></Col>
|
||||
<Col className="card-label"><Trans i18nKey="TOTAL_PLAYS" /></Col>
|
||||
<Col className="text-end">{props.data.Plays}</Col>
|
||||
</Row>
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@ import Config from "../../../lib/config";
|
||||
import "../../css/library/media-items.css";
|
||||
import "../../css/width_breakpoint_css.css";
|
||||
import "../../css/radius_breakpoint_css.css";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function LibraryItems(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -90,15 +91,15 @@ function LibraryItems(props) {
|
||||
return (
|
||||
<div className="library-items">
|
||||
<div className="d-md-flex justify-content-between">
|
||||
<h1 className="my-3">Media</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="LIBRARY_INFO.MEDIA"/></h1>
|
||||
|
||||
|
||||
<div className="d-flex flex-column flex-md-row">
|
||||
<div className="d-flex flex-row w-100">
|
||||
<FormSelect onChange={(e) => sortOrderLogic(e.target.value) } className="my-md-3 w-100 rounded-0 rounded-start">
|
||||
<option value="Title">Title</option>
|
||||
<option value="Views">Views</option>
|
||||
<option value="WatchTime">Watch Time</option>
|
||||
<option value="Title"><Trans i18nKey="TITLE"/></option>
|
||||
<option value="Views"><Trans i18nKey="VIEWS"/></option>
|
||||
<option value="WatchTime"><Trans i18nKey="WATCH_TIME"/></option>
|
||||
</FormSelect>
|
||||
|
||||
<Button className="my-md-3 rounded-0 rounded-end" onClick={()=>setSortAsc(!sortAsc)}>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import "../../css/globalstats.css";
|
||||
|
||||
import WatchTimeStats from "./globalstats/watchtimestats";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function LibraryGlobalStats(props) {
|
||||
const [dayStats, setDayStats] = useState({});
|
||||
@@ -69,12 +70,12 @@ function LibraryGlobalStats(props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="my-3">Library Stats</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="LIBRARY_INFO.LIBRARY_STATS"/></h1>
|
||||
<div className="global-stats-container">
|
||||
<WatchTimeStats data={dayStats} heading={"Last 24 Hours"} />
|
||||
<WatchTimeStats data={weekStats} heading={"Last 7 Days"} />
|
||||
<WatchTimeStats data={monthStats} heading={"Last 30 Days"} />
|
||||
<WatchTimeStats data={allStats} heading={"All Time"} />
|
||||
<WatchTimeStats data={dayStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_24_HRS"/>} />
|
||||
<WatchTimeStats data={weekStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_7_DAYS"/>} />
|
||||
<WatchTimeStats data={monthStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_30_DAYS"/>} />
|
||||
<WatchTimeStats data={allStats} heading={<Trans i18nKey="GLOBAL_STATS.ALL_TIME"/>} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ import RecentlyAddedCard from "./RecentlyAdded/recently-added-card";
|
||||
|
||||
import "../../css/users/user-details.css";
|
||||
import ErrorBoundary from "../general/ErrorBoundary";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function RecentlyAdded(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -57,7 +58,7 @@ function RecentlyAdded(props) {
|
||||
|
||||
return (
|
||||
<div className="last-played">
|
||||
<h1 className="my-3">Recently Added</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="HOME_PAGE.RECENTLY_ADDED"/></h1>
|
||||
<div className="last-played-container">
|
||||
{data && data.map((item) => (
|
||||
<ErrorBoundary key={item.Id}>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useParams } from 'react-router-dom';
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import AccountCircleFillIcon from "remixicon-react/AccountCircleFillIcon";
|
||||
import Config from "../../lib/config";
|
||||
@@ -9,6 +9,7 @@ import GlobalStats from './user-info/globalStats';
|
||||
import LastPlayed from './user-info/lastplayed';
|
||||
import UserActivity from './user-info/user-activity';
|
||||
import "../css/users/user-details.css";
|
||||
import { Trans } from 'react-i18next';
|
||||
|
||||
|
||||
|
||||
@@ -93,8 +94,8 @@ function UserInfo() {
|
||||
<div>
|
||||
<p className="user-name">{data.Name}</p>
|
||||
<ButtonGroup>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'>Overview</Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'>Activity</Button>
|
||||
<Button onClick={() => setActiveTab('tabOverview')} active={activeTab==='tabOverview'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.OVERVIEW"/></Button>
|
||||
<Button onClick={() => setActiveTab('tabActivity')} active={activeTab==='tabActivity'} variant='outline-primary' type='button'><Trans i18nKey="TAB_CONTROLS.ACTIVITY"/></Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ import axios from "axios";
|
||||
import "../../css/globalstats.css";
|
||||
|
||||
import WatchTimeStats from "./globalstats/watchtimestats";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function GlobalStats(props) {
|
||||
const [dayStats, setDayStats] = useState({});
|
||||
@@ -69,12 +70,12 @@ function GlobalStats(props) {
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1 className="py-3">User Stats</h1>
|
||||
<h1 className="py-3"><Trans i18nKey="USERS_PAGE.USER_STATS"/></h1>
|
||||
<div className="global-stats-container">
|
||||
<WatchTimeStats data={dayStats} heading={"Last 24 Hours"} />
|
||||
<WatchTimeStats data={weekStats} heading={"Last 7 Days"} />
|
||||
<WatchTimeStats data={monthStats} heading={"Last 30 Days"} />
|
||||
<WatchTimeStats data={allStats} heading={"All Time"} />
|
||||
<WatchTimeStats data={dayStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_24_HRS"/>} />
|
||||
<WatchTimeStats data={weekStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_7_DAYS"/>} />
|
||||
<WatchTimeStats data={monthStats} heading={<Trans i18nKey="GLOBAL_STATS.LAST_30_DAYS"/>} />
|
||||
<WatchTimeStats data={allStats} heading={<Trans i18nKey="GLOBAL_STATS.ALL_TIME"/>} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
|
||||
import LastWatchedCard from "../general/last-watched-card";
|
||||
@@ -6,6 +6,7 @@ import ErrorBoundary from "../general/ErrorBoundary";
|
||||
|
||||
import Config from "../../../lib/config";
|
||||
import "../../css/users/user-details.css";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function LastPlayed(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -61,7 +62,7 @@ function LastPlayed(props) {
|
||||
|
||||
return (
|
||||
<div className="last-played">
|
||||
<h1 className="my-3">Last Watched</h1>
|
||||
<h1 className="my-3"><Trans i18nKey="LAST_WATCHED"/></h1>
|
||||
<div className="last-played-container">
|
||||
{data.map((item) => (
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import axios from "axios";
|
||||
import ActivityTable from "../activity/activity-table";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
function UserActivity(props) {
|
||||
const [data, setData] = useState();
|
||||
@@ -39,9 +40,9 @@ function UserActivity(props) {
|
||||
return (
|
||||
<div className="Activity">
|
||||
<div className="Heading">
|
||||
<h1>User Activity</h1>
|
||||
<h1><Trans i18nKey="USERS_PAGE.USER_ACTIVITY"/></h1>
|
||||
<div className="pagination-range">
|
||||
<div className="header">Items</div>
|
||||
<div className="header"><Trans i18nKey="UNITS.ITEMS"/></div>
|
||||
<select value={itemCount} onChange={(event) => {setItemCount(event.target.value);}}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
|
||||
@@ -19,6 +19,8 @@ import { visuallyHidden } from '@mui/utils';
|
||||
import "./css/users/users.css";
|
||||
|
||||
import Loading from "./components/general/loading";
|
||||
import i18next from "i18next";
|
||||
import { Trans } from "react-i18next";
|
||||
|
||||
const token = localStorage.getItem('token');
|
||||
|
||||
@@ -35,37 +37,37 @@ function EnhancedTableHead(props) {
|
||||
id: 'UserName',
|
||||
numeric: false,
|
||||
disablePadding: true,
|
||||
label: 'User',
|
||||
label: i18next.t("USER"),
|
||||
},
|
||||
{
|
||||
id: 'LastWatched',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Last Watched',
|
||||
label: i18next.t("LAST_WATCHED"),
|
||||
},
|
||||
{
|
||||
id: 'LastClient',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Last Client',
|
||||
label: i18next.t("USERS_PAGE.LAST_CLIENT"),
|
||||
},
|
||||
{
|
||||
id: 'TotalPlays',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Plays',
|
||||
label: i18next.t("UNITS.PLAYS"),
|
||||
},
|
||||
{
|
||||
id: 'TotalWatchTime',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Watch Time',
|
||||
label: i18next.t("WATCH_TIME"),
|
||||
},
|
||||
{
|
||||
id: 'LastSeen',
|
||||
numeric: false,
|
||||
disablePadding: false,
|
||||
label: 'Last Seen',
|
||||
label: i18next.t("USERS.LAST_SEEN"),
|
||||
},
|
||||
];
|
||||
|
||||
@@ -110,11 +112,11 @@ function Row(row) {
|
||||
let formattedTime='';
|
||||
if(hours)
|
||||
{
|
||||
formattedTime+=`${hours} hours`;
|
||||
formattedTime+=`${hours} ${i18next.t("UNITS.HOURS")}`;
|
||||
}
|
||||
if(minutes)
|
||||
{
|
||||
formattedTime+=` ${minutes} minutes`;
|
||||
formattedTime+=` ${minutes} ${i18next.t("UNITS.MINUTES")}`;
|
||||
}
|
||||
|
||||
return formattedTime ;
|
||||
@@ -122,10 +124,10 @@ function Row(row) {
|
||||
|
||||
function formatLastSeenTime(time) {
|
||||
const units = {
|
||||
days: ['Day', 'Days'],
|
||||
hours: ['Hour', 'Hours'],
|
||||
minutes: ['Minute', 'Minutes'],
|
||||
seconds: ['Second', 'Seconds']
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")],
|
||||
seconds: [i18next.t("UNITS.SECOND"), i18next.t("UNITS.SECONDS")]
|
||||
};
|
||||
|
||||
let formattedTime = '';
|
||||
@@ -137,7 +139,7 @@ function Row(row) {
|
||||
}
|
||||
}
|
||||
|
||||
return `${formattedTime}ago`;
|
||||
return `${formattedTime+i18next.t("AGO").toLowerCase()}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -160,11 +162,11 @@ function Row(row) {
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell><Link to={`/users/${data.UserId}`} className="text-decoration-none">{data.UserName}</Link></TableCell>
|
||||
<TableCell><Link to={`/libraries/item/${data.NowPlayingItemId}`} className="text-decoration-none">{data.LastWatched || 'never'}</Link></TableCell>
|
||||
<TableCell>{data.LastClient || 'n/a'}</TableCell>
|
||||
<TableCell style={{textTransform:data.LastWatched ? 'none':'lowercase'}}><Link to={`/libraries/item/${data.NowPlayingItemId}`} className="text-decoration-none">{data.LastWatched || i18next.t("ERROR_MESSAGES.NEVER")}</Link></TableCell>
|
||||
<TableCell style={{textTransform:data.LastClient ? 'none':'lowercase'}}>{data.LastClient || i18next.t("ERROR_MESSAGES.N/A")}</TableCell>
|
||||
<TableCell>{data.TotalPlays}</TableCell>
|
||||
<TableCell>{formatTotalWatchTime(data.TotalWatchTime) || '0 minutes'}</TableCell>
|
||||
<TableCell>{data.LastSeen ? formatLastSeenTime(data.LastSeen) : 'never'}</TableCell>
|
||||
<TableCell>{formatTotalWatchTime(data.TotalWatchTime) || `0 ${i18next.t("UNITS.MINUTES")}`}</TableCell>
|
||||
<TableCell style={{textTransform: data.LastSeen ? 'none' :'lowercase'}}>{data.LastSeen ? formatLastSeenTime(data.LastSeen) : i18next.t("ERROR_MESSAGES.NEVER")}</TableCell>
|
||||
|
||||
</TableRow>
|
||||
</React.Fragment>
|
||||
@@ -248,10 +250,10 @@ function Users() {
|
||||
return ' never';
|
||||
}
|
||||
const units = {
|
||||
days: ['Day', 'Days'],
|
||||
hours: ['Hour', 'Hours'],
|
||||
minutes: ['Minute', 'Minutes'],
|
||||
seconds: ['Second', 'Seconds']
|
||||
days: [i18next.t("UNITS.DAY"), i18next.t("UNITS.DAYS")],
|
||||
hours: [i18next.t("UNITS.HOUR"), i18next.t("UNITS.HOUR")],
|
||||
minutes: [i18next.t("UNITS.MINUTE"), i18next.t("UNITS.MINUTES")],
|
||||
seconds: [i18next.t("UNITS.SECOND"), i18next.t("UNITS.SECONDS")]
|
||||
};
|
||||
|
||||
let formattedTime = '';
|
||||
@@ -263,7 +265,7 @@ function Users() {
|
||||
}
|
||||
}
|
||||
|
||||
return `${formattedTime}ago`;
|
||||
return `${formattedTime+i18next.t("AGO").toLowerCase()}`;
|
||||
}
|
||||
|
||||
|
||||
@@ -343,9 +345,9 @@ function Users() {
|
||||
return (
|
||||
<div className="Users">
|
||||
<div className="Heading py-2">
|
||||
<h1 >All Users</h1>
|
||||
<h1><Trans i18nKey="USERS_PAGE.ALL_USERS"/></h1>
|
||||
<div className="pagination-range">
|
||||
<div className="header">Items</div>
|
||||
<div className="header"><Trans i18nKey="UNITS.ITEMS"/></div>
|
||||
<select value={itemCount} onChange={(event) => {setRowsPerPage(event.target.value); setPage(0); setItemCount(event.target.value);}}>
|
||||
<option value="10">10</option>
|
||||
<option value="25">25</option>
|
||||
@@ -376,21 +378,21 @@ function Users() {
|
||||
<div className='d-flex justify-content-end my-2'>
|
||||
<ButtonGroup className="pagination-buttons">
|
||||
<Button className="page-btn" onClick={()=>setPage(0)} disabled={page === 0}>
|
||||
First
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.FIRST"/>
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={handlePreviousPageClick} disabled={page === 0}>
|
||||
Previous
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.PREVIOUS"/>
|
||||
</Button>
|
||||
|
||||
<div className="page-number d-flex align-items-center justify-content-center">{`${page *rowsPerPage + 1}-${Math.min((page * rowsPerPage+ 1 ) + (rowsPerPage - 1),data.length)} of ${data.length}`}</div>
|
||||
|
||||
<Button className="page-btn" onClick={handleNextPageClick} disabled={page >= Math.ceil(data.length / rowsPerPage) - 1}>
|
||||
Next
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.NEXT"/>
|
||||
</Button>
|
||||
|
||||
<Button className="page-btn" onClick={()=>setPage(Math.ceil(data.length / rowsPerPage) - 1)} disabled={page >= Math.ceil(data.length / rowsPerPage) - 1}>
|
||||
Last
|
||||
<Trans i18nKey="TABLE_NAV_BUTTONS.LAST"/>
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user