From 53a72bef2dbd2ded5ece486cacbffbd34e040d8d Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Tue, 7 Apr 2026 05:30:23 -0700 Subject: [PATCH] human language learn management adjustment --- src/app/AppRoutes.tsx | 2 + .../ContentManagementLayout.tsx | 1 + .../content-management/HumanLanguagePage.tsx | 223 ++++++++++++++++++ src/pages/content-management/SpeakingPage.tsx | 2 +- 4 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 src/pages/content-management/HumanLanguagePage.tsx diff --git a/src/app/AppRoutes.tsx b/src/app/AppRoutes.tsx index 1b2515d..830bf51 100644 --- a/src/app/AppRoutes.tsx +++ b/src/app/AppRoutes.tsx @@ -31,6 +31,7 @@ import { PracticeDetailsPage } from "../pages/content-management/PracticeDetails import { PracticeMembersPage } from "../pages/content-management/PracticeMembersPage" import { QuestionsPage } from "../pages/content-management/QuestionsPage" import { AddQuestionPage } from "../pages/content-management/AddQuestionPage" +import { HumanLanguagePage } from "../pages/content-management/HumanLanguagePage" import { UserLogPage } from "../pages/user-log/UserLogPage" import { IssuesPage } from "../pages/issues/IssuesPage" import { ProfilePage } from "../pages/ProfilePage" @@ -76,6 +77,7 @@ export function AppRoutes() { } /> } /> } /> + } /> } /> } /> {/* Course → Sub-course → Video/Practice */} diff --git a/src/pages/content-management/ContentManagementLayout.tsx b/src/pages/content-management/ContentManagementLayout.tsx index 1cd42ec..5e7dfa0 100644 --- a/src/pages/content-management/ContentManagementLayout.tsx +++ b/src/pages/content-management/ContentManagementLayout.tsx @@ -4,6 +4,7 @@ import { cn } from "../../lib/utils" const tabs = [ { label: "Overview", to: "/content" }, { label: "Courses", to: "/content/courses" }, + { label: "Human Language", to: "/content/human-language" }, { label: "Flows", to: "/content/flows" }, { label: "Speaking", to: "/content/speaking" }, { label: "Practice", to: "/content/practices" }, diff --git a/src/pages/content-management/HumanLanguagePage.tsx b/src/pages/content-management/HumanLanguagePage.tsx new file mode 100644 index 0000000..8ac9cf7 --- /dev/null +++ b/src/pages/content-management/HumanLanguagePage.tsx @@ -0,0 +1,223 @@ +import { useEffect, useMemo, useState } from "react" +import { Link } from "react-router-dom" +import { BookOpen, ChevronDown, ChevronRight, Languages } 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 { getCourseCategories, getCoursesByCategory, getLearningPath } from "../../api/courses.api" +import type { Course, CourseCategory, LearningPathSubCourse } from "../../types/course.types" + +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 [categories, setCategories] = useState([]) + const [courses, setCourses] = useState([]) + const [selectedCategoryId, setSelectedCategoryId] = useState(null) + const [selectedCourseId, setSelectedCourseId] = useState(null) + const [selectedLevel, setSelectedLevel] = useState("ALL") + const [collapsedLevels, setCollapsedLevels] = useState([]) + const [subCourses, setSubCourses] = useState([]) + + useEffect(() => { + const loadCategories = async () => { + setLoading(true) + try { + const res = await getCourseCategories() + const items = res.data?.data?.categories ?? [] + setCategories(items) + const humanLanguageCategory = + items.find((c) => c.name.toLowerCase().includes("human language")) ?? + items.find((c) => c.name.toLowerCase().includes("language")) ?? + null + setSelectedCategoryId(humanLanguageCategory?.id ?? (items[0]?.id ?? null)) + } finally { + setLoading(false) + } + } + loadCategories().catch(() => undefined) + }, []) + + useEffect(() => { + if (!selectedCategoryId) return + const loadCourses = async () => { + setLoading(true) + try { + const res = await getCoursesByCategory(selectedCategoryId) + const items = res.data?.data?.courses ?? [] + setCourses(items) + setSelectedCourseId(items[0]?.id ?? null) + } finally { + setLoading(false) + } + } + loadCourses().catch(() => undefined) + }, [selectedCategoryId]) + + useEffect(() => { + if (!selectedCourseId) return + const loadPath = async () => { + setLoading(true) + try { + const res = await getLearningPath(selectedCourseId) + setSubCourses(res.data?.data?.sub_courses ?? []) + } finally { + setLoading(false) + } + } + loadPath().catch(() => undefined) + }, [selectedCourseId]) + + const grouped = useMemo(() => { + const base = Object.fromEntries(CEFR_LEVELS.map((level) => [level, [] as LearningPathSubCourse[]])) as Record< + CefrLevel, + LearningPathSubCourse[] + > + for (const subCourse of subCourses) { + const level = (subCourse.sub_level ?? "").toUpperCase() as CefrLevel + if (CEFR_LEVELS.includes(level)) { + base[level].push(subCourse) + } + } + return base + }, [subCourses]) + + const levelRows = useMemo( + () => (selectedLevel === "ALL" ? CEFR_LEVELS : [selectedLevel]).map((level) => ({ level, rows: grouped[level] })), + [grouped, selectedLevel], + ) + + const toggleLevel = (level: CefrLevel) => { + setCollapsedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level])) + } + + return ( +
+
+
+
+ +
+
+

Human Language Content

+

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

+
+
+
+ + + + Filters + + +
+ + +
+
+ + +
+
+ + +
+
+
+ + {selectedCategoryId && selectedCourseId ? ( +
+ + + +
+ ) : null} + + {loading ? ( +
+ + Loading human language lessons... +
+ ) : ( +
+ {levelRows.map(({ level, rows }) => ( + + + {!collapsedLevels.includes(level) ? ( + + {rows.length === 0 ? ( +

No lessons found for this level.

+ ) : ( + rows.map((subCourse) => ( +
+

{subCourse.title}

+

+ {subCourse.videos.length} lesson video(s) • {subCourse.practices.length} practice(s) +

+
+ {subCourse.videos.map((video) => ( +
+ + {video.title} +
+ ))} +
+
+ )) + )} +
+ ) : null} +
+ ))} +
+ )} +
+ ) +} + diff --git a/src/pages/content-management/SpeakingPage.tsx b/src/pages/content-management/SpeakingPage.tsx index 8146b0b..ee37608 100644 --- a/src/pages/content-management/SpeakingPage.tsx +++ b/src/pages/content-management/SpeakingPage.tsx @@ -14,7 +14,7 @@ import { getSubCoursesByCourse, createQuestion, createQuestionSet, - getQuestions, + // getQuestions, getPracticeQuestionsByPractice, getQuestionSets, updateQuestion,