mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-21 00:11:27 -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:
139
stats/src/lib/stats-navigation.ts
Normal file
139
stats/src/lib/stats-navigation.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
import type { SessionSummary } from '../types/stats';
|
||||
import type { TabId } from '../components/layout/TabBar';
|
||||
|
||||
export type MediaDetailOrigin = { type: 'anime'; animeId: number } | { type: 'overview' };
|
||||
|
||||
export interface MediaDetailState {
|
||||
videoId: number;
|
||||
initialSessionId: number | null;
|
||||
origin: MediaDetailOrigin;
|
||||
}
|
||||
|
||||
export interface StatsViewState {
|
||||
activeTab: TabId;
|
||||
selectedAnimeId: number | null;
|
||||
focusedSessionId: number | null;
|
||||
mediaDetail: MediaDetailState | null;
|
||||
}
|
||||
|
||||
export function createInitialStatsView(): StatsViewState {
|
||||
return {
|
||||
activeTab: 'overview',
|
||||
selectedAnimeId: null,
|
||||
focusedSessionId: null,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function switchTab(state: StatsViewState, tabId: TabId): StatsViewState {
|
||||
return {
|
||||
activeTab: tabId,
|
||||
selectedAnimeId: null,
|
||||
focusedSessionId: tabId === 'sessions' ? state.focusedSessionId : null,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function navigateToAnime(state: StatsViewState, animeId: number): StatsViewState {
|
||||
return {
|
||||
...state,
|
||||
activeTab: 'anime',
|
||||
selectedAnimeId: animeId,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function navigateToSession(state: StatsViewState, sessionId: number): StatsViewState {
|
||||
return {
|
||||
...state,
|
||||
activeTab: 'sessions',
|
||||
focusedSessionId: sessionId,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function openAnimeEpisodeDetail(
|
||||
state: StatsViewState,
|
||||
animeId: number,
|
||||
videoId: number,
|
||||
sessionId: number | null = null,
|
||||
): StatsViewState {
|
||||
return {
|
||||
activeTab: 'anime',
|
||||
selectedAnimeId: animeId,
|
||||
focusedSessionId: null,
|
||||
mediaDetail: {
|
||||
videoId,
|
||||
initialSessionId: sessionId,
|
||||
origin: {
|
||||
type: 'anime',
|
||||
animeId,
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function openOverviewMediaDetail(
|
||||
state: StatsViewState,
|
||||
videoId: number,
|
||||
sessionId: number | null = null,
|
||||
): StatsViewState {
|
||||
return {
|
||||
activeTab: 'overview',
|
||||
selectedAnimeId: null,
|
||||
focusedSessionId: null,
|
||||
mediaDetail: {
|
||||
videoId,
|
||||
initialSessionId: sessionId,
|
||||
origin: {
|
||||
type: 'overview',
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function closeMediaDetail(state: StatsViewState): StatsViewState {
|
||||
if (!state.mediaDetail) {
|
||||
return state;
|
||||
}
|
||||
|
||||
if (state.mediaDetail.origin.type === 'overview') {
|
||||
return {
|
||||
activeTab: 'overview',
|
||||
selectedAnimeId: null,
|
||||
focusedSessionId: null,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
activeTab: 'anime',
|
||||
selectedAnimeId: state.mediaDetail.origin.animeId,
|
||||
focusedSessionId: null,
|
||||
mediaDetail: null,
|
||||
};
|
||||
}
|
||||
|
||||
export function getSessionNavigationTarget(session: Pick<SessionSummary, 'sessionId' | 'videoId'>):
|
||||
| {
|
||||
type: 'media-detail';
|
||||
videoId: number;
|
||||
sessionId: number;
|
||||
}
|
||||
| {
|
||||
type: 'session';
|
||||
sessionId: number;
|
||||
} {
|
||||
if (session.videoId != null) {
|
||||
return {
|
||||
type: 'media-detail',
|
||||
videoId: session.videoId,
|
||||
sessionId: session.sessionId,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
type: 'session',
|
||||
sessionId: session.sessionId,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user