added int

This commit is contained in:
elnatansamuel25 2026-05-07 12:29:51 +03:00
parent aa998e5599
commit d1fcfc19b0

View File

@ -1,4 +1,4 @@
import React, { useState } from "react"; import React, { useState, useEffect, useCallback } from "react";
import { import {
DndContext, DndContext,
closestCenter, closestCenter,
@ -33,8 +33,15 @@ import {
Edit2, Edit2,
Trash2, Trash2,
Image as ImageIcon, Image as ImageIcon,
Loader2,
} from "lucide-react"; } from "lucide-react";
import { cn } from "../../../lib/utils"; import { cn } from "../../../lib/utils";
import {
getLearningPrograms,
getProgramCourses,
getTopLevelCourseModules,
getModuleLessons,
} from "../../../api/courses.api";
// --- Types --- // --- Types ---
export type ItemType = "program" | "course" | "module" | "lesson"; export type ItemType = "program" | "course" | "module" | "lesson";
@ -56,46 +63,6 @@ export interface Lesson extends BaseItem {
moduleId: string; 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 --- // --- Components ---
interface SortableItemProps { interface SortableItemProps {
@ -345,14 +312,100 @@ function HierarchySection({
} }
export function ContentHierarchyList() { export function ContentHierarchyList() {
const [programs, setPrograms] = useState<Program[]>(initialPrograms); const [programs, setPrograms] = useState<Program[]>([]);
const [courses, setCourses] = useState<Course[]>(initialCourses); const [courses, setCourses] = useState<Course[]>([]);
const [modules, setModules] = useState<Module[]>(initialModules); const [modules, setModules] = useState<Module[]>([]);
const [lessons, setLessons] = useState<Lesson[]>(initialLessons); const [lessons, setLessons] = useState<Lesson[]>([]);
const [loading, setLoading] = useState<Record<string, boolean>>({});
const [openSections, setOpenSections] = useState<Record<string, boolean>>({ const [openSections, setOpenSections] = useState<Record<string, boolean>>({
program: true, 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) => { const toggleSection = (id: string) => {
setOpenSections((prev) => ({ ...prev, [id]: !prev[id] })); setOpenSections((prev) => ({ ...prev, [id]: !prev[id] }));
}; };
@ -360,11 +413,11 @@ export function ContentHierarchyList() {
const reorder = <T extends BaseItem>( const reorder = <T extends BaseItem>(
list: T[], list: T[],
setList: React.Dispatch<React.SetStateAction<T[]>>, setList: React.Dispatch<React.SetStateAction<T[]>>,
activeId: string, activeId: UniqueIdentifier,
overId: string, overId: UniqueIdentifier,
) => { ) => {
const oldIndex = list.findIndex((i) => i.id === activeId); const oldIndex = list.findIndex((i) => i.id === String(activeId));
const newIndex = list.findIndex((i) => i.id === overId); const newIndex = list.findIndex((i) => i.id === String(overId));
if (oldIndex !== -1 && newIndex !== -1) { if (oldIndex !== -1 && newIndex !== -1) {
setList(arrayMove(list, oldIndex, newIndex)); setList(arrayMove(list, oldIndex, newIndex));
} }
@ -372,7 +425,6 @@ export function ContentHierarchyList() {
const handleEdit = (type: ItemType, id: string) => { const handleEdit = (type: ItemType, id: string) => {
console.log(`Edit ${type}: ${id}`); console.log(`Edit ${type}: ${id}`);
// Logic for opening edit modal would go here
}; };
const handleDelete = (type: ItemType, id: string) => { const handleDelete = (type: ItemType, id: string) => {
@ -396,10 +448,7 @@ export function ContentHierarchyList() {
}; };
const handleReset = () => { const handleReset = () => {
setPrograms(initialPrograms); fetchHierarchy();
setCourses(initialCourses);
setModules(initialModules);
setLessons(initialLessons);
}; };
return ( 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" 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]" /> <RotateCcw className="h-4 w-4 transition-transform group-hover:rotate-[-45deg]" />
Reset All Sync with API
</button> </button>
</div> </div>
@ -430,15 +479,21 @@ export function ContentHierarchyList() {
isOpen={openSections.program} isOpen={openSections.program}
onToggle={() => toggleSection("program")} onToggle={() => toggleSection("program")}
> >
<DraggableList {loading.program ? (
items={programs} <div className="flex items-center justify-center py-8">
onReorder={(active, over) => <Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
reorder(programs, setPrograms, active, over) </div>
} ) : (
icon={<LayoutGrid className="h-4 w-4" />} <DraggableList
onEdit={(id) => handleEdit("program", id)} items={programs}
onDelete={(id) => handleDelete("program", id)} onReorder={(active, over) =>
/> reorder(programs, setPrograms, active, over)
}
icon={<LayoutGrid className="h-4 w-4" />}
onEdit={(id) => handleEdit("program", id)}
onDelete={(id) => handleDelete("program", id)}
/>
)}
</HierarchySection> </HierarchySection>
{/* Course Section */} {/* Course Section */}
@ -448,28 +503,34 @@ export function ContentHierarchyList() {
isOpen={openSections.course} isOpen={openSections.course}
onToggle={() => toggleSection("course")} onToggle={() => toggleSection("course")}
> >
{programs.map((program) => { {loading.course ? (
const programCourses = courses.filter( <div className="flex items-center justify-center py-8">
(c) => c.programId === program.id, <Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
); </div>
if (programCourses.length === 0) return null; ) : (
return ( programs.map((program) => {
<div key={program.id} className="mb-4 last:mb-0"> const programCourses = courses.filter(
<h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1"> (c) => c.programId === program.id,
{program.name} );
</h4> if (programCourses.length === 0) return null;
<DraggableList return (
items={programCourses} <div key={program.id} className="mb-4 last:mb-0">
onReorder={(active, over) => <h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1">
reorder(courses, setCourses, active, over) {program.name}
} </h4>
icon={<BookOpen className="h-4 w-4" />} <DraggableList
onEdit={(id) => handleEdit("course", id)} items={programCourses}
onDelete={(id) => handleDelete("course", id)} onReorder={(active, over) =>
/> reorder(courses, setCourses, active, over)
</div> }
); icon={<BookOpen className="h-4 w-4" />}
})} onEdit={(id) => handleEdit("course", id)}
onDelete={(id) => handleDelete("course", id)}
/>
</div>
);
})
)}
</HierarchySection> </HierarchySection>
{/* Module Section */} {/* Module Section */}
@ -479,28 +540,34 @@ export function ContentHierarchyList() {
isOpen={openSections.module} isOpen={openSections.module}
onToggle={() => toggleSection("module")} onToggle={() => toggleSection("module")}
> >
{courses.map((course) => { {loading.module ? (
const courseModules = modules.filter( <div className="flex items-center justify-center py-8">
(m) => m.courseId === course.id, <Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
); </div>
if (courseModules.length === 0) return null; ) : (
return ( courses.map((course) => {
<div key={course.id} className="mb-4 last:mb-0"> const courseModules = modules.filter(
<h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1"> (m) => m.courseId === course.id,
{course.name} );
</h4> if (courseModules.length === 0) return null;
<DraggableList return (
items={courseModules} <div key={course.id} className="mb-4 last:mb-0">
onReorder={(active, over) => <h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1">
reorder(modules, setModules, active, over) {course.name}
} </h4>
icon={<Layers className="h-4 w-4" />} <DraggableList
onEdit={(id) => handleEdit("module", id)} items={courseModules}
onDelete={(id) => handleDelete("module", id)} onReorder={(active, over) =>
/> reorder(modules, setModules, active, over)
</div> }
); icon={<Layers className="h-4 w-4" />}
})} onEdit={(id) => handleEdit("module", id)}
onDelete={(id) => handleDelete("module", id)}
/>
</div>
);
})
)}
</HierarchySection> </HierarchySection>
{/* Lesson Section */} {/* Lesson Section */}
@ -510,28 +577,34 @@ export function ContentHierarchyList() {
isOpen={openSections.lesson} isOpen={openSections.lesson}
onToggle={() => toggleSection("lesson")} onToggle={() => toggleSection("lesson")}
> >
{modules.map((module) => { {loading.lesson ? (
const moduleLessons = lessons.filter( <div className="flex items-center justify-center py-8">
(l) => l.moduleId === module.id, <Loader2 className="h-6 w-6 text-brand-500 animate-spin" />
); </div>
if (moduleLessons.length === 0) return null; ) : (
return ( modules.map((module) => {
<div key={module.id} className="mb-4 last:mb-0"> const moduleLessons = lessons.filter(
<h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1"> (l) => l.moduleId === module.id,
{module.name} );
</h4> if (moduleLessons.length === 0) return null;
<DraggableList return (
items={moduleLessons} <div key={module.id} className="mb-4 last:mb-0">
onReorder={(active, over) => <h4 className="text-[12px] font-bold text-grayScale-400 uppercase tracking-wider mb-2 px-1">
reorder(lessons, setLessons, active, over) {module.name}
} </h4>
icon={<PlayCircle className="h-4 w-4" />} <DraggableList
onEdit={(id) => handleEdit("lesson", id)} items={moduleLessons}
onDelete={(id) => handleDelete("lesson", id)} onReorder={(active, over) =>
/> reorder(lessons, setLessons, active, over)
</div> }
); icon={<PlayCircle className="h-4 w-4" />}
})} onEdit={(id) => handleEdit("lesson", id)}
onDelete={(id) => handleDelete("lesson", id)}
/>
</div>
);
})
)}
</HierarchySection> </HierarchySection>
</div> </div>
</div> </div>