Compare commits
No commits in common. "e4109a26a911a4948001861124b1014e07410265" and "767637a5efd845e5c5a4bf2a60f1adce4a7bf323" have entirely different histories.
e4109a26a9
...
767637a5ef
|
|
@ -1,4 +1,4 @@
|
||||||
# Yimaru Academy LMS Admin Dashboard
|
# Yimaru Academy Admin Dashboard
|
||||||
|
|
||||||
A modern, feature-rich admin dashboard for managing Yimaru Academy's educational platform. Built with React, TypeScript, and Tailwind CSS.
|
A modern, feature-rich admin dashboard for managing Yimaru Academy's educational platform. Built with React, TypeScript, and Tailwind CSS.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -77,31 +77,6 @@ export function HumanLanguagePage() {
|
||||||
return course.levels.filter((l) => l.modules.length > 0).map((l) => l.level.toUpperCase())
|
return course.levels.filter((l) => l.modules.length > 0).map((l) => l.level.toUpperCase())
|
||||||
}, [selectedCourses, selectedCourseId])
|
}, [selectedCourses, selectedCourseId])
|
||||||
|
|
||||||
/** A1 always; A2–C3 only after that level has at least one module (incremental UI). */
|
|
||||||
const visibleCefrLevels = useMemo(() => {
|
|
||||||
if (availableCourses.length === 0) return [] as CefrLevel[]
|
|
||||||
const out: CefrLevel[] = []
|
|
||||||
for (const level of CEFR_LEVELS) {
|
|
||||||
if (level === "A1") {
|
|
||||||
out.push(level)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
const hasContent = selectedCourses.some((c) => {
|
|
||||||
const node = c.levels.find((item) => item.level.toUpperCase() === level)
|
|
||||||
return node !== undefined && node.modules.length > 0
|
|
||||||
})
|
|
||||||
if (hasContent) out.push(level)
|
|
||||||
}
|
|
||||||
return out
|
|
||||||
}, [availableCourses.length, selectedCourses])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (selectedLevel === "ALL") return
|
|
||||||
if (!visibleCefrLevels.includes(selectedLevel)) {
|
|
||||||
setSelectedLevel("ALL")
|
|
||||||
}
|
|
||||||
}, [selectedLevel, visibleCefrLevels])
|
|
||||||
|
|
||||||
const toggleLevel = (level: CefrLevel) => {
|
const toggleLevel = (level: CefrLevel) => {
|
||||||
setCollapsedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level]))
|
setCollapsedLevels((prev) => (prev.includes(level) ? prev.filter((l) => l !== level) : [...prev, level]))
|
||||||
}
|
}
|
||||||
|
|
@ -330,7 +305,7 @@ export function HumanLanguagePage() {
|
||||||
onChange={(e) => setSelectedLevel(e.target.value as CefrLevel | "ALL")}
|
onChange={(e) => setSelectedLevel(e.target.value as CefrLevel | "ALL")}
|
||||||
>
|
>
|
||||||
<option value="ALL">ALL LEVELS</option>
|
<option value="ALL">ALL LEVELS</option>
|
||||||
{visibleCefrLevels.map((level) => (
|
{CEFR_LEVELS.map((level) => (
|
||||||
<option key={level} value={level}>
|
<option key={level} value={level}>
|
||||||
{level}
|
{level}
|
||||||
</option>
|
</option>
|
||||||
|
|
@ -420,9 +395,7 @@ export function HumanLanguagePage() {
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{availableCourses.length > 0
|
{availableCourses.length > 0
|
||||||
? visibleCefrLevels
|
? CEFR_LEVELS.filter((l) => selectedLevel === "ALL" || l === selectedLevel).map((level) => {
|
||||||
.filter((l) => selectedLevel === "ALL" || l === selectedLevel)
|
|
||||||
.map((level) => {
|
|
||||||
const modulesByCourse = selectedCourses.map((course: HumanLanguageCourseTree) => {
|
const modulesByCourse = selectedCourses.map((course: HumanLanguageCourseTree) => {
|
||||||
const levelNode = course.levels.find((item) => item.level.toUpperCase() === level)
|
const levelNode = course.levels.find((item) => item.level.toUpperCase() === level)
|
||||||
return {
|
return {
|
||||||
|
|
@ -430,14 +403,6 @@ export function HumanLanguagePage() {
|
||||||
modules: levelNode?.modules ?? [],
|
modules: levelNode?.modules ?? [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const levelRemoveIds =
|
|
||||||
selectedCourseId === "ALL"
|
|
||||||
? []
|
|
||||||
: (() => {
|
|
||||||
const courseEntry = modulesByCourse.find((entry) => entry.course.course_id === selectedCourseId)
|
|
||||||
return (courseEntry?.modules ?? []).flatMap((m) => m.sub_modules.map((s) => s.id))
|
|
||||||
})()
|
|
||||||
const canRemoveLevel = selectedCourseId !== "ALL" && levelRemoveIds.length > 0
|
|
||||||
return (
|
return (
|
||||||
<Card key={level} className="overflow-hidden border-grayScale-200/80 shadow-sm">
|
<Card key={level} className="overflow-hidden border-grayScale-200/80 shadow-sm">
|
||||||
<div className="flex w-full flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-grayScale-50/60 px-4 py-3">
|
<div className="flex w-full flex-wrap items-center justify-between gap-2 border-b border-grayScale-100 bg-grayScale-50/60 px-4 py-3">
|
||||||
|
|
@ -453,27 +418,19 @@ export function HumanLanguagePage() {
|
||||||
</span>
|
</span>
|
||||||
</button>
|
</button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
title={
|
className="border-red-200 text-red-600 hover:bg-red-50"
|
||||||
selectedCourseId === "ALL"
|
disabled={selectedCourseId === "ALL" || deletingKey === `level-${selectedCourseId}-${level}`}
|
||||||
? "Select a specific course to remove this level"
|
|
||||||
: !canRemoveLevel
|
|
||||||
? "Nothing to remove at this level"
|
|
||||||
: `Remove all content at ${level} for the selected course`
|
|
||||||
}
|
|
||||||
className="h-8 shrink-0 gap-1 border-red-200/90 px-2.5 text-xs font-medium text-red-600 hover:bg-red-50"
|
|
||||||
disabled={!canRemoveLevel || deletingKey === `level-${selectedCourseId}-${level}`}
|
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
if (!canRemoveLevel) return
|
if (selectedCourseId === "ALL") return
|
||||||
const courseEntry = modulesByCourse.find((entry) => entry.course.course_id === selectedCourseId)
|
const courseEntry = modulesByCourse.find((entry) => entry.course.course_id === selectedCourseId)
|
||||||
const ids = (courseEntry?.modules ?? []).flatMap((m) => m.sub_modules.map((s) => s.id))
|
const ids = (courseEntry?.modules ?? []).flatMap((m) => m.sub_modules.map((s) => s.id))
|
||||||
handleDeleteSubModules(ids, `level-${selectedCourseId}-${level}`, `Level ${level} removed`)
|
handleDeleteSubModules(ids, `level-${selectedCourseId}-${level}`, `Level ${level} removed`)
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3.5" aria-hidden />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
Remove
|
Remove level
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
{!collapsedLevels.includes(level) ? (
|
{!collapsedLevels.includes(level) ? (
|
||||||
|
|
@ -524,10 +481,9 @@ export function HumanLanguagePage() {
|
||||||
Add Sub-module
|
Add Sub-module
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 gap-1 border-red-200/90 px-2.5 text-xs font-medium text-red-600 hover:bg-red-50"
|
className="border-red-200 text-red-600 hover:bg-red-50"
|
||||||
disabled={deletingKey === `module-${module.id}`}
|
disabled={deletingKey === `module-${module.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleDeleteSubModules(
|
handleDeleteSubModules(
|
||||||
|
|
@ -537,8 +493,8 @@ export function HumanLanguagePage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3.5" aria-hidden />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
Remove
|
Remove Module
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -555,10 +511,9 @@ export function HumanLanguagePage() {
|
||||||
<Button size="sm">Add practice/audio questions</Button>
|
<Button size="sm">Add practice/audio questions</Button>
|
||||||
</Link>
|
</Link>
|
||||||
<Button
|
<Button
|
||||||
type="button"
|
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="outline"
|
variant="outline"
|
||||||
className="h-8 gap-1 border-red-200/90 px-2.5 text-xs font-medium text-red-600 hover:bg-red-50"
|
className="border-red-200 text-red-600 hover:bg-red-50"
|
||||||
disabled={deletingKey === `submodule-${subModule.id}`}
|
disabled={deletingKey === `submodule-${subModule.id}`}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
handleDeleteSubModules(
|
handleDeleteSubModules(
|
||||||
|
|
@ -568,8 +523,8 @@ export function HumanLanguagePage() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<Trash2 className="h-3 w-3.5" aria-hidden />
|
<Trash2 className="h-3.5 w-3.5" />
|
||||||
Remove
|
Remove Sub-module
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user