aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/atoms/study.ts
blob: fca17b76d2a2171ed2a3c4a6369c628363c07456 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import { atomFamily } from "jotai-family";
import { atomWithSuspenseQuery } from "jotai-tanstack-query";
import { getStartOfStudyDayBoundary } from "../../shared/date";
import { apiClient } from "../api/client";
import { createSeededRandom, shuffle } from "../utils/random";

export interface StudyCard {
	id: string;
	deckId: string;
	noteId: string;
	isReversed: boolean;
	front: string;
	back: string;
	state: number;
	due: string;
	stability: number;
	difficulty: number;
	reps: number;
	lapses: number;
	noteType: {
		frontTemplate: string;
		backTemplate: string;
	};
	fieldValuesMap: Record<string, string>;
}

export interface StudyDeck {
	id: string;
	name: string;
}

export interface StudyData {
	deck: StudyDeck;
	cards: StudyCard[];
}

// =====================
// Study Session - Suspense-compatible
// =====================

export const studyDataAtomFamily = atomFamily((deckId: string) =>
	atomWithSuspenseQuery(() => ({
		queryKey: ["decks", deckId, "study"],
		queryFn: async (): Promise<StudyData> => {
			// Fetch deck and due cards in parallel
			const [deckRes, cardsRes] = await Promise.all([
				apiClient.rpc.api.decks[":id"].$get({ param: { id: deckId } }),
				apiClient.rpc.api.decks[":deckId"].study.$get({ param: { deckId } }),
			]);

			const deckData = await apiClient.handleResponse<{ deck: StudyDeck }>(
				deckRes,
			);
			const cardsData = await apiClient.handleResponse<{
				cards: StudyCard[];
			}>(cardsRes);

			const seed = getStartOfStudyDayBoundary().getTime();
			return {
				deck: deckData.deck,
				cards: shuffle(cardsData.cards, createSeededRandom(seed)),
			};
		},
	})),
);