From 3c29b1ad5de038bc6a51ec7f642b9c67ead9d2bc Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Fri, 13 Jun 2025 16:23:35 -0400 Subject: [PATCH 1/2] Hide metric cards if value is zero --- .../components/widgets/ItemCountsWidget.tsx | 154 ++++++++++-------- .../metrics/components/MetricCard.tsx | 2 +- 2 files changed, 87 insertions(+), 69 deletions(-) diff --git a/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx index 12a58a60e..f4a5e7987 100644 --- a/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx +++ b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx @@ -5,14 +5,89 @@ 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 React, { useMemo } from 'react'; import { useItemCounts } from 'apps/dashboard/features/metrics/api/useItemCounts'; -import MetricCard from 'apps/dashboard/features/metrics/components/MetricCard'; +import MetricCard, { type MetricCardProps } from 'apps/dashboard/features/metrics/components/MetricCard'; import globalize from 'lib/globalize'; const ItemCountsWidget = () => { - const { data: counts } = useItemCounts(); + const { + data: counts, + isPending + } = useItemCounts(); + + const cards: MetricCardProps[] = useMemo(() => { + const cardProps: MetricCardProps[] = []; + + if (isPending || counts?.MovieCount) { + cardProps.push({ + Icon: Movie, + metrics: [{ + label: globalize.translate('Movies'), + value: counts?.MovieCount + }] + }); + } + + if (isPending || counts?.SeriesCount || counts?.EpisodeCount) { + cardProps.push({ + Icon: Tv, + metrics: [{ + label: globalize.translate('Series'), + value: counts?.SeriesCount + }, { + label: globalize.translate('Episodes'), + value: counts?.EpisodeCount + }] + }); + } + + if (isPending || counts?.AlbumCount || counts?.SongCount) { + cardProps.push({ + Icon: MusicNote, + metrics: [{ + label: globalize.translate('Albums'), + value: counts?.AlbumCount + }, { + label: globalize.translate('Songs'), + value: counts?.SongCount + }] + }); + } + + if (isPending || counts?.MusicVideoCount) { + cardProps.push({ + Icon: MusicVideo, + metrics: [{ + label: globalize.translate('MusicVideos'), + value: counts?.MusicVideoCount + }] + }); + } + + if (isPending || counts?.BookCount) { + cardProps.push({ + Icon: Book, + metrics: [{ + label: globalize.translate('Books'), + value: counts?.BookCount + }] + }); + } + + if (isPending || counts?.BoxSetCount) { + cardProps.push({ + Icon: VideoLibrary, + metrics: [{ + label: globalize.translate('Collections'), + value: counts?.BoxSetCount + }] + }); + } + + return cardProps; + }, [ counts, isPending ]); return ( { marginTop: 2 }} > - - - - - - - - - - - - - - - - - - - - - - - + {cards.map(card => ( + metric.label).join('-')} + size={{ xs: 12, sm: 6, lg: 4 }} + > + + + ))} ); }; diff --git a/src/apps/dashboard/features/metrics/components/MetricCard.tsx b/src/apps/dashboard/features/metrics/components/MetricCard.tsx index 5ab87bc16..f251d7aab 100644 --- a/src/apps/dashboard/features/metrics/components/MetricCard.tsx +++ b/src/apps/dashboard/features/metrics/components/MetricCard.tsx @@ -14,7 +14,7 @@ interface Metric { value?: number } -interface MetricCardProps { +export interface MetricCardProps { metrics: Metric[] Icon: typeof SvgIcon } From e9747adf4892a1927526290205f79f23f310b88f Mon Sep 17 00:00:00 2001 From: Bill Thornton Date: Wed, 25 Jun 2025 17:37:57 -0400 Subject: [PATCH 2/2] Refactor ItemCountsWidget to reduce duplication --- .../components/widgets/ItemCountsWidget.tsx | 123 ++++++++---------- 1 file changed, 54 insertions(+), 69 deletions(-) diff --git a/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx index f4a5e7987..1488b5ebf 100644 --- a/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx +++ b/src/apps/dashboard/components/widgets/ItemCountsWidget.tsx @@ -1,3 +1,4 @@ +import type { ItemCounts } from '@jellyfin/sdk/lib/generated-client/models/item-counts'; import Book from '@mui/icons-material/Book'; import Movie from '@mui/icons-material/Movie'; import MusicNote from '@mui/icons-material/MusicNote'; @@ -5,12 +6,51 @@ 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 SvgIcon from '@mui/material/SvgIcon'; import React, { useMemo } from 'react'; import { useItemCounts } from 'apps/dashboard/features/metrics/api/useItemCounts'; import MetricCard, { type MetricCardProps } from 'apps/dashboard/features/metrics/components/MetricCard'; import globalize from 'lib/globalize'; +interface MetricDefinition { + key: keyof ItemCounts + i18n: string +} + +interface CardDefinition { + Icon: typeof SvgIcon + metrics: MetricDefinition[] +} + +const CARD_DEFINITIONS: CardDefinition[] = [ + { + Icon: Movie, + metrics: [{ key: 'MovieCount', i18n: 'Movies' }] + }, { + Icon: Tv, + metrics: [ + { key: 'SeriesCount', i18n: 'Series' }, + { key: 'EpisodeCount', i18n: 'Episodes' } + ] + }, { + Icon: MusicNote, + metrics: [ + { key: 'AlbumCount', i18n: 'Albums' }, + { key: 'SongCount', i18n: 'Songs' } + ] + }, { + Icon: MusicVideo, + metrics: [{ key: 'MusicVideoCount', i18n: 'MusicVideos' }] + }, { + Icon: Book, + metrics: [{ key: 'BookCount', i18n: 'Books' }] + }, { + Icon: VideoLibrary, + metrics: [{ key: 'BoxSetCount', i18n: 'Collections' }] + } +]; + const ItemCountsWidget = () => { const { data: counts, @@ -18,75 +58,20 @@ const ItemCountsWidget = () => { } = useItemCounts(); const cards: MetricCardProps[] = useMemo(() => { - const cardProps: MetricCardProps[] = []; - - if (isPending || counts?.MovieCount) { - cardProps.push({ - Icon: Movie, - metrics: [{ - label: globalize.translate('Movies'), - value: counts?.MovieCount - }] - }); - } - - if (isPending || counts?.SeriesCount || counts?.EpisodeCount) { - cardProps.push({ - Icon: Tv, - metrics: [{ - label: globalize.translate('Series'), - value: counts?.SeriesCount - }, { - label: globalize.translate('Episodes'), - value: counts?.EpisodeCount - }] - }); - } - - if (isPending || counts?.AlbumCount || counts?.SongCount) { - cardProps.push({ - Icon: MusicNote, - metrics: [{ - label: globalize.translate('Albums'), - value: counts?.AlbumCount - }, { - label: globalize.translate('Songs'), - value: counts?.SongCount - }] - }); - } - - if (isPending || counts?.MusicVideoCount) { - cardProps.push({ - Icon: MusicVideo, - metrics: [{ - label: globalize.translate('MusicVideos'), - value: counts?.MusicVideoCount - }] - }); - } - - if (isPending || counts?.BookCount) { - cardProps.push({ - Icon: Book, - metrics: [{ - label: globalize.translate('Books'), - value: counts?.BookCount - }] - }); - } - - if (isPending || counts?.BoxSetCount) { - cardProps.push({ - Icon: VideoLibrary, - metrics: [{ - label: globalize.translate('Collections'), - value: counts?.BoxSetCount - }] - }); - } - - return cardProps; + return CARD_DEFINITIONS + .filter(def => ( + // Include all cards while the request is pending + isPending + // Check if the metrics are present in counts + || def.metrics.some(({ key }) => counts?.[key]) + )) + .map(({ Icon, metrics }) => ({ + Icon, + metrics: metrics.map(({ i18n, key }) => ({ + label: globalize.translate(i18n), + value: counts?.[key] + })) + })); }, [ counts, isPending ]); return (