From 584056e3794107022643f9922c1f99da742cdbd8 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 2 Sep 2026 15:07:41 +0700 Subject: [PATCH] feat: enhance course class management by adding course class assignments and improving course selection UI --- .../Admin/Manage/CourseClassController.php | 1 + app/Services/Admin/Master/CourseService.php | 8 +- resources/js/components/app-sidebar.tsx | 18 +- .../admin/manage/course-classes/index.tsx | 246 +++++++++++++----- 4 files changed, 204 insertions(+), 69 deletions(-) diff --git a/app/Http/Controllers/Admin/Manage/CourseClassController.php b/app/Http/Controllers/Admin/Manage/CourseClassController.php index 5265235..d84ea7c 100644 --- a/app/Http/Controllers/Admin/Manage/CourseClassController.php +++ b/app/Http/Controllers/Admin/Manage/CourseClassController.php @@ -34,6 +34,7 @@ public function index(PaginatedRequest $request): Response 'courses' => $this->courseService->getAllForSelect(), 'lecturers' => $this->lecturerService->getAllForSelect(), 'academicTerms' => $this->academicTermService->getAllForSelect(), + 'courseClassAssignments' => $this->service->getAllForSelect(), 'filters' => $request->only(['academic_term_id', 'method']), ]); } diff --git a/app/Services/Admin/Master/CourseService.php b/app/Services/Admin/Master/CourseService.php index 6c8658e..74fb144 100644 --- a/app/Services/Admin/Master/CourseService.php +++ b/app/Services/Admin/Master/CourseService.php @@ -10,7 +10,13 @@ class CourseService { public function getAllForSelect(): Collection { - return Course::select(['id', 'code', 'name', 'department_id'])->get(); + return Course::query() + ->select(['courses.id', 'courses.code', 'courses.name', 'courses.department_id', 'courses.semester_number']) + ->join('departments', 'departments.id', '=', 'courses.department_id') + ->with('department:id,name') + ->orderBy('departments.name') + ->orderBy('courses.semester_number') + ->get(); } public function getSemesterNumbers(): array diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index cf7af54..a3fb7c0 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -112,15 +112,6 @@ function buildNavMain({ ]; const kelolaItems: NavItem[] = [ - ...(can('view-course-registrations') - ? [ - { - name: 'Registrasi KRS', - url: courseRegistrationsRoute.url(), - icon: FileCheck2, - }, - ] - : []), ...(can('view-course-classes') ? [ { @@ -130,6 +121,15 @@ function buildNavMain({ }, ] : []), + ...(can('view-course-registrations') + ? [ + { + name: 'Registrasi KRS', + url: courseRegistrationsRoute.url(), + icon: FileCheck2, + }, + ] + : []), ...(can('view-announcements') ? [ { diff --git a/resources/js/pages/admin/manage/course-classes/index.tsx b/resources/js/pages/admin/manage/course-classes/index.tsx index 065d9b5..01e98fe 100644 --- a/resources/js/pages/admin/manage/course-classes/index.tsx +++ b/resources/js/pages/admin/manage/course-classes/index.tsx @@ -12,10 +12,13 @@ import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; import { Combobox, + ComboboxCollection, ComboboxContent, ComboboxEmpty, + ComboboxGroup, ComboboxInput, ComboboxItem, + ComboboxLabel, ComboboxList, } from '@/components/ui/combobox'; import { Label } from '@/components/ui/label'; @@ -39,7 +42,15 @@ import { ClassMethodLabels, ClassMethods } from '@/types/course-class'; import type { CourseClass } from '@/types/course-class'; import { createCourseClassColumns } from './columns'; -type Course = { id: number; code: string; name: string; department_id: number }; +type Course = { + id: number; + code: string; + name: string; + department_id: number; + department: { id: number; name: string } | null; + semester_number: number | null; +}; +type CourseGroup = { value: string; items: Course[] }; type Lecturer = { id: number; lecturer_number: string; @@ -51,6 +62,29 @@ type AcademicTerm = { academic_year: string; semester: string; }; +type CourseClassAssignment = { + id: number; + course_id: number; + academic_term_id: number; +}; + +function groupCoursesByDepartmentAndSemester(courses: Course[]): CourseGroup[] { + const groups: CourseGroup[] = []; + let currentKey: string | null = null; + + for (const course of courses) { + const key = `${course.department?.name ?? 'Tanpa Jurusan'} — Semester ${course.semester_number ?? 'Tidak ditentukan'}`; + + if (key !== currentKey) { + currentKey = key; + groups.push({ value: key, items: [] }); + } + + groups[groups.length - 1].items.push(course); + } + + return groups; +} type Props = { courseClasses: { @@ -63,6 +97,7 @@ type Props = { courses: Course[]; lecturers: Lecturer[]; academicTerms: AcademicTerm[]; + courseClassAssignments: CourseClassAssignment[]; highlight?: number; filters: { academic_term_id?: string; @@ -75,6 +110,7 @@ export default function CourseClassIndex({ courses, lecturers, academicTerms, + courseClassAssignments, highlight, filters, }: Props) { @@ -193,6 +229,7 @@ export default function CourseClassIndex({ courses={courses} lecturers={lecturers} academicTerms={academicTerms} + courseClassAssignments={courseClassAssignments} /> void; courses: Course[]; lecturers: Lecturer[]; academicTerms: AcademicTerm[]; + courseClassAssignments: CourseClassAssignment[]; }) { - const [courseId, setCourseId] = useState(''); + const [academicTermId, setAcademicTermId] = useState(''); + const [course, setCourse] = useState(null); const [lecturer, setLecturer] = useState(null); - const selectedCourse = courses.find((c) => String(c.id) === courseId); - const availableLecturers = selectedCourse + const takenCourseIds = new Set( + courseClassAssignments + .filter((a) => String(a.academic_term_id) === academicTermId) + .map((a) => a.course_id), + ); + const availableCourses = courses.filter((c) => !takenCourseIds.has(c.id)); + const courseGroups = groupCoursesByDepartmentAndSemester(availableCourses); + const availableLecturers = course ? lecturers.filter((l) => l.departments.some( - (department) => - department.id === selectedCourse.department_id, + (department) => department.id === course.department_id, ), ) : lecturers; function reset() { - setCourseId(''); + setAcademicTermId(''); + setCourse(null); setLecturer(null); } @@ -295,8 +342,19 @@ function CreateForm({ Periode Akademik{' '} * - - + - + + + + Mata kuliah tidak ditemukan. + + + {(group: CourseGroup) => ( + + + {group.value} + + + {(c: Course) => ( + + {c.code} - {c.name} + + )} + + + )} + + +
@@ -364,12 +443,12 @@ function CreateForm({ `${lect.user?.profile?.full_name ?? 'N/A'} - ${lect.lecturer_number}` } isItemEqualToValue={(a, b) => a.id === b.id} - disabled={!selectedCourse} + disabled={!course} > void; @@ -432,9 +512,15 @@ function EditForm({ courses: Course[]; lecturers: Lecturer[]; academicTerms: AcademicTerm[]; + courseClassAssignments: CourseClassAssignment[]; }) { - const [courseId, setCourseId] = useState( - editing ? String(editing.course_id) : '', + const [academicTermId, setAcademicTermId] = useState( + editing ? String(editing.academic_term_id) : '', + ); + const [course, setCourse] = useState( + editing + ? (courses.find((c) => c.id === editing.course_id) ?? null) + : null, ); const [lecturer, setLecturer] = useState( editing @@ -442,12 +528,21 @@ function EditForm({ : null, ); - const selectedCourse = courses.find((c) => String(c.id) === courseId); - const availableLecturers = selectedCourse + const takenCourseIds = new Set( + courseClassAssignments + .filter( + (a) => + String(a.academic_term_id) === academicTermId && + a.id !== editing?.id, + ) + .map((a) => a.course_id), + ); + const availableCourses = courses.filter((c) => !takenCourseIds.has(c.id)); + const courseGroups = groupCoursesByDepartmentAndSemester(availableCourses); + const availableLecturers = course ? lecturers.filter((l) => l.departments.some( - (department) => - department.id === selectedCourse.department_id, + (department) => department.id === course.department_id, ), ) : lecturers; @@ -472,29 +567,53 @@ function EditForm({ - + + + + Mata kuliah tidak ditemukan. + + + {(group: CourseGroup) => ( + + + {group.value} + + + {(c: Course) => ( + + {c.code} -{' '} + {c.name} + + )} + + + )} + + +
@@ -515,12 +634,12 @@ function EditForm({ `${lect.user?.profile?.full_name ?? 'N/A'} - ${lect.lecturer_number}` } isItemEqualToValue={(a, b) => a.id === b.id} - disabled={!selectedCourse} + disabled={!course} > * - { + setAcademicTermId(value); + setCourse(null); + setLecturer(null); + }} >