diff --git a/src/pages/content-management/HumanLanguagePage.tsx b/src/pages/content-management/HumanLanguagePage.tsx index bb50645..ba9b3a1 100644 --- a/src/pages/content-management/HumanLanguagePage.tsx +++ b/src/pages/content-management/HumanLanguagePage.tsx @@ -59,6 +59,7 @@ import type { LearningPathPractice, QuestionDetail, QuestionSetQuestion, + SubModuleLessonDetail, } from "../../types/course.types" import { cn } from "../../lib/utils" import { toast } from "sonner" @@ -83,6 +84,12 @@ type PracticeQuestionsFetchState = | { status: "ok"; questions: QuestionSetQuestion[]; totalCount: number } | { status: "error"; message: string } +type LessonDetailFetchState = + | { status: "idle" } + | { status: "loading"; startedAt: number } + | { status: "ok"; data: SubModuleLessonDetail } + | { status: "error"; message: string } + type PracticeDialogState = | { open: false } | { @@ -357,6 +364,7 @@ export function HumanLanguagePage() { /** Selected lesson / practice card per sub-module (for inline detail panel). */ const [subModuleCardSelection, setSubModuleCardSelection] = useState>({}) const [practiceQuestionsState, setPracticeQuestionsState] = useState>({}) + const [lessonDetailState, setLessonDetailState] = useState>({}) const [practiceDialog, setPracticeDialog] = useState({ open: false }) const [lessonDialog, setLessonDialog] = useState({ open: false }) const [questionDialog, setQuestionDialog] = useState({ open: false }) @@ -880,6 +888,38 @@ export function HumanLanguagePage() { } } + const loadLessonDetailIfNeeded = async (lessonId: number, forceRefresh = false) => { + let skipFetch = false + setLessonDetailState((prev) => { + const ex = prev[lessonId] + if (!forceRefresh && ex?.status === "ok") { + skipFetch = true + return prev + } + if (!forceRefresh && ex?.status === "loading" && Date.now() - ex.startedAt < 15000) { + skipFetch = true + return prev + } + return { ...prev, [lessonId]: { status: "loading", startedAt: Date.now() } } + }) + if (skipFetch) return + try { + const res = await withTimeout(getSubModuleLessonById(lessonId), 12000) + const data = res.data?.data + if (!data) throw new Error("Missing lesson detail payload") + setLessonDetailState((prev) => ({ + ...prev, + [lessonId]: { status: "ok", data }, + })) + } catch (error) { + console.error("Failed to load lesson detail:", error) + setLessonDetailState((prev) => ({ + ...prev, + [lessonId]: { status: "error", message: "Could not load lesson detail" }, + })) + } + } + const getSubModuleSelection = (smKey: string): SubModuleCardSelection => subModuleCardSelection[smKey] ?? { lessonId: null, practiceId: null } @@ -1329,11 +1369,13 @@ export function HumanLanguagePage() { } const toggleLessonCard = (smKey: string, lessonId: number) => { + const currentLessonId = subModuleCardSelection[smKey]?.lessonId ?? null + const nextLessonId = currentLessonId === lessonId ? null : lessonId setSubModuleCardSelection((prev) => { const cur = prev[smKey] ?? { lessonId: null, practiceId: null } - const nextLessonId = cur.lessonId === lessonId ? null : lessonId return { ...prev, [smKey]: { ...cur, lessonId: nextLessonId } } }) + if (nextLessonId !== null) void loadLessonDetailIfNeeded(nextLessonId) } const toggleLessonSelection = (smKey: string, lessonId: number) => { @@ -1751,6 +1793,10 @@ export function HumanLanguagePage() { cardSel.lessonId !== null ? lessonRows.find((v) => v.id === cardSel.lessonId) ?? null : null + const selectedLessonFetch = + cardSel.lessonId !== null ? lessonDetailState[cardSel.lessonId] : undefined + const selectedLessonDetail = + selectedLessonFetch?.status === "ok" ? selectedLessonFetch.data : null const selectedLessonIds = selectedLessonIdsBySubModule[smKey] ?? [] const selectedLessonRows = lessonRows.filter((row) => selectedLessonIds.includes(row.id)) const selectedPracticeMeta = @@ -1992,47 +2038,53 @@ export function HumanLanguagePage() { Lesson detail

- {selectedLesson.title} + {selectedLessonDetail?.title ?? selectedLesson.title}

+ {selectedLessonFetch?.status === "loading" ? ( +

Loading latest lesson details...

+ ) : null} + {selectedLessonFetch?.status === "error" ? ( +

{selectedLessonFetch.message}

+ ) : null}
Status
- {(selectedLesson.status ?? "DRAFT").toLowerCase()} + {(selectedLessonDetail?.status ?? selectedLesson.status ?? "DRAFT").toLowerCase()}
Display order
- {selectedLesson.display_order} + {selectedLessonDetail?.display_order ?? selectedLesson.display_order}
Questions
- {selectedLesson.question_count} + {selectedLessonDetail?.question_count ?? selectedLesson.question_count}
Intro video
- {selectedLesson.intro_video_url ? ( + {(selectedLessonDetail?.intro_video_url ?? selectedLesson.intro_video_url) ? ( - {selectedLesson.intro_video_url} + {selectedLessonDetail?.intro_video_url ?? selectedLesson.intro_video_url} ) : ( No intro video URL set for this lesson. )} - {selectedLesson.intro_video_url + {(selectedLessonDetail?.intro_video_url ?? selectedLesson.intro_video_url) ? renderMediaPreview( - selectedLesson.intro_video_url, + selectedLessonDetail?.intro_video_url ?? selectedLesson.intro_video_url ?? "", "video", "mt-3", "Intro video preview",