diff --git a/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx new file mode 100644 index 000000000..12a58a60e --- /dev/null +++ b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx @@ -0,0 +1,95 @@ +import Book from '@mui/icons-material/Book'; +import Movie from '@mui/icons-material/Movie'; +import MusicNote from '@mui/icons-material/MusicNote'; +import MusicVideo from '@mui/icons-material/MusicVideo'; +import Tv from '@mui/icons-material/Tv'; +import VideoLibrary from '@mui/icons-material/VideoLibrary'; +import Grid from '@mui/material/Grid2'; +import React from 'react'; + +import { useItemCounts } from 'apps/dashboard/features/metrics/api/useItemCounts'; +import MetricCard from 'apps/dashboard/features/metrics/components/MetricCard'; +import globalize from 'lib/globalize'; + +const ItemCountsWidget = () => { + const { data: counts } = useItemCounts(); + + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default ItemCountsWidget; diff --git a/src/apps/dashboard/controllers/dashboard.html b/src/apps/dashboard/controllers/dashboard.html index 55906b688..e0656d1cc 100644 --- a/src/apps/dashboard/controllers/dashboard.html +++ b/src/apps/dashboard/controllers/dashboard.html @@ -20,13 +20,13 @@
- - -
@@ -36,6 +36,8 @@
+ +
diff --git a/src/apps/dashboard/controllers/dashboard.js b/src/apps/dashboard/controllers/dashboard.js index 8e91a8f33..c9dd75b97 100644 --- a/src/apps/dashboard/controllers/dashboard.js +++ b/src/apps/dashboard/controllers/dashboard.js @@ -33,6 +33,7 @@ import 'elements/emby-itemscontainer/emby-itemscontainer'; import 'components/listview/listview.scss'; import 'styles/flexstyles.scss'; import './dashboard.scss'; +import ItemCountsWidget from '../components/widgets/ItemCountsWidget'; function showPlaybackInfo(btn, session) { let title; @@ -748,7 +749,7 @@ const DashboardPage = { export default function (view) { const serverId = ApiClient.serverId(); - let unmountPathsWidget; + let unmountWidgetFns = []; function onRestartRequired(evt, apiClient) { console.debug('onRestartRequired not implemented', evt, apiClient); @@ -824,7 +825,8 @@ export default function (view) { button: page.querySelector('.btnRefresh') }); - unmountPathsWidget = renderComponent(ServerPathWidget, {}, page.querySelector('#serverPaths')); + unmountWidgetFns.push(renderComponent(ItemCountsWidget, {}, page.querySelector('#itemCounts'))); + unmountWidgetFns.push(renderComponent(ServerPathWidget, {}, page.querySelector('#serverPaths'))); page.querySelector('#btnRestartServer').addEventListener('click', DashboardPage.restart); page.querySelector('#btnShutdown').addEventListener('click', DashboardPage.shutdown); @@ -852,7 +854,10 @@ export default function (view) { button: page.querySelector('.btnRefresh') }); - if (unmountPathsWidget) unmountPathsWidget(); + unmountWidgetFns.forEach(unmount => { + unmount(); + }); + unmountWidgetFns = []; page.querySelector('#btnRestartServer').removeEventListener('click', DashboardPage.restart); page.querySelector('#btnShutdown').removeEventListener('click', DashboardPage.shutdown); diff --git a/src/apps/dashboard/features/metrics/api/useItemCounts.ts b/src/apps/dashboard/features/metrics/api/useItemCounts.ts new file mode 100644 index 000000000..ee2ef772d --- /dev/null +++ b/src/apps/dashboard/features/metrics/api/useItemCounts.ts @@ -0,0 +1,33 @@ +import type { Api } from '@jellyfin/sdk'; +import type { LibraryApiGetItemCountsRequest } from '@jellyfin/sdk/lib/generated-client/api/library-api'; +import { getLibraryApi } from '@jellyfin/sdk/lib/utils/api/library-api'; +import { queryOptions, useQuery } from '@tanstack/react-query'; +import type { AxiosRequestConfig } from 'axios'; + +import { useApi } from 'hooks/useApi'; + +const fetchItemCounts = async ( + api: Api, + params?: LibraryApiGetItemCountsRequest, + options?: AxiosRequestConfig +) => { + const response = await getLibraryApi(api) + .getItemCounts(params, options); + return response.data; +}; + +const getItemCountsQuery = ( + api?: Api, + params?: LibraryApiGetItemCountsRequest +) => queryOptions({ + queryKey: [ 'ItemCounts', params ], + queryFn: ({ signal }) => fetchItemCounts(api!, params, { signal }), + enabled: !!api +}); + +export const useItemCounts = ( + params?: LibraryApiGetItemCountsRequest +) => { + const { api } = useApi(); + return useQuery(getItemCountsQuery(api, params)); +}; diff --git a/src/apps/dashboard/features/metrics/components/MetricCard.tsx b/src/apps/dashboard/features/metrics/components/MetricCard.tsx new file mode 100644 index 000000000..102d9da73 --- /dev/null +++ b/src/apps/dashboard/features/metrics/components/MetricCard.tsx @@ -0,0 +1,71 @@ +import Box from '@mui/material/Box'; +import Card from '@mui/material/Card'; +import Skeleton from '@mui/material/Skeleton'; +import Stack from '@mui/material/Stack'; +import SvgIcon from '@mui/material/SvgIcon'; +import Typography from '@mui/material/Typography'; +import React, { type FC } from 'react'; + +import { useLocale } from 'hooks/useLocale'; +import { toLocaleString } from 'utils/number'; + +interface Metric { + label: string + value?: number +} + +interface MetricCardProps { + metrics: Metric[] + Icon: typeof SvgIcon +} + +const MetricCard: FC = ({ + metrics, + Icon +}) => { + const { dateTimeLocale } = useLocale(); + + return ( + + + {metrics.map(({ label, value }) => ( + + + {label} + + + {value ? ( + toLocaleString(value, dateTimeLocale) + ) : ( + + )} + + + ))} + + + + ); +}; + +export default MetricCard; diff --git a/src/hooks/useLocale.tsx b/src/hooks/useLocale.tsx index a7b8bc625..63bbb5818 100644 --- a/src/hooks/useLocale.tsx +++ b/src/hooks/useLocale.tsx @@ -11,11 +11,11 @@ export function useLocale() { const { dateTimeLocale: dateTimeSetting, language } = useUserSettings(); const [ dateFnsLocale, setDateFnsLocale ] = useState(enUS); - const locale = useMemo(() => ( + const locale: string = useMemo(() => ( normalizeLocaleName(language || getDefaultLanguage()) ), [ language ]); - const dateTimeLocale = useMemo(() => ( + const dateTimeLocale: string = useMemo(() => ( dateTimeSetting ? normalizeLocaleName(dateTimeSetting) : locale ), [ dateTimeSetting, locale ]); diff --git a/src/themes/dark/theme.scss b/src/themes/dark/theme.scss index f055a4cfc..b64687592 100644 --- a/src/themes/dark/theme.scss +++ b/src/themes/dark/theme.scss @@ -91,8 +91,8 @@ html { } .button-delete { - background: rgb(247, 0, 0); - color: rgba(255, 255, 255, 0.87); + background: #cb272a; + color: #fff; } .checkboxLabel { diff --git a/src/utils/number.ts b/src/utils/number.ts index af7e5f730..d411addcd 100644 --- a/src/utils/number.ts +++ b/src/utils/number.ts @@ -13,6 +13,20 @@ export function randomInt(min: number, max: number): number { return Math.floor(Math.random() * (max - min + 1)) + min; } +/** + * Gets the value of a number formatted as a string. + * @param {number} value The value as a number. + * @param {string} locale The locale to use for formatting (i.e. en-us). + * @returns {string} The value formatted as a string. + */ +export function toLocaleString(value: number, locale: string): string { + if (toLocaleStringSupportsOptions()) { + return value.toLocaleString(locale); + } + + return value.toString(); +} + /** * Gets the value of a number formatted as a perentage. * @param {number} value The value as a number.