From 1987acd734379cbd87ef859b283736f2f5c19897 Mon Sep 17 00:00:00 2001 From: Nurfog Date: Mon, 2 Mar 2026 17:32:48 -0300 Subject: [PATCH] feat: Refactor course editor layout to support dynamic page headers and standardize styling with new utility classes, including some localization. --- .../src/app/courses/[id]/analytics/page.tsx | 319 +++++---- .../app/courses/[id]/announcements/page.tsx | 227 ++++--- .../src/app/courses/[id]/calendar/page.tsx | 148 ++--- .../src/app/courses/[id]/grades/page.tsx | 306 +++++---- .../[id]/settings/TeamManagementSection.tsx | 4 +- .../src/app/courses/[id]/settings/page.tsx | 610 +++++++++--------- .../src/app/courses/[id]/students/page.tsx | 321 +++++---- web/studio/src/app/courses/[id]/team/page.tsx | 290 +++++---- web/studio/src/app/globals.css | 24 + .../src/components/CourseEditorLayout.tsx | 18 +- 10 files changed, 1106 insertions(+), 1161 deletions(-) diff --git a/web/studio/src/app/courses/[id]/analytics/page.tsx b/web/studio/src/app/courses/[id]/analytics/page.tsx index 7028a0a..e2ed49e 100644 --- a/web/studio/src/app/courses/[id]/analytics/page.tsx +++ b/web/studio/src/app/courses/[id]/analytics/page.tsx @@ -9,16 +9,15 @@ import { Users, TrendingUp, AlertTriangle, - ArrowLeft, CheckCircle2, BookOpen, Layers, ShieldAlert } from "lucide-react"; -import CourseEditorLayout from "@/components/CourseEditorLayout"; import DropoutRiskDashboard from "@/components/Analytics/DropoutRiskDashboard"; import LiveSessions from "@/components/Courses/LiveSessions"; import { Video } from "lucide-react"; +import CourseEditorLayout from "@/components/CourseEditorLayout"; export default function AnalyticsPage() { const { id } = useParams() as { id: string }; @@ -34,19 +33,13 @@ export default function AnalyticsPage() { useEffect(() => { const fetchData = async () => { - // Wait for auth to load - if (!user) { - return; - } - - // Check authorization + if (!user) return; if (user.role !== 'admin' && user.role !== 'instructor') { router.push('/'); return; } try { - // Fetch cohorts once const cohortsData = await lmsApi.getCohorts(); setCohorts(cohortsData); @@ -94,185 +87,171 @@ export default function AnalyticsPage() { .sort((a, b) => a.average_score - b.average_score); return ( -
-
- {/* Header */} -
-
- -
-

- Course Analytics -

-

Performance insights and student progress for {course?.title}

-
-
-
- - -
- {user?.role} View -
+ + + +
+ {user?.role} View
+ } + > +
+ {/* Tab Selector */} +
+ + + +
- -
- {/* Tab Selector */} -
- - - + {activeAnalyticsTab === "overview" && ( +
+
+
+
+
+ +
+ Enrollments +
+
{analytics.total_enrollments}
+
Active Learners
+
+ +
+
+
+ +
+ Average Score +
+
{Math.round(analytics.average_score * 100)}%
+
Across all assessments
+
+ +
+
+
+ +
+ Attention Needed +
+
{difficultLessons.length}
+
Struggling Lessons
+
- {activeAnalyticsTab === "overview" ? ( -
- {/* Stats Grid */} -
-
-
-
- +
+
+

+ + Rendimiento de Lecciones +

+
+ {analytics.lessons.map((lesson) => ( +
+
+
+

{lesson.lesson_title}

+

{lesson.submission_count} submissions

+
+
+ {Math.round(lesson.average_score * 100)}% +
- Enrollments -
-
{analytics.total_enrollments}
-
Active Learners
-
- -
-
-
- +
+
- Average Score
-
{Math.round(analytics.average_score * 100)}%
-
Across all assessments
-
- -
-
-
- -
- Attention Needed -
-
{difficultLessons.length}
-
Struggling Lessons
-
+ ))}
+
-
- {/* Lesson Breakdown */} -
-

- - Lesson Performance -

+
+
+

+ + Lecciones con Dificultad +

+ {difficultLessons.length > 0 ? (
- {analytics.lessons.map((lesson) => ( -
-
-
-

{lesson.lesson_title}

-

{lesson.submission_count} submissions

-
-
- {Math.round(lesson.average_score * 100)}% -
-
-
-
+ {difficultLessons.map(l => ( +
+
+

{l.lesson_title}

+

+ Average score is below 70%. Consider reviewing the material or difficulty of questions. +

+
{Math.round(l.average_score * 100)}%
))}
-
- - {/* Actionable Insights */} -
-
-

- - Struggling Lessons -

- {difficultLessons.length > 0 ? ( -
- {difficultLessons.map(l => ( -
-
-

{l.lesson_title}

-

- Average score is below 70%. Consider reviewing the material or difficulty of questions. -

-
-
{Math.round(l.average_score * 100)}%
-
- ))} -
- ) : ( -
- -

All set!

-

No lessons currently fall below the difficulty threshold.

-
- )} + ) : ( +
+ +

All set!

+

No lessons currently fall below the difficulty threshold.

- -
-

- - Content Strategy Tip -

-

- High submission counts with low average scores often indicate that the assessment might be misleading or the prerequisites aren't clearly explained in previous lessons. -

-
-
+ )}
-
- ) : ( - - )} +
+

+ + Content Strategy Tip +

+

+ High submission counts with low average scores often indicate that the assessment might be misleading or the prerequisites aren't clearly explained in previous lessons. +

+
+ +
- + )} + + {activeAnalyticsTab === "risks" && ( + + )} + + {activeAnalyticsTab === "live" && ( + + )}
-
+ ); } diff --git a/web/studio/src/app/courses/[id]/announcements/page.tsx b/web/studio/src/app/courses/[id]/announcements/page.tsx index 94a77c6..463cd93 100644 --- a/web/studio/src/app/courses/[id]/announcements/page.tsx +++ b/web/studio/src/app/courses/[id]/announcements/page.tsx @@ -25,7 +25,7 @@ export default function AnnouncementsPage() { lmsApi.getCohorts() ]); setAnnouncements(annData); - setCohorts(cohortData.filter(c => c.course_id === id)); + setCohorts(cohortData.filter((c: any) => c.course_id === id)); } catch (error) { console.error("Error fetching announcements:", error); } finally { @@ -54,136 +54,127 @@ export default function AnnouncementsPage() { ); return ( -
-
- {/* Header */} -
-
- -
-

- Announcements -

-

Manage course communications and cohort segments

-
-
+ <> + setShowNewModal(true)} - className="flex items-center gap-2 px-6 py-3 bg-orange-600 hover:bg-orange-500 rounded-xl font-bold shadow-lg shadow-orange-500/20 transition-all active:scale-95" + className="flex items-center gap-2 px-6 py-2.5 bg-orange-600 hover:bg-orange-500 text-white rounded-xl font-bold text-sm shadow-md shadow-orange-500/20 transition-all active:scale-95" > - New Announcement + Nuevo Anuncio -
- - -
- {/* Search Bar */} -
-
- - setSearchTerm(e.target.value)} - className="w-full bg-black/20 border border-white/10 rounded-xl py-2 pl-10 pr-4 text-sm focus:outline-none focus:border-orange-500/50 transition-all" - /> -
+ } + > +
+

+ + Comunicados del Curso +

+ {/* Search Bar */} +
+
+ + setSearchTerm(e.target.value)} + className="w-full bg-black/20 border border-white/10 rounded-xl py-2 pl-10 pr-4 text-sm focus:outline-none focus:border-orange-500/50 transition-all" + />
+
- {/* Announcements List */} - {loading ? ( -
- -

Loading announcements...

-
- ) : filteredAnnouncements.length > 0 ? ( -
- {filteredAnnouncements.map((a) => ( -
-
-
-
- {a.author_avatar ? ( - {a.author_name} - ) : ( - a.author_name.charAt(0) + {/* Announcements List */} + {loading ? ( +
+ +

Loading announcements...

+
+ ) : filteredAnnouncements.length > 0 ? ( +
+ {filteredAnnouncements.map((a) => ( +
+
+
+
+ {a.author_avatar ? ( + {a.author_name} + ) : ( + a.author_name.charAt(0) + )} +
+
+

{a.author_name}

+
+ {formatDistanceToNow(new Date(a.created_at), { addSuffix: true, locale: es })} + {a.cohort_ids && a.cohort_ids.length > 0 && ( + <> + +
+ + {a.cohort_ids.length} Cohorts +
+ )}
-
-

{a.author_name}

-
- {formatDistanceToNow(new Date(a.created_at), { addSuffix: true, locale: es })} - {a.cohort_ids && a.cohort_ids.length > 0 && ( - <> - -
- - {a.cohort_ids.length} Cohorts -
- - )} -
-
-
-
- {a.is_pinned && } -
-

{a.title}

-

{a.content}

- - {/* Display Target Cohort Names if segmented */} - {a.cohort_ids && a.cohort_ids.length > 0 && ( -
- {a.cohort_ids.map(cid => { - const cohort = cohorts.find(c => c.id === cid); - return ( - - {cohort?.name || 'Unknown Cohort'} - - ); - })} -
- )} +
+ {a.is_pinned && } + +
- ))} -
- ) : ( -
- -

No announcements found

-

Start by creating a new announcement for your students.

-
- )} -
- -
+

{a.title}

+

{a.content}

- {showNewModal && ( - setShowNewModal(false)} - onSuccess={() => { - setShowNewModal(false); - fetchData(); - }} - /> - )} -
+ {/* Display Target Cohort Names if segmented */} + {a.cohort_ids && a.cohort_ids.length > 0 && ( +
+ {a.cohort_ids.map(cid => { + const cohort = cohorts.find(c => c.id === cid); + return ( + + {cohort?.name || 'Unknown Cohort'} + + ); + })} +
+ )} +
+ ))} +
+ ) : ( +
+ +

No announcements found

+

Start by creating a new announcement for your students.

+
+ )} +
+ + + { + showNewModal && ( + setShowNewModal(false)} + onSuccess={() => { + setShowNewModal(false); + fetchData(); + }} + /> + )} + ); } diff --git a/web/studio/src/app/courses/[id]/calendar/page.tsx b/web/studio/src/app/courses/[id]/calendar/page.tsx index 5e53c91..fa7e56f 100644 --- a/web/studio/src/app/courses/[id]/calendar/page.tsx +++ b/web/studio/src/app/courses/[id]/calendar/page.tsx @@ -98,98 +98,80 @@ export default function CourseCalendarPage({ params }: { params: { id: string } const year = currentDate.getFullYear(); return ( -
-
- {/* Header */} -
-
- -
-

- Course Calendar -

-

Manage important dates and deadlines for {course?.title}

+ +
+
+
+

{monthName} {year}

+
+ + + +
+
+ +
+
+ Examen +
+
+ Tarea +
+
+ En Vivo +
+
+ Lección
- -
-
-
-

{monthName} {year}

-
- - - -
-
- -
-
- Exam -
-
- Assignment -
-
- Live -
-
- Lesson -
-
+
+ {['Dom', 'Lun', 'Mar', 'Mié', 'Jue', 'Vie', 'Sáb'].map(day => ( +
+ {day}
+ ))} + {renderCalendar()} +
-
- {['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'].map(day => ( -
- {day} -
- ))} - {renderCalendar()} -
- -
-

- - Upcoming Deadlines -

-
- {lessons - .filter(l => l.due_date && new Date(l.due_date) >= new Date()) - .sort((a, b) => new Date(a.due_date!).getTime() - new Date(b.due_date!).getTime()) - .slice(0, 6) - .map(lesson => ( -
-
-
-
- {lesson.important_date_type || 'Activity'} -
-
{lesson.title}
-
-
-
{new Date(lesson.due_date!).toLocaleDateString()}
-
Due Date
-
+
+

+ + Upcoming Deadlines +

+
+ {lessons + .filter(l => l.due_date && new Date(l.due_date) >= new Date()) + .sort((a, b) => new Date(a.due_date!).getTime() - new Date(b.due_date!).getTime()) + .slice(0, 6) + .map(lesson => ( +
+
+
+
+ {lesson.important_date_type || 'Activity'}
+
{lesson.title}
- )) - } -
-
+
+
{new Date(lesson.due_date!).toLocaleDateString()}
+
Due Date
+
+
+
+ )) + }
- +
-
+ ); } diff --git a/web/studio/src/app/courses/[id]/grades/page.tsx b/web/studio/src/app/courses/[id]/grades/page.tsx index 4fa728c..d60ef59 100644 --- a/web/studio/src/app/courses/[id]/grades/page.tsx +++ b/web/studio/src/app/courses/[id]/grades/page.tsx @@ -6,7 +6,6 @@ import { lmsApi, cmsApi, StudentGradeReport, Cohort } from "@/lib/api"; import { useAuth } from "@/context/AuthContext"; import CourseEditorLayout from "@/components/CourseEditorLayout"; import { - ArrowLeft, Search, GraduationCap, Download, @@ -142,39 +141,28 @@ export default function GradebookPage() { ); return ( -
-
- {/* Header */} -
-
- -
-

- Gradebook -

-

Student, progress, and performance tracking

-
-
-
- - -
+ + +
+ } + > + <> {/* Bulk Enroll Modal */} {showBulkEnroll && ( @@ -182,7 +170,7 @@ export default function GradebookPage() {

- Bulk Student Enrollment + Inscripción Masiva de Estudiantes