import { useEffect, useMemo, useState } from "react" import { Link } from "react-router-dom" import { BookOpen, ChevronDown, ChevronRight, Languages, Loader2, Plus } from "lucide-react" import { Card, CardContent, CardHeader, CardTitle } from "../../components/ui/card" import { Button } from "../../components/ui/button" import { SpinnerIcon } from "../../components/ui/spinner-icon" import { createCourse, createHumanLanguageLesson, getHumanLanguageHierarchy } from "../../api/courses.api" import type { HumanLanguageCourseTree, HumanLanguageSubCategoryTree } from "../../types/course.types" import { toast } from "sonner" const CEFR_LEVELS = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"] as const type CefrLevel = (typeof CEFR_LEVELS)[number] export function HumanLanguagePage() { const [loading, setLoading] = useState(false) const [categoryId, setCategoryId] = useState(null) const [subCategories, setSubCategories] = useState([]) const [selectedSubCategoryId, setSelectedSubCategoryId] = useState("ALL") const [selectedCourseId, setSelectedCourseId] = useState("ALL") const [selectedLevel, setSelectedLevel] = useState("ALL") const [collapsedLevels, setCollapsedLevels] = useState([]) const [creatingKey, setCreatingKey] = useState(null) const [quickSubCategoryName, setQuickSubCategoryName] = useState("") const [quickCourseName, setQuickCourseName] = useState("") const [quickCreating, setQuickCreating] = useState(false) const loadHierarchy = async () => { setLoading(true) try { const res = await getHumanLanguageHierarchy() const data = res.data?.data setCategoryId(data?.category_id ?? null) setSubCategories(data?.sub_categories ?? []) } finally { setLoading(false) } } useEffect(() => { const run = async () => { setLoading(true) try { await loadHierarchy() } finally { setLoading(false) } } run().catch(() => undefined) }, []) const filteredSubCategories = useMemo( () => selectedSubCategoryId === "ALL" ? subCategories : subCategories.filter((s) => s.sub_category_id === selectedSubCategoryId), [subCategories, selectedSubCategoryId], ) const availableCourses = useMemo(() => { return filteredSubCategories.flatMap((s) => s.courses) }, [filteredSubCategories]) const selectedCourses = useMemo( () => selectedCourseId === "ALL" ? availableCourses : availableCourses.filter((c) => c.course_id === selectedCourseId), [availableCourses, selectedCourseId], ) const toggleLevel = (level: CefrLevel) => { setCollapsedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level])) } const parseModuleNumber = (title: string): number | null => { const match = title.match(/module-(\d+)/i) if (!match) return null const value = Number(match[1]) return Number.isFinite(value) ? value : null } const parseSubModuleNumber = (title: string): { module: number; sub: number } | null => { const match = title.match(/(?:sub-)?module-(\d+)\.(\d+)/i) if (!match) return null const module = Number(match[1]) const sub = Number(match[2]) if (!Number.isFinite(module) || !Number.isFinite(sub)) return null return { module, sub } } const handleCreateModule = async (courseId: number, level: string, modules: { title: string }[]) => { const key = `module-${courseId}-${level}` setCreatingKey(key) try { const maxExisting = modules .map((m) => parseModuleNumber(m.title)) .filter((v): v is number => v !== null) .reduce((acc, n) => Math.max(acc, n), 0) const next = maxExisting + 1 const title = `Module-${next}` await createHumanLanguageLesson({ course_id: courseId, cefr_level: level, title, description: `${level} ${title}`, }) toast.success(`${title} created`) await loadHierarchy() } catch (error) { console.error("Failed to create module:", error) toast.error("Failed to create module") } finally { setCreatingKey(null) } } const handleCreateSubModule = async ( courseId: number, level: string, moduleTitle: string, existingSubModules: { title: string }[], ) => { const moduleNo = parseModuleNumber(moduleTitle) if (!moduleNo) { toast.error("Cannot derive module number from title") return } const key = `submodule-${courseId}-${level}-${moduleNo}` setCreatingKey(key) try { const maxExisting = existingSubModules .map((s) => parseSubModuleNumber(s.title)) .filter((v): v is { module: number; sub: number } => v !== null && v.module === moduleNo) .reduce((acc, item) => Math.max(acc, item.sub), 0) const next = maxExisting + 1 const title = `Module-${moduleNo}.${next}` await createHumanLanguageLesson({ course_id: courseId, cefr_level: level, title, description: `${level} ${title}`, }) toast.success(`Sub-module ${moduleNo}.${next} created`) await loadHierarchy() } catch (error) { console.error("Failed to create sub-module:", error) toast.error("Failed to create sub-module") } finally { setCreatingKey(null) } } const handleQuickCreatePath = async () => { if (!categoryId) { toast.error("Human Language category is not available") return } if (!quickSubCategoryName.trim() || !quickCourseName.trim()) { toast.error("Subcategory and course names are required") return } setQuickCreating(true) try { const title = `${quickSubCategoryName.trim()} - ${quickCourseName.trim()}` await createCourse({ category_id: categoryId, title, description: `${quickSubCategoryName.trim()} / ${quickCourseName.trim()}`, }) toast.success("Subcategory/course path created") setQuickSubCategoryName("") setQuickCourseName("") await loadHierarchy() } catch (error) { console.error("Failed to quick-create language path:", error) toast.error("Failed to create subcategory/course path") } finally { setQuickCreating(false) } } return (

Human Language Content

Dedicated management view for CEFR levels A1 to C3 with no sub-levels.

Filters
{categoryId && selectedCourseId !== "ALL" ? (
) : null} {loading ? (
Loading human language lessons...
) : (
{availableCourses.length === 0 ? (

No Human Language subcategory/course is available yet. Create the language course path first, then you can add incremental modules and sub-modules per level.

setQuickSubCategoryName(e.target.value)} /> setQuickCourseName(e.target.value)} />
{categoryId ? ( ) : null}
) : null} {CEFR_LEVELS.filter((l) => selectedLevel === "ALL" || l === selectedLevel).map((level) => { const modulesByCourse = selectedCourses .map((course: HumanLanguageCourseTree) => { const levelNode = course.levels.find((item) => item.level.toUpperCase() === level) return { course, modules: levelNode?.modules ?? [], } }) return ( {!collapsedLevels.includes(level) ? ( {modulesByCourse.length === 0 ? (

No lessons found for this level.

) : ( modulesByCourse.map((entry) => (

{entry.course.course_name}

{entry.modules.length === 0 ? (

No modules yet. Use “Add Module” to start.

) : ( entry.modules.map((module) => (

Module: {module.title}

{module.sub_modules.map((subModule) => (

Sub-module: {subModule.title}

{categoryId ? (
) : null}
{subModule.videos.map((video) => (
{video.title}
))} {subModule.practices.map((practice) => (
Practice: {practice.title} ({practice.question_count} audio question(s))
))}
))}
)) )}
)) )}
) : null}
) })}
)}
) }