This commit is contained in:
rgrgogu
2026-07-19 10:49:56 +08:00
parent 6d9e8e1db9
commit bd7200a9cf
3 changed files with 36 additions and 7 deletions
@@ -233,6 +233,7 @@ export default function CourseAssessment() {
const [confirmLoading, setConfirmLoading] = useState(false);
const pendingSaveRef = useRef(null); // stores the meta+questions payload until confirmed
const initialSnapshot = useRef(null);
const originalQuestionIdsRef = useRef([]); // ids loaded from the server — used to detect a full-pool wipe on save
const questionRefs = useRef([]);
const navItemRefs = useRef([]);
@@ -271,6 +272,7 @@ export default function CourseAssessment() {
setTitle(t); setPassingScore(ps); setTimeLimit(tl); setIsRequired(ir);
setMaxQuestions(mq); setMaxAttempts(ma); setCooldownHours(ch); setShuffleQuestions(sq); setQuestions(qs);
initialSnapshot.current = snapAssessment({ title: t, passingScore: ps, timeLimit: tl, isRequired: ir, maxQuestions: mq, maxAttempts: ma, cooldownHours: ch, shuffleQuestions: sq, questions: qs });
originalQuestionIdsRef.current = qs.map((q) => q.question_id).filter(Boolean);
}, [localAssessment]);
// ── Measure sticky header → --assessment-h ────────────────────────────────
@@ -430,14 +432,28 @@ export default function CourseAssessment() {
let id = assessmentId;
if (!id) {
const res = await createAssessment(courseId, meta);
id = res?.data?.data?.data?.assessment_id;
// res is the raw response body: { status, message, data: { data: assessment } }
// (createAssessment() itself unwraps the same object as data?.data?.data to get
// the assessment row — this needs the same two levels, not three).
id = res?.data?.data?.assessment_id;
if (!id) return;
setLocalAssessment((prev) => ({ ...prev, assessment_id: id }));
} else {
await updateAssessment(courseId, id, meta);
}
await bulkSyncAssessmentQuestions(courseId, id, questions, user?.user_id);
// A save that keeps none of the previously-loaded question ids would archive the
// entire existing pool in one call — could be a deliberate "start over", but is
// exactly the signature of a stale/incomplete state bug. Require explicit confirmation.
const retainedIds = questions.map((q) => q.question_id).filter(Boolean);
const wipesPool = originalQuestionIdsRef.current.length > 0 && retainedIds.length === 0;
if (wipesPool && !window.confirm(
`This will permanently remove all ${originalQuestionIdsRef.current.length} existing question(s) from this assessment. Continue?`
)) {
return;
}
await bulkSyncAssessmentQuestions(courseId, id, questions, user?.user_id, wipesPool);
initialSnapshot.current = snapAssessment({ title, passingScore, timeLimit, isRequired, maxQuestions, maxAttempts, cooldownHours, shuffleQuestions, questions });
};
@@ -222,6 +222,7 @@ export default function ModifyQuiz() {
const navContainerRef = useRef(null);
const headerRef = useRef(null);
const initialSnapshot = useRef(null);
const originalQuestionIdsRef = useRef([]); // ids loaded from the server — used to detect a full-pool wipe on save
// Junction revamp — this builder runs course-scoped AND from the standalone
// Unit Library (/admin/units/:unitId/quiz/edit, no :courseId param).
@@ -273,6 +274,7 @@ export default function ModifyQuiz() {
const qs = (localQuiz.questions ?? []).map((q) => ({ ...q, _tempId: q.question_id, options: q.options ?? [] }));
setTitle(t); setPassingScore(ps); setIsRequired(ir); setMaxQuestions(mq); setShuffleQuestions(sq); setQuestions(qs);
initialSnapshot.current = snapQuiz({ title: t, passingScore: ps, isRequired: ir, maxQuestions: mq, shuffleQuestions: sq, questions: qs });
originalQuestionIdsRef.current = qs.map((q) => q.question_id).filter(Boolean);
}, [localQuiz]);
// ── Measure sticky header → --quiz-h ──────────────────────────────────────
@@ -416,7 +418,18 @@ export default function ModifyQuiz() {
await updateQuiz(courseId, unitId, quizId, meta);
}
await bulkSyncQuizQuestions(courseId, unitId, quizId, questions, user?.user_id);
// A save that keeps none of the previously-loaded question ids would archive the
// entire existing pool in one call — could be a deliberate "start over", but is
// exactly the signature of a stale/incomplete state bug. Require explicit confirmation.
const retainedIds = questions.map((q) => q.question_id).filter(Boolean);
const wipesPool = originalQuestionIdsRef.current.length > 0 && retainedIds.length === 0;
if (wipesPool && !window.confirm(
`This will permanently remove all ${originalQuestionIdsRef.current.length} existing question(s) from this quiz. Continue?`
)) {
return;
}
await bulkSyncQuizQuestions(courseId, unitId, quizId, questions, user?.user_id, wipesPool);
initialSnapshot.current = snapQuiz({ title, passingScore, isRequired, maxQuestions, shuffleQuestions, questions });
navigate(`${scopeBase}/view`);