diff --git a/frontend/src/lib/stores/studySession.ts b/frontend/src/lib/stores/studySession.ts new file mode 100644 index 0000000..c1cda3b --- /dev/null +++ b/frontend/src/lib/stores/studySession.ts @@ -0,0 +1,13 @@ +/** + * 카드 학습 세션 전달용 store. + * + * 복습함(/study/review-box)에서 선택한 카드들을 cards-study 복습 세션으로 넘긴다. + * 백엔드 '세션 by card_ids' 엔드포인트 없이(= eid contention 중 fastapi 무재빌드) 동작하도록 + * 선택 카드 객체 배열을 그대로 전달. cards-study 가 startReview 에서 consume(읽고 비움). + * + * 모듈 레벨 store 라 SPA 네비게이션 동안 유지되고, 새로고침 시 사라진다(그땐 복습함에서 다시 선택). + */ +import { writable } from 'svelte/store'; + +// CardItem[] | null — 복습함에서 '선택 복습' 시 set, cards-study 가 소비 후 null. +export const pendingReviewCards = writable(null); diff --git a/frontend/src/routes/study/+page.svelte b/frontend/src/routes/study/+page.svelte index ce26300..7346a59 100644 --- a/frontend/src/routes/study/+page.svelte +++ b/frontend/src/routes/study/+page.svelte @@ -3,7 +3,7 @@ // 주제로 보기(퀴즈·복습·통계) / 자료 학습 / 필사 세션 / 암기카드 검수. import { onMount } from 'svelte'; import { api } from '$lib/api'; - import { BookOpen, PenLine, GraduationCap, FolderKanban, Layers, Repeat, Flag } from 'lucide-svelte'; + import { BookOpen, PenLine, GraduationCap, FolderKanban, Layers, Repeat, Flag, Inbox } from 'lucide-svelte'; let cardReviewCount = $state(0); let questionFlagCount = $state(0); @@ -86,6 +86,17 @@
검수한 암기카드를 모바일에서 학습. 복습(간격반복 1·3·7·14일)으로 자기평가하거나, 그냥 공부로 덜 본 카드를 가볍게 훑어봅니다.
+ +오늘 복습할 카드와 미확인 카드를 한눈에 보고, 골라서 복습합니다.
+ + + /** + * /study/review-box — 복습함 (카드 SR 복습 현황 + 선택 학습, B4). + * + * GET /study-cards/due (review_stage 포함) 로 오늘의 복습 큐를 받아 2탭으로 분리: + * - 오늘 할 일: review_stage != null (예전에 평가돼 복습일이 도래한 카드) + * - 미확인 : review_stage == null (검수 통과했지만 아직 한 번도 회상 안 한 새 카드) + * - 완료 : 졸업 카드 — 백엔드 엔드포인트 필요(현재 미배포 = eid contention 중 fastapi 무재빌드)라 추후. + * + * 멀티셀렉트 → 선택 카드를 pendingReviewCards store 로 cards-study 복습 세션에 전달(백엔드 세션 X). + */ + import { onMount } from 'svelte'; + import { goto } from '$app/navigation'; + import { api } from '$lib/api'; + import { addToast } from '$lib/stores/toast'; + import { pendingReviewCards } from '$lib/stores/studySession'; + import { ArrowLeft, Repeat, GraduationCap, CheckCheck, Play } from 'lucide-svelte'; + import Button from '$lib/components/ui/Button.svelte'; + import Skeleton from '$lib/components/ui/Skeleton.svelte'; + import EmptyState from '$lib/components/ui/EmptyState.svelte'; + + let loading = $state(true); + let cards = $state([]); // /due 결과 (CardItem[], review_stage 포함) + let tab = $state('today'); // 'today' | 'new' | 'done' + let selected = $state({}); // card.id -> true + + let newCards = $derived(cards.filter((c) => c.review_stage === null || c.review_stage === undefined)); + let dueCards = $derived(cards.filter((c) => c.review_stage !== null && c.review_stage !== undefined)); + let shown = $derived(tab === 'today' ? dueCards : tab === 'new' ? newCards : []); + let selectedCount = $derived(shown.filter((c) => selected[c.id]).length); + let allShownSelected = $derived(shown.length > 0 && shown.every((c) => selected[c.id])); + + async function load() { + loading = true; + try { + cards = (await api('/study-cards/due?limit=200')) ?? []; + } catch (err) { + addToast('error', err?.detail || '복습 카드 조회 실패'); + cards = []; + } finally { + loading = false; + } + } + + function frontText(c) { + const t = (c.format === 'cloze' && c.cloze_text ? c.cloze_text : c.cue) ?? ''; + return t.length > 60 ? t.slice(0, 60) + '…' : t; + } + + function toggle(id) { + selected = { ...selected, [id]: !selected[id] }; + } + function selectAllShown() { + const next = { ...selected }; + shown.forEach((c) => { next[c.id] = !allShownSelected; }); + selected = next; + } + + function startCards(list) { + if (!list.length) return; + pendingReviewCards.set(list); + goto('/study/cards-study?mode=review'); + } + function startSelected() { + startCards(shown.filter((c) => selected[c.id])); + } + function startTab() { + startCards(shown); + } + + function setTab(t) { + if (t === 'done') return; // 완료 탭은 백엔드 준비 전 비활성 + tab = t; + } + + onMount(load); + + ++ 검수 통과한 암기카드의 복습 현황입니다. 탭에서 카드를 골라 선택 복습하거나, 탭 전체를 한 번에 복습할 수 있어요. +
+ + +