added int
This commit is contained in:
parent
aa998e5599
commit
d1fcfc19b0
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect, useCallback } from "react";
|
||||
import {
|
||||
DndContext,
|
||||
closestCenter,
|
||||
|
|
@ -33,8 +33,15 @@ import {
|
|||
Edit2,
|
||||
Trash2,
|
||||
Image as ImageIcon,
|
||||
Loader2,
|
||||
} from "lucide-react";
|
||||
import { cn } from "../../../lib/utils";
|
||||
import {
|
||||
getLearningPrograms,
|
||||
getProgramCourses,
|
||||
getTopLevelCourseModules,
|
||||
getModuleLessons,
|
||||
} from "../../../api/courses.api";
|
||||
|
||||
// --- Types ---
|
||||
export type ItemType = "program" | "course" | "module" | "lesson";
|
||||
|
|
@ -56,46 +63,6 @@ export interface Lesson extends BaseItem {
|
|||
moduleId: string;
|
||||
}
|
||||
|
||||
// --- Mock Data ---
|
||||
const initialPrograms: Program[] = [
|
||||
{
|
||||
id: "p1",
|
||||
name: "Web Development Masterclass",
|
||||
thumbnail:
|
||||
"https://images.unsplash.com/photo-1498050108023-c5249f4df085?w=100&h=100&fit=crop",
|
||||
},
|
||||
{
|
||||
id: "p2",
|
||||
name: "Mobile App Development",
|
||||
thumbnail:
|
||||
"https://images.unsplash.com/photo-1512941937669-90a1b58e7e9c?w=100&h=100&fit=crop",
|
||||
},
|
||||
{
|
||||
id: "p3",
|
||||
name: "UI/UX Design Fundamentals",
|
||||
thumbnail:
|
||||
"https://images.unsplash.com/photo-1586717791821-3f44a563eb4c?w=100&h=100&fit=crop",
|
||||
},
|
||||
];
|
||||
|
||||
const initialCourses: Course[] = [
|
||||
{ id: "c1", name: "React for Beginners", programId: "p1" },
|
||||
{ id: "c2", name: "Advanced Node.js", programId: "p1" },
|
||||
{ id: "c3", name: "Swift UI Intro", programId: "p2" },
|
||||
];
|
||||
|
||||
const initialModules: Module[] = [
|
||||
{ id: "m1", name: "Introduction to Hooks", courseId: "c1" },
|
||||
{ id: "m2", name: "State Management", courseId: "c1" },
|
||||
{ id: "m3", name: "Backend Architecture", courseId: "c2" },
|
||||
];
|
||||
|
||||
const initialLessons: Lesson[] = [
|
||||
{ id: "l1", name: "What is useState?", moduleId: "m1" },
|
||||
{ id: "l2", name: "useEffect deep dive", moduleId: "m1" },
|
||||
{ id: "l3", name: "Redux Setup", moduleId: "m2" },
|
||||
];
|
||||
|
||||
// --- Components ---
|
||||
|
||||
interface SortableItemProps {
|
||||
|
|
@ -345,14 +312,100 @@ function HierarchySection({
|
|||
}
|
||||
|
||||
export function ContentHierarchyList() {
|
||||
const [programs, setPrograms] = useState<Program[]>(initialPrograms);
|
||||
const [courses, setCourses] = useState<Course[]>(initialCourses);
|
||||
const [modules, setModules] = useState<Module[]>(initialModules);
|
||||
const [lessons, setLessons] = useState<Lesson[]>(initialLessons);
|
||||
const [programs, setPrograms] = useState<Program[]>([]);
|
||||
const [courses, setCourses] = useState<Course[]>([]);
|
||||
const [modules, setModules] = useState<Module[]>([]);
|
||||
const [lessons, setLessons] = useState<Lesson[]>([]);
|
||||
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
||||
const [openSections, setOpenSections] = useState<Record<string, boolean>>({
|
||||
program: true,
|
||||
});
|
||||
|
||||
const fetchHierarchy = useCallback(async () => {
|
||||
setLoading({ program: true });
|
||||
try {
|
||||
// 1. Fetch Programs
|
||||
const programsRes = await getLearningPrograms();
|
||||
const programData = programsRes.data?.data;
|
||||
const fetchedPrograms: Program[] = (programData?.programs || []).map(
|
||||
(p) => ({
|
||||
id: String(p.id),
|
||||
name: p.name,
|
||||
thumbnail: p.thumbnail || undefined,
|
||||
}),
|
||||
);
|
||||
setPrograms(fetchedPrograms);
|
||||
setLoading((prev) => ({ ...prev, program: false }));
|
||||
|
||||
if (fetchedPrograms.length === 0) return;
|
||||
|
||||
// 2. Fetch Courses for all programs
|
||||
setLoading((prev) => ({ ...prev, course: true }));
|
||||
const coursesPromises = fetchedPrograms.map((p) =>
|
||||
getProgramCourses(Number(p.id)),
|
||||
);
|
||||
const coursesResults = await Promise.all(coursesPromises);
|
||||
const fetchedCourses: Course[] = coursesResults.flatMap((res, idx) => {
|
||||
const courseData = res.data?.data;
|
||||
return (courseData?.courses || []).map((c) => ({
|
||||
id: String(c.id),
|
||||
name: c.name,
|
||||
thumbnail: c.thumbnail_url || c.thumbnail || undefined,
|
||||
programId: fetchedPrograms[idx].id,
|
||||
}));
|
||||
});
|
||||
setCourses(fetchedCourses);
|
||||
setLoading((prev) => ({ ...prev, course: false }));
|
||||
|
||||
if (fetchedCourses.length === 0) return;
|
||||
|
||||
// 3. Fetch Modules for all courses
|
||||
setLoading((prev) => ({ ...prev, module: true }));
|
||||
const modulesPromises = fetchedCourses.map((c) =>
|
||||
getTopLevelCourseModules(Number(c.id)),
|
||||
);
|
||||
const modulesResults = await Promise.all(modulesPromises);
|
||||
const fetchedModules: Module[] = modulesResults.flatMap((res, idx) => {
|
||||
const moduleData = res.data?.data;
|
||||
return (moduleData?.modules || []).map((m) => ({
|
||||
id: String(m.id),
|
||||
name: m.name,
|
||||
thumbnail: m.icon || undefined,
|
||||
courseId: fetchedCourses[idx].id,
|
||||
}));
|
||||
});
|
||||
setModules(fetchedModules);
|
||||
setLoading((prev) => ({ ...prev, module: false }));
|
||||
|
||||
if (fetchedModules.length === 0) return;
|
||||
|
||||
// 4. Fetch Lessons for all modules
|
||||
setLoading((prev) => ({ ...prev, lesson: true }));
|
||||
const lessonsPromises = fetchedModules.map((m) =>
|
||||
getModuleLessons(Number(m.id)),
|
||||
);
|
||||
const lessonsResults = await Promise.all(lessonsPromises);
|
||||
const fetchedLessons: Lesson[] = lessonsResults.flatMap((res, idx) => {
|
||||
const lessonData = res.data?.data;
|
||||
return (lessonData?.lessons || []).map((l) => ({
|
||||
id: String(l.id),
|
||||
name: l.title,
|
||||
thumbnail: l.thumbnail || undefined,
|
||||
moduleId: fetchedModules[idx].id,
|
||||
}));
|
||||
});
|
||||
setLessons(fetchedLessons);
|
||||
} catch (error) {
|
||||
console.error("Failed to fetch content hierarchy:", error);
|
||||
} finally {
|
||||
setLoading({});
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
fetchHierarchy();
|
||||
}, [fetchHierarchy]);
|
||||
|
||||
const toggleSection = (id: string) => {
|
||||
setOpenSections((prev) => ({ ...prev, [id]: !prev[id] }));
|
||||
};
|
||||
|
|
@ -360,11 +413,11 @@ export function ContentHierarchyList() {
|
|||
const reorder = <T extends BaseItem>(
|
||||
list: T[],
|
||||
setList: React.Dispatch<React.SetStateAction<T[]>>,
|
||||
activeId: string,
|
||||
overId: string,
|
||||
activeId: UniqueIdentifier,
|
||||
overId: UniqueIdentifier,
|
||||
) => {
|
||||
const oldIndex = list.findIndex((i) => i.id === activeId);
|
||||
const newIndex = list.findIndex((i) => i.id === overId);
|
||||
const oldIndex = list.findIndex((i) => i.id === String(activeId));
|
||||
const newIndex = list.findIndex((i) => i.id === String(overId));
|
||||
if (oldIndex !== -1 && newIndex !== -1) {
|
||||
setList(arrayMove(list, oldIndex, newIndex));
|
||||
}
|
||||
|
|
@ -372,7 +425,6 @@ export function ContentHierarchyList() {
|
|||
|
||||
const handleEdit = (type: ItemType, id: string) => {
|
||||
console.log(`Edit ${type}: ${id}`);
|
||||
// Logic for opening edit modal would go here
|
||||
};
|
||||
|
||||
const handleDelete = (type: ItemType, id: string) => {
|
||||
|
|
@ -396,10 +448,7 @@ export function ContentHierarchyList() {
|
|||
};
|
||||
|
||||
const handleReset = () => {
|
||||
setPrograms(initialPrograms);
|
||||
setCourses(initialCourses);
|
||||
setModules(initialModules);
|
||||
setLessons(initialLessons);
|
||||
fetchHierarchy();
|
||||
};
|
||||
|
||||
return (
|
||||
|
|
@ -418,7 +467,7 @@ export function ContentHierarchyList() {
|
|||
className="text-[13px] font-bold text-brand-300 hover:text-brand-400 transition-colors flex items-center gap-2 group"
|
||||
>
|
||||
<RotateCcw className="h-4 w-4 transition-transform group-hover:rotate-[-45deg]" />
|
||||
Reset All
|
||||
Sync with API
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
@ -430,6 +479,11 @@ export function ContentHierarchyList() {
|
|||
isOpen={openSections.program}
|
||||
onToggle={() => toggleSection("program")}
|
||||
>
|
||||
{loading.program ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<DraggableList
|
||||
items={programs}
|
||||
onReorder={(active, over) =>
|
||||
|
|
@ -439,6 +493,7 @@ export function ContentHierarchyList() {
|
|||
onEdit={(id) => handleEdit("program", id)}
|
||||
onDelete={(id) => handleDelete("program", id)}
|
||||
/>
|
||||
)}
|
||||
</HierarchySection>
|
||||
|
||||
{/* Course Section */}
|
||||
|
|
@ -448,7 +503,12 @@ export function ContentHierarchyList() {
|
|||
isOpen={openSections.course}
|
||||
onToggle={() => toggleSection("course")}
|
||||
>
|
||||
{programs.map((program) => {
|
||||
{loading.course ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
programs.map((program) => {
|
||||
const programCourses = courses.filter(
|
||||
(c) => c.programId === program.id,
|
||||
);
|
||||
|
|
@ -469,7 +529,8 @@ export function ContentHierarchyList() {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</HierarchySection>
|
||||
|
||||
{/* Module Section */}
|
||||
|
|
@ -479,7 +540,12 @@ export function ContentHierarchyList() {
|
|||
isOpen={openSections.module}
|
||||
onToggle={() => toggleSection("module")}
|
||||
>
|
||||
{courses.map((course) => {
|
||||
{loading.module ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
courses.map((course) => {
|
||||
const courseModules = modules.filter(
|
||||
(m) => m.courseId === course.id,
|
||||
);
|
||||
|
|
@ -500,7 +566,8 @@ export function ContentHierarchyList() {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</HierarchySection>
|
||||
|
||||
{/* Lesson Section */}
|
||||
|
|
@ -510,7 +577,12 @@ export function ContentHierarchyList() {
|
|||
isOpen={openSections.lesson}
|
||||
onToggle={() => toggleSection("lesson")}
|
||||
>
|
||||
{modules.map((module) => {
|
||||
{loading.lesson ? (
|
||||
<div className="flex items-center justify-center py-8">
|
||||
<Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
modules.map((module) => {
|
||||
const moduleLessons = lessons.filter(
|
||||
(l) => l.moduleId === module.id,
|
||||
);
|
||||
|
|
@ -531,7 +603,8 @@ export function ContentHierarchyList() {
|
|||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
})
|
||||
)}
|
||||
</HierarchySection>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user