From 449b595df0980d9b57a523b8fe95008bbc9cc923 Mon Sep 17 00:00:00 2001 From: Yared Yemane Date: Tue, 7 Apr 2026 09:12:57 -0700 Subject: [PATCH] more UI adjustment --- .../content-management/HumanLanguagePage.tsx | 260 +++++++++++++++--- 1 file changed, 225 insertions(+), 35 deletions(-) diff --git a/src/pages/content-management/HumanLanguagePage.tsx b/src/pages/content-management/HumanLanguagePage.tsx index a8f0f78..ec54e04 100644 --- a/src/pages/content-management/HumanLanguagePage.tsx +++ b/src/pages/content-management/HumanLanguagePage.tsx @@ -13,10 +13,40 @@ import { } from "../../components/ui/dialog" import { SpinnerIcon } from "../../components/ui/spinner-icon" import { createCourse, createCourseCategory, createHumanLanguageLesson, deleteSubCourse, getHumanLanguageHierarchy } from "../../api/courses.api" -import type { HumanLanguageCourseTree, HumanLanguageSubCategoryTree } from "../../types/course.types" +import { Badge } from "../../components/ui/badge" +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "../../components/ui/table" +import type { + HumanLanguageCourseTree, + HumanLanguageSubCategoryTree, + LearningPathPractice, + LearningPathVideo, +} from "../../types/course.types" import { toast } from "sonner" const CEFR_LEVELS = ["A1", "A2", "A3", "B1", "B2", "B3", "C1", "C2", "C3"] as const +type SubModulePanelTab = "lessons" | "practices" + +function formatDurationSeconds(total: number): string { + const s = Math.max(0, Math.floor(total)) + const m = Math.floor(s / 60) + const r = s % 60 + return `${m}:${r.toString().padStart(2, "0")}` +} + +function truncateMiddle(str: string, max = 42): string { + const t = str.trim() + if (t.length <= max) return t + const half = Math.floor((max - 3) / 2) + return `${t.slice(0, half)}…${t.slice(-half)}` +} + +function practiceStatusStyle(status: string): string { + const u = status.toUpperCase() + if (u === "PUBLISHED") return "bg-green-50 text-green-700 ring-1 ring-inset ring-green-200" + if (u === "DRAFT") return "bg-grayScale-50 text-grayScale-600 ring-1 ring-inset ring-grayScale-200" + if (u === "ARCHIVED") return "bg-amber-50 text-amber-700 ring-1 ring-inset ring-amber-200" + return "bg-grayScale-50 text-grayScale-600 ring-1 ring-inset ring-grayScale-200" +} type CefrLevel = (typeof CEFR_LEVELS)[number] type PendingRemove = { @@ -44,6 +74,8 @@ export function HumanLanguagePage() { /** Course IDs whose path body is collapsed (headers stay visible). */ const [collapsedPathIds, setCollapsedPathIds] = useState([]) const [pendingRemove, setPendingRemove] = useState(null) + /** Per sub-module panel tab (lessons table vs practices table). */ + const [subModulePanelTab, setSubModulePanelTab] = useState>({}) const loadHierarchy = async () => { setLoading(true) @@ -575,47 +607,205 @@ export function HumanLanguagePage() { - {module.sub_modules.map((subModule) => ( -
-
-

Sub-module: {subModule.title}

- {categoryId ? ( -
- - + + - -
+ +
+
+ + Lessons + {panelTab === "lessons" ? ( + + ) : null} + +
- ) : null} +
+ +
+ {panelTab === "lessons" ? ( + lessonRows.length === 0 ? ( +
+ No lesson videos yet. Use{" "} + Open editor to add + videos. +
+ ) : ( + + + + # + Title + Duration + Order + Video URL + + + + {lessonRows.map((v, idx) => ( + + {idx + 1} + {v.title} + + {formatDurationSeconds(v.duration ?? 0)} + + + {v.display_order} + + + {v.video_url ? ( + + {truncateMiddle(v.video_url, 48)} + + ) : ( + + )} + + + ))} + +
+ ) + ) : practiceRows.length === 0 ? ( +
+ No practices yet. Use{" "} + Open editor to create a + practice. +
+ ) : ( + + + + # + Title + Status + Questions + Order + Actions + + + + {practiceRows.map((p, idx) => ( + + {idx + 1} + +

+ {p.title} +

+
+ + + {(p.status ?? "—").replace(/_/g, " ").toLowerCase()} + + + + {p.question_count} + + + {p.display_order ?? "—"} + + + {categoryId ? ( + + Questions + + ) : ( + + )} + +
+ ))} +
+
+ )} +
-

- {subModule.videos.length} lesson video(s) · {subModule.practices.length} practice(s) -

-
- ))} + ) + })} )) )}