mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-03-21 00:11:27 -07:00
feat(stats): build anime-centric stats dashboard frontend
5-tab React dashboard with Catppuccin Mocha theme: - Overview: hero stats, streak calendar, watch time chart, recent sessions - Anime: grid with cover art, episode list with completion %, detail view - Trends: 15 charts across Activity, Efficiency, Anime, and Patterns - Vocabulary: POS-filtered word/kanji lists with detail panels - Sessions: expandable session history with event timeline Features: - Cross-tab navigation (anime <-> vocabulary) - Global word detail panel overlay - Expandable episode detail with Anki card links (Expression field) - Per-anime multi-line trend charts - Watch time by day-of-week and hour-of-day - Collapsible sections with accessibility (aria-expanded) - Card size selector for anime grid - Cover art caching via AniList - HTTP API client with file:// protocol fallback for Electron overlay
This commit is contained in:
46
stats/src/components/overview/QuickStats.tsx
Normal file
46
stats/src/components/overview/QuickStats.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { todayLocalDay } from '../../lib/formatters';
|
||||
import type { DailyRollup } from '../../types/stats';
|
||||
|
||||
interface QuickStatsProps {
|
||||
rollups: DailyRollup[];
|
||||
}
|
||||
|
||||
export function QuickStats({ rollups }: QuickStatsProps) {
|
||||
const daysWithActivity = new Set(
|
||||
rollups.filter((r) => r.totalActiveMin > 0).map((r) => r.rollupDayOrMonth),
|
||||
);
|
||||
const today = todayLocalDay();
|
||||
const streakStart = daysWithActivity.has(today) ? today : today - 1;
|
||||
let streak = 0;
|
||||
for (let d = streakStart; daysWithActivity.has(d); d--) {
|
||||
streak++;
|
||||
}
|
||||
|
||||
const weekStart = today - 6;
|
||||
const weekRollups = rollups.filter((r) => r.rollupDayOrMonth >= weekStart);
|
||||
const weekMinutes = weekRollups.reduce((sum, r) => sum + r.totalActiveMin, 0);
|
||||
const weekCards = weekRollups.reduce((sum, r) => sum + r.totalCards, 0);
|
||||
const avgMinPerDay = Math.round(weekMinutes / 7);
|
||||
|
||||
return (
|
||||
<div className="bg-ctp-surface0 border border-ctp-surface1 rounded-lg p-4">
|
||||
<h3 className="text-sm font-semibold text-ctp-text mb-3">Quick Stats</h3>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-ctp-subtext0">Streak</span>
|
||||
<span className="text-ctp-peach font-medium">
|
||||
{streak} day{streak !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-ctp-subtext0">Avg/day this week</span>
|
||||
<span className="text-ctp-text">{avgMinPerDay}m</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-ctp-subtext0">Cards this week</span>
|
||||
<span className="text-ctp-green font-medium">{weekCards}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user