diff options
| author | nsfisis <54318333+nsfisis@users.noreply.github.com> | 2026-02-08 21:24:08 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-02-08 21:24:08 +0900 |
| commit | c0d092b3bfef491d9aa02a4e7e8f503ea35e6420 (patch) | |
| tree | ac2e447cc7a2e568187a414c241258adf009c1d5 /src | |
| parent | 5e7c3ad7ed8c287b538de97d4de3a4df87e9a100 (diff) | |
| parent | 6d53e63d9f3fd81125d0f61e9701ecd262318875 (diff) | |
| download | kioku-c0d092b3bfef491d9aa02a4e7e8f503ea35e6420.tar.gz kioku-c0d092b3bfef491d9aa02a4e7e8f503ea35e6420.tar.zst kioku-c0d092b3bfef491d9aa02a4e7e8f503ea35e6420.zip | |
Merge pull request #13 from nsfisis/claude/clarify-deck-numbers-mg3d4
Simplify deck stats to show due card count from server
Diffstat (limited to 'src')
| -rw-r--r-- | src/client/pages/DeckDetailPage.test.tsx | 29 | ||||
| -rw-r--r-- | src/client/pages/DeckDetailPage.tsx | 52 | ||||
| -rw-r--r-- | src/server/routes/decks.ts | 18 | ||||
| -rw-r--r-- | src/server/routes/study.test.ts | 2 | ||||
| -rw-r--r-- | src/server/routes/study.ts | 2 |
5 files changed, 30 insertions, 73 deletions
diff --git a/src/client/pages/DeckDetailPage.test.tsx b/src/client/pages/DeckDetailPage.test.tsx index 815dff1..3c741ad 100644 --- a/src/client/pages/DeckDetailPage.test.tsx +++ b/src/client/pages/DeckDetailPage.test.tsx @@ -258,33 +258,16 @@ describe("DeckDetailPage", () => { ); }); - it("displays card counts by state", () => { + it("displays due card count from deck data", () => { renderWithProviders({ - initialDeck: mockDeck, + initialDeck: { ...mockDeck, dueCardCount: 5 }, initialCards: mockCards, }); - // New cards (state=0, but card-1 is not due yet, so 0) - const newLabel = screen.getByText("New"); - expect(newLabel).toBeDefined(); - const newContainer = newLabel.parentElement; - expect(newContainer?.querySelector(".text-info")?.textContent).toBe("0"); - - // Learning cards (state=1 or 3, none in mockCards) - const learningLabel = screen.getByText("Learning"); - expect(learningLabel).toBeDefined(); - const learningContainer = learningLabel.parentElement; - expect(learningContainer?.querySelector(".text-warning")?.textContent).toBe( - "0", - ); - - // Review cards (state=2, card-2 is due now) - const reviewLabel = screen.getByText("Review"); - expect(reviewLabel).toBeDefined(); - const reviewContainer = reviewLabel.parentElement; - expect(reviewContainer?.querySelector(".text-success")?.textContent).toBe( - "1", - ); + const dueLabel = screen.getByText("Due"); + expect(dueLabel).toBeDefined(); + const dueContainer = dueLabel.parentElement; + expect(dueContainer?.querySelector(".text-primary")?.textContent).toBe("5"); }); it("does not display card list (cards are hidden)", () => { diff --git a/src/client/pages/DeckDetailPage.tsx b/src/client/pages/DeckDetailPage.tsx index 6bc89ba..d717d60 100644 --- a/src/client/pages/DeckDetailPage.tsx +++ b/src/client/pages/DeckDetailPage.tsx @@ -7,7 +7,6 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { useAtomValue } from "jotai"; import { Suspense } from "react"; import { Link, useParams } from "wouter"; -import { getEndOfStudyDayBoundary } from "../../shared/date"; import { cardsByDeckAtomFamily, deckByIdAtomFamily } from "../atoms"; import { ErrorBoundary } from "../components/ErrorBoundary"; import { LoadingSpinner } from "../components/LoadingSpinner"; @@ -25,52 +24,21 @@ 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: deck } = useAtomValue(deckByIdAtomFamily(deckId)); const { data: cards } = useAtomValue(cardsByDeckAtomFamily(deckId)); - // Count cards due today (study day boundary is 3:00 AM) - 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-4 gap-4"> + <div className="grid grid-cols-2 gap-4"> <div> <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">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 className="text-sm text-muted mb-1">Due</p> + <p className="text-2xl font-semibold text-primary"> + {deck.dueCardCount} </p> </div> </div> @@ -100,7 +68,7 @@ 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-4 gap-4"> + <div className="grid grid-cols-2 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" /> @@ -109,14 +77,6 @@ function DeckContent({ deckId }: { deckId: string }) { <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-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-14 bg-muted/20 rounded animate-pulse mb-1" /> - <div className="h-8 w-10 bg-muted/20 rounded animate-pulse" /> - </div> </div> </div> } diff --git a/src/server/routes/decks.ts b/src/server/routes/decks.ts index 6a24c66..d73aa0c 100644 --- a/src/server/routes/decks.ts +++ b/src/server/routes/decks.ts @@ -22,7 +22,7 @@ const deckIdParamSchema = z.object({ id: z.uuid(), }); -const REVIEW_CARDS_LIMIT = 100; +const REVIEW_CARDS_LIMIT = 80; export function createDecksRouter(deps: DeckDependencies) { const { deckRepo, cardRepo, reviewLogRepo } = deps; @@ -82,7 +82,21 @@ export function createDecksRouter(deps: DeckDependencies) { throw Errors.notFound("Deck not found", "DECK_NOT_FOUND"); } - return c.json({ deck }, 200); + const now = new Date(); + const [dueNewCards, dueReviewCards, reviewedNewCards] = await Promise.all( + [ + cardRepo.countDueNewCards(deck.id, now), + cardRepo.countDueReviewCards(deck.id, now), + reviewLogRepo.countTodayNewCardReviews(deck.id, now), + ], + ); + + const newCardBudget = Math.max(0, deck.newCardsPerDay - reviewedNewCards); + const newCardsToStudy = Math.min(dueNewCards, newCardBudget); + const reviewCardsToStudy = Math.min(dueReviewCards, REVIEW_CARDS_LIMIT); + const dueCardCount = newCardsToStudy + reviewCardsToStudy; + + return c.json({ deck: { ...deck, dueCardCount } }, 200); }) .put( "/:id", diff --git a/src/server/routes/study.test.ts b/src/server/routes/study.test.ts index a58ea0d..514d966 100644 --- a/src/server/routes/study.test.ts +++ b/src/server/routes/study.test.ts @@ -198,7 +198,7 @@ describe("GET /api/decks/:deckId/study", () => { expect(mockCardRepo.findDueReviewCardsForStudy).toHaveBeenCalledWith( DECK_ID, expect.any(Date), - 100, + 80, ); }); diff --git a/src/server/routes/study.ts b/src/server/routes/study.ts index efd450c..d05f3ca 100644 --- a/src/server/routes/study.ts +++ b/src/server/routes/study.ts @@ -62,7 +62,7 @@ export function createStudyRouter(deps: StudyDependencies) { // Fetch new cards (limited) and review cards separately const [newCards, reviewCards] = await Promise.all([ cardRepo.findDueNewCardsForStudy(deckId, now, newCardBudget), - cardRepo.findDueReviewCardsForStudy(deckId, now, 100), + cardRepo.findDueReviewCardsForStudy(deckId, now, 80), ]); return c.json({ cards: [...newCards, ...reviewCards] }, 200); |
