diff --git a/src/pages/content-management/HumanLanguagePage.tsx b/src/pages/content-management/HumanLanguagePage.tsx index 7264d01..af86384 100644 --- a/src/pages/content-management/HumanLanguagePage.tsx +++ b/src/pages/content-management/HumanLanguagePage.tsx @@ -72,6 +72,7 @@ import { } from "../../components/content-management/PracticeQuestionEditorFields" const CEFR_LEVELS = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"] as const +const HUMAN_LANGUAGE_SCROLL_KEY = "human-language-page:scroll-y" type SubModulePanelTab = "lessons" | "practices" type SubModuleCardSelection = { lessonId: number | null; practiceId: number | null } @@ -99,6 +100,16 @@ type LessonDialogState = questionSetId: number } +type LessonListItem = { + id: number + question_set_id: number + title: string + display_order: number + status: string + question_count: number + intro_video_url: string +} + type QuestionDialogState = | { open: false } | { @@ -373,6 +384,12 @@ export function HumanLanguagePage() { 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 [lessonBulkTargetDelete, setLessonBulkTargetDelete] = useState<{ + title: string + lessons: { id: number; questionSetId: number; title: string }[] + subModuleKey: string + } | null>(null) + const [selectedLessonIdsBySubModule, setSelectedLessonIdsBySubModule] = useState>({}) const [questionTargetDelete, setQuestionTargetDelete] = useState<{ id: number; practiceId: number; text: string } | null>(null) const [savingPractice, setSavingPractice] = useState(false) const [savingLesson, setSavingLesson] = useState(false) @@ -430,6 +447,12 @@ export function HumanLanguagePage() { setLoading(true) try { await loadHierarchy() + const saved = sessionStorage.getItem(HUMAN_LANGUAGE_SCROLL_KEY) + const targetY = saved ? Number(saved) : 0 + if (Number.isFinite(targetY) && targetY > 0) { + window.requestAnimationFrame(() => window.scrollTo({ top: targetY, behavior: "auto" })) + setTimeout(() => window.scrollTo({ top: targetY, behavior: "auto" }), 250) + } } finally { setLoading(false) } @@ -437,6 +460,18 @@ export function HumanLanguagePage() { run().catch(() => undefined) }, []) + useEffect(() => { + const save = () => sessionStorage.setItem(HUMAN_LANGUAGE_SCROLL_KEY, String(window.scrollY || 0)) + const onBeforeUnload = () => save() + window.addEventListener("scroll", save, { passive: true }) + window.addEventListener("beforeunload", onBeforeUnload) + return () => { + save() + window.removeEventListener("scroll", save) + window.removeEventListener("beforeunload", onBeforeUnload) + } + }, []) + const filteredSubCategories = useMemo( () => selectedSubCategoryId === "ALL" @@ -1185,6 +1220,25 @@ export function HumanLanguagePage() { } } + const handleDeleteSelectedLessonsConfirmed = async () => { + if (!lessonBulkTargetDelete || lessonBulkTargetDelete.lessons.length === 0) return + setDeletingLesson(true) + try { + for (const lesson of lessonBulkTargetDelete.lessons) { + await deleteQuestionSet(lesson.questionSetId) + } + toast.success(`${lessonBulkTargetDelete.lessons.length} lesson(s) deleted`) + setSelectedLessonIdsBySubModule((prev) => ({ ...prev, [lessonBulkTargetDelete.subModuleKey]: [] })) + setLessonBulkTargetDelete(null) + await loadHierarchy() + } catch (error) { + console.error("Failed to delete selected lessons:", error) + toast.error("Failed to delete selected lessons") + } finally { + setDeletingLesson(false) + } + } + const handleDeleteQuestionConfirmed = async () => { if (!questionTargetDelete) return setDeletingQuestion(true) @@ -1212,6 +1266,14 @@ export function HumanLanguagePage() { }) } + const toggleLessonSelection = (smKey: string, lessonId: number) => { + setSelectedLessonIdsBySubModule((prev) => { + const current = prev[smKey] ?? [] + const next = current.includes(lessonId) ? current.filter((id) => id !== lessonId) : [...current, lessonId] + return { ...prev, [smKey]: next } + }) + } + const togglePracticeCard = (smKey: string, practiceId: number) => { const currentPracticeId = subModuleCardSelection[smKey]?.practiceId ?? null const nextPracticeId = currentPracticeId === practiceId ? null : practiceId @@ -1590,7 +1652,7 @@ export function HumanLanguagePage() { const smKey = `${course.course_id}-${subModule.id}` const panelTab = subModulePanelTab[smKey] ?? "lessons" const cardSel = getSubModuleSelection(smKey) - const lessonRows = [ + const lessonRows: LessonListItem[] = [ ...(subModule.lessons ?? []).map((lesson) => ({ id: lesson.id, question_set_id: lesson.question_set_id, @@ -1619,6 +1681,8 @@ export function HumanLanguagePage() { cardSel.lessonId !== null ? lessonRows.find((v) => v.id === cardSel.lessonId) ?? null : null + const selectedLessonIds = selectedLessonIdsBySubModule[smKey] ?? [] + const selectedLessonRows = lessonRows.filter((row) => selectedLessonIds.includes(row.id)) const selectedPracticeMeta = cardSel.practiceId !== null ? practiceRows.find((p) => p.id === cardSel.practiceId) ?? null @@ -1728,16 +1792,42 @@ export function HumanLanguagePage() { New practice ) : panelTab === "lessons" ? ( - +
+ {selectedLessonRows.length > 0 ? ( + + ) : null} + +
) : null} @@ -1787,6 +1877,17 @@ export function HumanLanguagePage() { {v.question_set_id > 0 ? (
+ + + + + + !open && setPracticeTargetDelete(null)}>