mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-20 12:11:28 -07:00
feat: overhaul stats dashboard with navigation, trends, and anime views
Add navigation state machine for tab/detail routing, anime overview stats with Yomitan lookup rates, session word count accuracy fixes, vocabulary tab hook order fix, simplified trends data fetching from backend-aggregated endpoints, and improved session detail charts.
This commit is contained in:
@@ -1,16 +1,44 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { CoverImage } from './CoverImage';
|
||||
import { formatDuration, formatNumber, formatPercent } from '../../lib/formatters';
|
||||
import { getStatsClient } from '../../hooks/useStatsApi';
|
||||
import { buildLookupRateDisplay } from '../../lib/yomitan-lookup';
|
||||
import type { MediaDetailData } from '../../types/stats';
|
||||
|
||||
interface MediaHeaderProps {
|
||||
detail: NonNullable<MediaDetailData['detail']>;
|
||||
initialKnownWordsSummary?: {
|
||||
totalUniqueWords: number;
|
||||
knownWordCount: number;
|
||||
} | null;
|
||||
}
|
||||
|
||||
export function MediaHeader({ detail }: MediaHeaderProps) {
|
||||
const hitRate =
|
||||
export function MediaHeader({ detail, initialKnownWordsSummary = null }: MediaHeaderProps) {
|
||||
const knownTokenRate =
|
||||
detail.totalLookupCount > 0 ? detail.totalLookupHits / detail.totalLookupCount : null;
|
||||
const avgSessionMs =
|
||||
detail.totalSessions > 0 ? Math.round(detail.totalActiveMs / detail.totalSessions) : 0;
|
||||
const lookupRate = buildLookupRateDisplay(detail.totalYomitanLookupCount, detail.totalWordsSeen);
|
||||
|
||||
const [knownWordsSummary, setKnownWordsSummary] = useState<{
|
||||
totalUniqueWords: number;
|
||||
knownWordCount: number;
|
||||
} | null>(initialKnownWordsSummary);
|
||||
|
||||
useEffect(() => {
|
||||
let cancelled = false;
|
||||
getStatsClient()
|
||||
.getMediaKnownWordsSummary(detail.videoId)
|
||||
.then((data) => {
|
||||
if (!cancelled) setKnownWordsSummary(data);
|
||||
})
|
||||
.catch(() => {
|
||||
if (!cancelled) setKnownWordsSummary(null);
|
||||
});
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [detail.videoId]);
|
||||
|
||||
return (
|
||||
<div className="flex gap-4">
|
||||
@@ -32,12 +60,37 @@ export function MediaHeader({ detail }: MediaHeaderProps) {
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-ctp-mauve font-medium">{formatNumber(detail.totalWordsSeen)}</div>
|
||||
<div className="text-xs text-ctp-overlay2">words seen</div>
|
||||
<div className="text-xs text-ctp-overlay2">word occurrences</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-ctp-peach font-medium">{formatPercent(hitRate)}</div>
|
||||
<div className="text-xs text-ctp-overlay2">lookup rate</div>
|
||||
<div className="text-ctp-lavender font-medium">
|
||||
{formatNumber(detail.totalYomitanLookupCount)}
|
||||
</div>
|
||||
<div className="text-xs text-ctp-overlay2">Yomitan lookups</div>
|
||||
</div>
|
||||
<div>
|
||||
<div className="text-ctp-sapphire font-medium">
|
||||
{lookupRate?.shortValue ?? '\u2014'}
|
||||
</div>
|
||||
<div className="text-xs text-ctp-overlay2">
|
||||
{lookupRate?.longValue ?? 'lookup rate'}
|
||||
</div>
|
||||
</div>
|
||||
{knownWordsSummary && knownWordsSummary.totalUniqueWords > 0 ? (
|
||||
<div>
|
||||
<div className="text-ctp-green font-medium">
|
||||
{formatNumber(knownWordsSummary.knownWordCount)} / {formatNumber(knownWordsSummary.totalUniqueWords)}
|
||||
</div>
|
||||
<div className="text-xs text-ctp-overlay2">
|
||||
known unique words ({Math.round((knownWordsSummary.knownWordCount / knownWordsSummary.totalUniqueWords) * 100)}%)
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div>
|
||||
<div className="text-ctp-peach font-medium">{formatPercent(knownTokenRate)}</div>
|
||||
<div className="text-xs text-ctp-overlay2">known token match rate</div>
|
||||
</div>
|
||||
)}
|
||||
<div>
|
||||
<div className="text-ctp-text font-medium">{detail.totalSessions}</div>
|
||||
<div className="text-xs text-ctp-overlay2">sessions</div>
|
||||
|
||||
Reference in New Issue
Block a user