diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-02-05 23:54:46 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-02-05 23:54:46 +0900 |
| commit | 24da9e91b9d5284a104cc207e59a619f3c48bb7f (patch) | |
| tree | 9970fd96d447d25361329b11a32981ccdc1c1499 /src/client/pages/DeckDetailPage.tsx | |
| parent | 19bf3a9b2cf91e49af8c70f974a5b3fcf2bcd869 (diff) | |
| download | kioku-24da9e91b9d5284a104cc207e59a619f3c48bb7f.tar.gz kioku-24da9e91b9d5284a104cc207e59a619f3c48bb7f.tar.zst kioku-24da9e91b9d5284a104cc207e59a619f3c48bb7f.zip | |
feat(deck): display card counts by state on deck detail page
Show separate counts for New, Learning, and Review cards instead of
a single "Due Today" count. Uses FSRS CardState to categorize cards
with color-coded display (blue/orange/green).
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/client/pages/DeckDetailPage.tsx')
| -rw-r--r-- | src/client/pages/DeckDetailPage.tsx | 56 |
1 files changed, 46 insertions, 10 deletions
diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx index 792bbe2..6bc89ba 100644 --- a/src/client/pages/DeckDetailPage.tsx +++ b/src/client/pages/DeckDetailPage.tsx @@ -25,6 +25,14 @@ function DeckHeader({ deckId }: { deckId: string }) { ); } +// CardState values from FSRS +const CardState = { + New: 0, + Learning: 1, + Review: 2, + Relearning: 3, +} as const; + function DeckStats({ deckId }: { deckId: string }) { const { data: cards } = useAtomValue(cardsByDeckAtomFamily(deckId)); @@ -32,17 +40,37 @@ function DeckStats({ deckId }: { deckId: string }) { const boundary = getEndOfStudyDayBoundary(); const dueCards = cards.filter((card) => new Date(card.due) < boundary); + // Count by card state + const newCards = dueCards.filter((card) => card.state === CardState.New); + const learningCards = dueCards.filter( + (card) => + card.state === CardState.Learning || card.state === CardState.Relearning, + ); + const reviewCards = dueCards.filter( + (card) => card.state === CardState.Review, + ); + return ( <div className="bg-white rounded-xl border border-border/50 p-6 mb-6"> - <div className="grid grid-cols-2 gap-6"> + <div className="grid grid-cols-4 gap-4"> <div> - <p className="text-sm text-muted mb-1">Total Cards</p> + <p className="text-sm text-muted mb-1">Total</p> <p className="text-2xl font-semibold text-ink">{cards.length}</p> </div> <div> - <p className="text-sm text-muted mb-1">Due Today</p> - <p className="text-2xl font-semibold text-primary"> - {dueCards.length} + <p className="text-sm text-muted mb-1">New</p> + <p className="text-2xl font-semibold text-info">{newCards.length}</p> + </div> + <div> + <p className="text-sm text-muted mb-1">Learning</p> + <p className="text-2xl font-semibold text-warning"> + {learningCards.length} + </p> + </div> + <div> + <p className="text-sm text-muted mb-1">Review</p> + <p className="text-2xl font-semibold text-success"> + {reviewCards.length} </p> </div> </div> @@ -72,14 +100,22 @@ function DeckContent({ deckId }: { deckId: string }) { <Suspense fallback={ <div className="bg-white rounded-xl border border-border/50 p-6 mb-6"> - <div className="grid grid-cols-2 gap-6"> + <div className="grid grid-cols-4 gap-4"> + <div> + <div className="h-4 w-12 bg-muted/20 rounded animate-pulse mb-1" /> + <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> + </div> + <div> + <div className="h-4 w-12 bg-muted/20 rounded animate-pulse mb-1" /> + <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> + </div> <div> - <div className="h-4 w-20 bg-muted/20 rounded animate-pulse mb-1" /> - <div className="h-8 w-12 bg-muted/20 rounded animate-pulse" /> + <div className="h-4 w-16 bg-muted/20 rounded animate-pulse mb-1" /> + <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> </div> <div> - <div className="h-4 w-20 bg-muted/20 rounded animate-pulse mb-1" /> - <div className="h-8 w-12 bg-muted/20 rounded animate-pulse" /> + <div className="h-4 w-14 bg-muted/20 rounded animate-pulse mb-1" /> + <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> </div> </div> </div> |
