feat(study): Phase 2-A 풀이 시작 UI — 학습 단계 + 분량 토글
vision 의 단일 풀이 진입점 — 옵션 토글로 학습 단계 (입문/학습 중/시험 직전) +
분량 (30/50/100) 선택. Phase 1-E bucket+stage 알고리즘과 매칭.
- 학습 단계 3 카드 + 분량 3 토글이 메인 옵션
- 단계 선택 시 분량 토글 노출
- 단계 미선택 시 "고급 옵션" collapsible — 기존 PR-12-B subject 단위 출제 호환
- 시작 버튼 disabled 상태 가이드 (단계 선택 또는 고급 옵션 펼침 필요)
서버 호출:
- optStage 있으면 { stage, size, abandon_existing } body
- 없으면 기존 { target_per_subject, subject, wrong_only, abandon_existing }
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,12 @@
|
||||
|
||||
// 진입 화면 옵션 (start 모드용)
|
||||
let mode = $state('start'); // 'start' | 'playing'
|
||||
// Phase 2-A: 학습 단계 + 분량 (Phase 1-E bucket+stage 알고리즘 입력)
|
||||
// null = 기본 (subject bucket 모드, 기존 PR-12-B 동작)
|
||||
let optStage = $state(null); // null | 'intro' | 'learning' | 'pre_exam'
|
||||
let optSize = $state(50); // 30 / 50 / 100
|
||||
let advancedOpen = $state(false);
|
||||
// 기존 옵션 (advanced — stage 미선택 시만 사용)
|
||||
let optSubject = $state('');
|
||||
let optWrongOnly = $state(false);
|
||||
let optTarget = $state(20);
|
||||
@@ -89,18 +95,27 @@
|
||||
}
|
||||
});
|
||||
|
||||
/** 'start' 모드에서 [시작] 클릭 → 신규 세션 생성 후 ?session=N 으로 이동. */
|
||||
/** 'start' 모드에서 [시작] 클릭 → 신규 세션 생성 후 ?session=N 으로 이동.
|
||||
* optStage 가 설정되면 Phase 1-E bucket+stage 알고리즘으로 출제 (단일 풀이 진입점).
|
||||
* 미설정이면 기존 subject bucket + spacing 경로 (advanced 옵션 호환). */
|
||||
async function start() {
|
||||
loading = true;
|
||||
try {
|
||||
const body = optStage
|
||||
? {
|
||||
stage: optStage,
|
||||
size: optSize,
|
||||
abandon_existing: false,
|
||||
}
|
||||
: {
|
||||
target_per_subject: optTarget,
|
||||
subject: optSubject.trim() || null,
|
||||
wrong_only: optWrongOnly,
|
||||
abandon_existing: false,
|
||||
};
|
||||
const res = await api(`/study-topics/${topicId}/quiz-sessions`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({
|
||||
target_per_subject: optTarget,
|
||||
subject: optSubject.trim() || null,
|
||||
wrong_only: optWrongOnly,
|
||||
abandon_existing: false, // start 화면에서는 in_progress 있으면 그쪽으로 이어감.
|
||||
}),
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
goto(`/study/topics/${topicId}/review?session=${res.id}`, { replaceState: true });
|
||||
// loadSession 은 navigate 후 onMount 가 다시 안 트리거되므로 직접 호출.
|
||||
@@ -171,38 +186,93 @@
|
||||
<div class="p-5 flex flex-col gap-4">
|
||||
<h1 class="text-base font-semibold text-text">문제풀이</h1>
|
||||
<p class="text-xs text-dim">
|
||||
기본은 과목별 {optTarget}문제씩 무작위 균등 추출. 한 과목이 부족하면 가용한 만큼만 출제됩니다.
|
||||
학습 단계와 분량을 선택하세요. 단계에 맞춰 안 푼 문제, 오답, 복습 예정, 빈출 유형이 자동으로 섞여 출제됩니다.
|
||||
풀이 중 정답·해설은 표시하지 않으며, 다 풀면 결과 화면에서 카테고리별로 한 번에 확인합니다.
|
||||
나갔다 와도 같은 위치에서 이어풀 수 있습니다.
|
||||
</p>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<TextInput label="과목 (선택, 비워두면 전체 균등)" bind:value={optSubject} placeholder="예: 연소공학" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs text-dim">과목당 문제 수</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={optTarget}
|
||||
min="1"
|
||||
max="100"
|
||||
class="px-3 py-2 bg-surface border border-default rounded text-sm text-text outline-none focus:border-accent"
|
||||
/>
|
||||
</label>
|
||||
<!-- 학습 단계 -->
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-xs text-dim">학습 단계</span>
|
||||
<div class="grid grid-cols-3 gap-1.5">
|
||||
{#each [
|
||||
{ v: 'intro', label: '입문', desc: '안 푼 문제 위주' },
|
||||
{ v: 'learning', label: '학습 중', desc: '오답·복습·신규 균형' },
|
||||
{ v: 'pre_exam', label: '시험 직전', desc: '실전 + 약점 보강' },
|
||||
] as opt (opt.v)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (optStage = opt.v)}
|
||||
class="p-2 rounded border text-left transition-colors
|
||||
{optStage === opt.v ? 'border-accent bg-accent/10 text-text' : 'border-default bg-surface text-dim hover:border-accent/40'}"
|
||||
>
|
||||
<div class="text-xs font-semibold">{opt.label}</div>
|
||||
<div class="text-[10px] mt-0.5">{opt.desc}</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="flex items-center gap-2 text-xs text-text cursor-pointer">
|
||||
<input type="checkbox" bind:checked={optWrongOnly} />
|
||||
<span>오답만 (가장 최근 attempt 가 오답인 문제만)</span>
|
||||
</label>
|
||||
<!-- 분량 -->
|
||||
{#if optStage}
|
||||
<div class="flex flex-col gap-1.5">
|
||||
<span class="text-xs text-dim">분량</span>
|
||||
<div class="grid grid-cols-3 gap-1.5">
|
||||
{#each [30, 50, 100] as size (size)}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (optSize = size)}
|
||||
class="p-2 rounded border text-center transition-colors
|
||||
{optSize === size ? 'border-accent bg-accent/10 text-text' : 'border-default bg-surface text-dim hover:border-accent/40'}"
|
||||
>
|
||||
<div class="text-xs font-semibold">{size}문제</div>
|
||||
</button>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<!-- 고급 옵션 (단계 미선택 시만 활성) -->
|
||||
{#if !optStage}
|
||||
<button
|
||||
type="button"
|
||||
onclick={() => (advancedOpen = !advancedOpen)}
|
||||
class="text-xs text-dim hover:text-text text-left"
|
||||
>
|
||||
{advancedOpen ? '▾' : '▸'} 고급 옵션 (단계 미선택 시 — 과목 단위 균등 출제)
|
||||
</button>
|
||||
{#if advancedOpen}
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-3 pl-3 border-l-2 border-default">
|
||||
<div>
|
||||
<TextInput label="과목 (선택, 비워두면 전체 균등)" bind:value={optSubject} placeholder="예: 연소공학" />
|
||||
</div>
|
||||
<div>
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs text-dim">과목당 문제 수</span>
|
||||
<input
|
||||
type="number"
|
||||
bind:value={optTarget}
|
||||
min="1"
|
||||
max="100"
|
||||
class="px-3 py-2 bg-surface border border-default rounded text-sm text-text outline-none focus:border-accent"
|
||||
/>
|
||||
</label>
|
||||
</div>
|
||||
<label class="flex items-center gap-2 text-xs text-text cursor-pointer md:col-span-2">
|
||||
<input type="checkbox" bind:checked={optWrongOnly} />
|
||||
<span>오답만 (가장 최근 attempt 가 오답인 문제만)</span>
|
||||
</label>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<div class="flex gap-2 justify-end">
|
||||
<Button href={`/study/topics/${topicId}`} variant="ghost" icon={ArrowLeft}>주제로</Button>
|
||||
<Button onclick={start} icon={Play}>시작</Button>
|
||||
<Button onclick={start} icon={Play} disabled={!optStage && !advancedOpen}>시작</Button>
|
||||
</div>
|
||||
{#if !optStage && !advancedOpen}
|
||||
<p class="text-[11px] text-dim text-right">학습 단계를 선택하거나 고급 옵션으로 시작</p>
|
||||
{/if}
|
||||
</div>
|
||||
{/snippet}
|
||||
</Card>
|
||||
|
||||
Reference in New Issue
Block a user