fix(study): 클라이언트 카운터 신뢰 X — 서버 max+1 자동 채움 (user-edited dirty flag)
이전 fix(effect→onchange)에도 race 재발 (id 306,307 qnum=1,2 로 또 들어감). 근본 해결 — 클라이언트의 f_qnum 표시값과 실제 저장값을 분리. 변경: - f_qnum_user_edited dirty flag 추가 - input 에 oninput → user_edited=true (사용자가 직접 박스 수정한 경우) - onMount fallback / onRoundChange / applyNewRound / 저장 후 → user_edited=false - POST body 의 exam_question_number: user_edited=true 면 명시 전송, false 면 null → 서버가 같은 회차 max+1 자동 채움 (PR-6 의 기존 서버 로직) - POST 응답의 실제 저장 qnum 으로 화면 동기화 (saved.exam_question_number) → 표시값이 어긋났어도 저장 후 정확하게 갱신 - applyNewRound 에서 이미 존재하는 회차명 입력 시 next_question_number 적용 (사용자가 dropdown 대신 새 회차 모드로 같은 이름 다시 입력해도 1번부터 다시 시작 X) 이제 클라이언트가 어떤 표시값을 보여주든 실제 저장은 항상 정확. 사용자가 직접 박스를 수정한 경우만 명시 전송.
This commit is contained in:
@@ -48,7 +48,11 @@
|
||||
let f_exam_round = $state('');
|
||||
let f_exam_round_mode = $state('select'); // 'select' | 'new'
|
||||
let f_exam_round_new = $state('');
|
||||
let f_qnum = $state(1); // 문항 번호
|
||||
let f_qnum = $state(1); // 문항 번호 (표시용 — 저장 시 서버 max+1 우선)
|
||||
// PR-7: 사용자가 input 박스를 직접 편집했는지 추적. true 면 저장 시 명시 전송,
|
||||
// false 면 null 보내고 서버가 같은 회차 max+1 자동 채움. 회차 변경/저장 후/onMount
|
||||
// 에서 false 로 reset.
|
||||
let f_qnum_user_edited = $state(false);
|
||||
|
||||
// 본문 자동 reset 안 되는 메타 (편의성 — 비워둠이 default)
|
||||
let explanation = $state('');
|
||||
@@ -146,6 +150,8 @@
|
||||
f_qnum = found.next_question_number;
|
||||
}
|
||||
}
|
||||
// 진입 시점은 사용자가 input 박스를 만진 적 없음 — false 로 reset.
|
||||
f_qnum_user_edited = false;
|
||||
// $effect 의 lastExamRound 를 현재 값으로 sync — 첫 실행이 또 reset 하지 않도록.
|
||||
lastExamRound = f_exam_round;
|
||||
});
|
||||
@@ -176,6 +182,7 @@
|
||||
} else if (!found) {
|
||||
f_qnum = 1;
|
||||
}
|
||||
f_qnum_user_edited = false;
|
||||
lastExamRound = f_exam_round;
|
||||
refreshCompleteFlag();
|
||||
}
|
||||
@@ -200,11 +207,20 @@
|
||||
addToast('error', '회차명을 입력하세요');
|
||||
return;
|
||||
}
|
||||
f_exam_round = f_exam_round_new.trim();
|
||||
const newRound = f_exam_round_new.trim();
|
||||
f_exam_round = newRound;
|
||||
f_exam_round_new = '';
|
||||
f_exam_round_mode = 'select';
|
||||
f_qnum = 1;
|
||||
lastExamRound = f_exam_round;
|
||||
// PR-7 fix: 사용자가 "새 회차" 모드에 이미 존재하는 회차명을 입력했으면
|
||||
// next_question_number 로 시작 (1번부터 다시 시작 X). dropdown 선택과 동일 동작.
|
||||
const found = examRounds.find((r) => r.exam_round === newRound);
|
||||
if (found && found.next_question_number) {
|
||||
f_qnum = found.next_question_number;
|
||||
} else {
|
||||
f_qnum = 1;
|
||||
}
|
||||
f_qnum_user_edited = false;
|
||||
lastExamRound = newRound;
|
||||
refreshCompleteFlag();
|
||||
}
|
||||
|
||||
@@ -234,6 +250,9 @@
|
||||
persist();
|
||||
try {
|
||||
const subj = effectiveSubject();
|
||||
// PR-7 fix: 클라이언트 카운터를 신뢰하지 않고 서버가 항상 max+1 결정.
|
||||
// 사용자가 직접 input 박스를 다른 값으로 수정한 경우만 명시 전송.
|
||||
const userEditedQnum = f_qnum_user_edited && f_exam_round;
|
||||
const body = {
|
||||
question_text: q_text.trim(),
|
||||
choice_1: c1.trim(),
|
||||
@@ -245,18 +264,25 @@
|
||||
scope: f_scope || null,
|
||||
exam_name: f_exam_name || null,
|
||||
exam_round: f_exam_round || null,
|
||||
exam_question_number: f_exam_round ? Number(f_qnum) : null,
|
||||
// 사용자가 직접 수정 안 했으면 null → 서버가 같은 회차 max+1 자동 채움
|
||||
exam_question_number: userEditedQnum ? Number(f_qnum) : null,
|
||||
explanation: explanation || null,
|
||||
source_note: autoSourceNote() || null,
|
||||
};
|
||||
await api(`/study-topics/${topicId}/questions`, {
|
||||
const saved = await api(`/study-topics/${topicId}/questions`, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(body),
|
||||
});
|
||||
addToast('success', `문제 저장됨${f_exam_round ? ` (${f_exam_round} ${f_qnum}번)` : ''}`);
|
||||
// 응답의 실제 저장값으로 표시 동기화 (서버가 결정한 qnum)
|
||||
const actualQnum = saved?.exam_question_number ?? null;
|
||||
if (actualQnum) f_qnum = actualQnum;
|
||||
addToast('success', `문제 저장됨${f_exam_round && actualQnum ? ` (${f_exam_round} ${actualQnum}번)` : ''}`);
|
||||
if (continueAfter) {
|
||||
clearForCont();
|
||||
f_qnum = Number(f_qnum) + 1;
|
||||
// 다음 표시값 = 방금 저장된 qnum + 1. 사용자가 다시 수정하기 전까지는
|
||||
// 자동(서버 max+1) 모드 유지.
|
||||
if (actualQnum) f_qnum = actualQnum + 1;
|
||||
f_qnum_user_edited = false;
|
||||
// 회차 진행률 갱신 (도달 체크)
|
||||
await refreshExamRounds();
|
||||
persist();
|
||||
@@ -406,6 +432,7 @@
|
||||
<label class="flex flex-col gap-1.5">
|
||||
<span class="text-xs text-dim">문항 번호</span>
|
||||
<input type="number" min="1" bind:value={f_qnum}
|
||||
oninput={() => (f_qnum_user_edited = true)}
|
||||
class="w-28 px-3 py-2 bg-surface border border-default rounded text-sm text-text outline-none focus:border-accent" />
|
||||
</label>
|
||||
{#if currentProgress() && currentProgress().size}
|
||||
|
||||
Reference in New Issue
Block a user