feat(ui): make stat numbers clickable to open games
Build, Test & Push Frontend / quality-check (push) Successful in 28s
Build, Test & Push Frontend / docker (push) Successful in 26s

StatDrill context holds one dialog for a page. StatCount wraps a number:
total and status counts link to the library filter, other numbers open
StatGamesDialog. DrillRow and DrillButton support drill-down in tag bars
(genres, platforms, moods) and other stat displays. Dialog shows 40 games
per page with bottom-sheet layout on phones, handles pagination and errors
(403 not shared, 401 expired, 400 bad argument).
This commit is contained in:
lobo committed 2026-10-03 18:51:59 +02:00
1 parent ed5df8c40d
commit 777ba87fd6
31 files changed
+1026 -261

No files matched your search

+16 -4
View File
@@ -6,10 +6,12 @@ import {Button} from '@/components/ui/button';
import {Card} from '@/components/ui/card';
import GameCover from './GameCover.jsx';
import PileTrend from './stats/PileTrend.jsx';
import {StatCount} from './stats/StatDrill.jsx';
import {STATUS_META} from './GameStatusBadge.jsx';
import {backlogShare} from '../utils/backlog.js';
import {GAME_STATUSES} from '../utils/limits.js';
import {formatPlaytime} from '../utils/games.js';
import {libraryLink} from '../utils/statGames.js';
/**
* @param trend the TREND group of GET /api/library/stats; the card shows no trend while it is missing
@@ -34,17 +36,25 @@ export default function PileOfShameCard({summary, trend, syncing, onShuffle, shu
<div>
<div className="flex items-baseline gap-2">
<span className="text-4xl font-bold tabular-nums">{backlog.toLocaleString(i18n.language)}</span>
<StatCount to={libraryLink('BACKLOG')} title={t('DASHBOARD.PILE_OF_SHAME')} params={{key: 'PILE'}}
label={`${t('BACKLOG.STATUS.BACKLOG')}: ${backlog}`}>
<span className="text-4xl font-bold tabular-nums">{backlog.toLocaleString(i18n.language)}</span>
</StatCount>
<span className="text-sm text-muted-foreground">
{t('DASHBOARD.PILE_SHARE', {percent: backlogShare(summary)})}
</span>
</div>
<p className="mt-1 text-sm text-muted-foreground">
{t('DASHBOARD.NEVER_PLAYED', {count: summary.neverPlayed})}
<StatCount params={summary.neverPlayed > 0 ? {key: 'NEVER_STARTED'} : null}
title={t('STATS.OVERVIEW.NEVER_STARTED')}>
{t('DASHBOARD.NEVER_PLAYED', {count: summary.neverPlayed})}
</StatCount>
</p>
{summary.backlogMinutes > 0 && (
<p className="text-sm text-muted-foreground">
{t('DASHBOARD.BACKLOG_HOURS', {time: formatPlaytime(summary.backlogMinutes)})}
<StatCount params={{key: 'PILE_TIME_TO_BEAT'}} title={t('STATS.PILE.NORMAL')}>
{t('DASHBOARD.BACKLOG_HOURS', {time: formatPlaytime(summary.backlogMinutes)})}
</StatCount>
</p>
)}
</div>
@@ -67,7 +77,9 @@ export default function PileOfShameCard({summary, trend, syncing, onShuffle, shu
<li key={s} className="flex items-center gap-1.5">
<span className={`size-2 rounded-full ${STATUS_META[s].bar}`}/>
{t(`BACKLOG.STATUS.${s}`)}
<span className="tabular-nums text-foreground">{summary.byStatus[s]}</span>
<StatCount to={libraryLink(s)} label={`${t(`BACKLOG.STATUS.${s}`)}: ${summary.byStatus[s]}`}>
<span className="tabular-nums text-foreground">{summary.byStatus[s]}</span>
</StatCount>
</li>
))}
</ul>