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",