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,6 +479,11 @@ export function ContentHierarchyList() {
isOpen={openSections.program} isOpen={openSections.program}
onToggle={() => toggleSection("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 <DraggableList
items={programs} items={programs}
onReorder={(active, over) => onReorder={(active, over) =>
@ -439,6 +493,7 @@ export function ContentHierarchyList() {
onEdit={(id) => handleEdit("program", id)} onEdit={(id) => handleEdit("program", id)}
onDelete={(id) => handleDelete("program", id)} onDelete={(id) => handleDelete("program", id)}
/> />
)}
</HierarchySection> </HierarchySection>
{/* Course Section */} {/* Course Section */}
@ -448,7 +503,12 @@ export function ContentHierarchyList() {
isOpen={openSections.course} isOpen={openSections.course}
onToggle={() => toggleSection("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( const programCourses = courses.filter(
(c) => c.programId === program.id, (c) => c.programId === program.id,
); );
@ -469,7 +529,8 @@ export function ContentHierarchyList() {
/> />
</div> </div>
); );
})} })
)}
</HierarchySection> </HierarchySection>
{/* Module Section */} {/* Module Section */}
@ -479,7 +540,12 @@ export function ContentHierarchyList() {
isOpen={openSections.module} isOpen={openSections.module}
onToggle={() => toggleSection("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( const courseModules = modules.filter(
(m) => m.courseId === course.id, (m) => m.courseId === course.id,
); );
@ -500,7 +566,8 @@ export function ContentHierarchyList() {
/> />
</div> </div>
); );
})} })
)}
</HierarchySection> </HierarchySection>
{/* Lesson Section */} {/* Lesson Section */}
@ -510,7 +577,12 @@ export function ContentHierarchyList() {
isOpen={openSections.lesson} isOpen={openSections.lesson}
onToggle={() => toggleSection("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( const moduleLessons = lessons.filter(
(l) => l.moduleId === module.id, (l) => l.moduleId === module.id,
); );
@ -531,7 +603,8 @@ export function ContentHierarchyList() {
/> />
</div> </div>
); );
})} })
)}
</HierarchySection> </HierarchySection>
</div> </div>
</div> </div>