diff --git a/src/pages/content-management/HumanLanguagePage.tsx b/src/pages/content-management/HumanLanguagePage.tsx index 3bc747f..7264d01 100644 --- a/src/pages/content-management/HumanLanguagePage.tsx +++ b/src/pages/content-management/HumanLanguagePage.tsx @@ -91,6 +91,14 @@ type PracticeDialogState = practiceId?: number } +type LessonDialogState = + | { open: false } + | { + open: true + lessonId: number + questionSetId: number + } + type QuestionDialogState = | { open: false } | { @@ -344,7 +352,14 @@ export function HumanLanguagePage() { const [subModuleCardSelection, setSubModuleCardSelection] = useState>({}) const [practiceQuestionsState, setPracticeQuestionsState] = useState>({}) const [practiceDialog, setPracticeDialog] = useState({ open: false }) + const [lessonDialog, setLessonDialog] = useState({ open: false }) const [questionDialog, setQuestionDialog] = useState({ open: false }) + const [lessonForm, setLessonForm] = useState({ + title: "", + description: "", + introVideoUrl: "", + status: "DRAFT" as "DRAFT" | "PUBLISHED" | "ARCHIVED", + }) const [practiceForm, setPracticeForm] = useState({ title: "", description: "", @@ -357,9 +372,12 @@ export function HumanLanguagePage() { const [questionDraft, setQuestionDraft] = useState(() => createEmptyPracticeQuestionDraft()) const [questionDetailById, setQuestionDetailById] = useState>({}) const [practiceTargetDelete, setPracticeTargetDelete] = useState<{ id: number; title: string } | null>(null) + const [lessonTargetDelete, setLessonTargetDelete] = useState<{ id: number; questionSetId: number; title: string } | null>(null) const [questionTargetDelete, setQuestionTargetDelete] = useState<{ id: number; practiceId: number; text: string } | null>(null) const [savingPractice, setSavingPractice] = useState(false) + const [savingLesson, setSavingLesson] = useState(false) const [savingQuestion, setSavingQuestion] = useState(false) + const [deletingLesson, setDeletingLesson] = useState(false) const [deletingPractice, setDeletingPractice] = useState(false) const [deletingQuestion, setDeletingQuestion] = useState(false) /** While fetching full question detail before opening the edit dialog (avoids empty form flash). */ @@ -815,6 +833,57 @@ export function HumanLanguagePage() { } } + const openEditLessonDialog = async (lesson: { id: number; question_set_id: number; title: string }) => { + setLessonDialog({ open: true, lessonId: lesson.id, questionSetId: lesson.question_set_id }) + setSavingLesson(false) + try { + const detail = (await getQuestionSetById(lesson.question_set_id)).data?.data + setLessonForm({ + title: detail?.title ?? lesson.title ?? "", + description: detail?.description ?? "", + introVideoUrl: detail?.intro_video_url ?? "", + status: + (detail?.status as "DRAFT" | "PUBLISHED" | "ARCHIVED" | undefined) && ["DRAFT", "PUBLISHED", "ARCHIVED"].includes(detail.status) + ? (detail.status as "DRAFT" | "PUBLISHED" | "ARCHIVED") + : "DRAFT", + }) + } catch (error) { + console.error("Failed to load lesson detail:", error) + setLessonForm({ + title: lesson.title ?? "", + description: "", + introVideoUrl: "", + status: "DRAFT", + }) + toast.error("Could not load full lesson details") + } + } + + const handleSaveLesson = async () => { + if (!lessonDialog.open) return + if (!lessonForm.title.trim()) { + toast.error("Lesson title is required") + return + } + setSavingLesson(true) + try { + await updateQuestionSet(lessonDialog.questionSetId, { + title: lessonForm.title.trim(), + description: lessonForm.description.trim() || undefined, + intro_video_url: lessonForm.introVideoUrl.trim() || undefined, + status: lessonForm.status, + }) + toast.success("Lesson updated") + setLessonDialog({ open: false }) + await loadHierarchy(false) + } catch (error) { + console.error("Failed to update lesson:", error) + toast.error("Failed to update lesson") + } finally { + setSavingLesson(false) + } + } + const practiceFieldErrors = useMemo(() => { const title = practiceForm.title.trim() return { @@ -1100,6 +1169,22 @@ export function HumanLanguagePage() { } } + const handleDeleteLessonConfirmed = async () => { + if (!lessonTargetDelete) return + setDeletingLesson(true) + try { + await deleteQuestionSet(lessonTargetDelete.questionSetId) + toast.success("Lesson deleted") + setLessonTargetDelete(null) + await loadHierarchy() + } catch (error) { + console.error("Failed to delete lesson:", error) + toast.error("Failed to delete lesson") + } finally { + setDeletingLesson(false) + } + } + const handleDeleteQuestionConfirmed = async () => { if (!questionTargetDelete) return setDeletingQuestion(true) @@ -1508,6 +1593,7 @@ export function HumanLanguagePage() { const lessonRows = [ ...(subModule.lessons ?? []).map((lesson) => ({ id: lesson.id, + question_set_id: lesson.question_set_id, title: lesson.title, display_order: lesson.display_order, status: lesson.status, @@ -1517,6 +1603,7 @@ export function HumanLanguagePage() { ...((subModule.lessons?.length ?? 0) === 0 ? subModule.videos.map((video) => ({ id: video.id, + question_set_id: 0, title: video.title, display_order: video.display_order, status: "PUBLISHED", @@ -1673,14 +1760,14 @@ export function HumanLanguagePage() { type="button" onClick={() => toggleLessonCard(smKey, v.id)} className={cn( - "flex flex-col gap-1.5 rounded-xl border bg-white p-3 text-left shadow-sm transition-all hover:border-brand-200 hover:shadow-md", + "flex flex-col gap-1.5 rounded-xl border bg-white p-3 text-left shadow-sm transition-all hover:border-grayScale-300 hover:shadow-md", isActive - ? "border-brand-400 ring-2 ring-brand-400/30" + ? "border-grayScale-300 ring-1 ring-grayScale-200" : "border-grayScale-100", )} > -
-
+
+
@@ -1688,7 +1775,9 @@ export function HumanLanguagePage() { {v.title}

- + {(v.status ?? "DRAFT").replace(/_/g, " ").toLowerCase()} @@ -1696,6 +1785,38 @@ export function HumanLanguagePage() {
+ {v.question_set_id > 0 ? ( +
+ + +
+ ) : null}
) @@ -1710,6 +1831,12 @@ export function HumanLanguagePage() { {selectedLesson.title}
+
+
Status
+
+ {(selectedLesson.status ?? "DRAFT").toLowerCase()} +
+
Display order
@@ -2283,6 +2410,100 @@ export function HumanLanguagePage() { + { + if (!open) setLessonDialog({ open: false }) + }} + > + + + Edit lesson + Update lesson metadata stored in the linked question set. + +
+
+ + setLessonForm((prev) => ({ ...prev, title: e.target.value }))} + placeholder="Lesson title" + /> +
+
+ +