From 53afdb1fec3b0bd064bf5d60a8580b815d8a292e Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 2 Sep 2026 19:00:39 +0700 Subject: [PATCH] feat: enhance student and lecturer data retrieval with improved joins and sorting --- .../Manage/CourseRegistrationService.php | 20 +++-- app/Services/Admin/Users/LecturerService.php | 5 +- app/Services/Admin/Users/StudentService.php | 10 ++- .../admin/finances/tuition-invoices/index.tsx | 85 ++++++++++++++++--- .../manage/course-registrations/columns.tsx | 5 -- .../manage/course-registrations/index.tsx | 3 + resources/js/types/tuition-invoice.ts | 1 + 7 files changed, 99 insertions(+), 30 deletions(-) diff --git a/app/Services/Admin/Manage/CourseRegistrationService.php b/app/Services/Admin/Manage/CourseRegistrationService.php index 77b3cac..5d55813 100644 --- a/app/Services/Admin/Manage/CourseRegistrationService.php +++ b/app/Services/Admin/Manage/CourseRegistrationService.php @@ -41,15 +41,19 @@ public function departmentSummary(): Collection public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $advisorLecturerId = null, ?array $ledDepartmentIds = null): LengthAwarePaginator { $paginator = Student::query() - ->select(['id', 'user_id', 'student_number', 'department_id']) - ->where('status', StudentStatus::Active) - ->when($ledDepartmentIds !== null, fn ($q) => $q->whereIn('department_id', $ledDepartmentIds)) - ->when($departmentId, fn ($q) => $q->where('department_id', $departmentId)) - ->when($advisorLecturerId, fn ($q) => $q->where('academic_advisor_id', $advisorLecturerId)) - ->when($search, fn ($q) => $q->where('student_number', 'like', "%{$search}%") - ->orWhereHas('user.profile', fn ($q) => $q->where('full_name', 'like', "%{$search}%"))) + ->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id']) + ->join('departments', 'departments.id', '=', 'students.department_id') + ->join('users', 'users.id', '=', 'students.user_id') + ->join('user_profiles', 'user_profiles.user_id', '=', 'users.id') + ->where('students.status', StudentStatus::Active) + ->when($ledDepartmentIds !== null, fn ($q) => $q->whereIn('students.department_id', $ledDepartmentIds)) + ->when($departmentId, fn ($q) => $q->where('students.department_id', $departmentId)) + ->when($advisorLecturerId, fn ($q) => $q->where('students.academic_advisor_id', $advisorLecturerId)) + ->when($search, fn ($q) => $q->where('students.student_number', 'like', "%{$search}%") + ->orWhere('user_profiles.full_name', 'like', "%{$search}%")) ->with(['user.profile', 'department:id,name']) - ->orderBy('student_number') + ->orderBy('departments.name') + ->orderBy('user_profiles.full_name') ->paginate($perPage); return $paginator->through(fn (Student $student) => [ diff --git a/app/Services/Admin/Users/LecturerService.php b/app/Services/Admin/Users/LecturerService.php index 5c0eed2..a336688 100644 --- a/app/Services/Admin/Users/LecturerService.php +++ b/app/Services/Admin/Users/LecturerService.php @@ -14,12 +14,15 @@ class LecturerService { public function getAllForSelect(): Collection { - return Lecturer::select(['id', 'user_id', 'lecturer_number']) + return Lecturer::select(['lecturers.id', 'lecturers.user_id', 'lecturers.lecturer_number']) + ->join('users', 'users.id', '=', 'lecturers.user_id') + ->join('user_profiles', 'user_profiles.user_id', '=', 'users.id') ->with([ 'user:id,username', 'user.profile:id,user_id,full_name', 'departments:id,name', ]) + ->orderBy('user_profiles.full_name') ->get(); } diff --git a/app/Services/Admin/Users/StudentService.php b/app/Services/Admin/Users/StudentService.php index 54fbc5a..ec35cfc 100644 --- a/app/Services/Admin/Users/StudentService.php +++ b/app/Services/Admin/Users/StudentService.php @@ -14,13 +14,19 @@ class StudentService { public function getAllForSelect(?string $status = null): Collection { - return Student::select(['id', 'user_id', 'student_number', 'department_id', 'current_semester']) + return Student::select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id', 'students.current_semester']) + ->join('departments', 'departments.id', '=', 'students.department_id') + ->join('users', 'users.id', '=', 'students.user_id') + ->join('user_profiles', 'user_profiles.user_id', '=', 'users.id') ->with([ 'user:id,username', 'user.profile:id,user_id,full_name', 'department:id,name', ]) - ->when($status, fn ($q) => $q->where('status', $status)) + ->when($status, fn ($q) => $q->where('students.status', $status)) + ->orderBy('departments.name') + ->orderBy('students.current_semester') + ->orderBy('user_profiles.full_name') ->get(); } diff --git a/resources/js/pages/admin/finances/tuition-invoices/index.tsx b/resources/js/pages/admin/finances/tuition-invoices/index.tsx index 1832b18..14eba48 100644 --- a/resources/js/pages/admin/finances/tuition-invoices/index.tsx +++ b/resources/js/pages/admin/finances/tuition-invoices/index.tsx @@ -32,10 +32,13 @@ import { ComboboxChip, ComboboxChips, ComboboxChipsInput, + ComboboxCollection, ComboboxContent, ComboboxEmpty, + ComboboxGroup, ComboboxInput, ComboboxItem, + ComboboxLabel, ComboboxList, useComboboxAnchor, } from '@/components/ui/combobox'; @@ -108,6 +111,28 @@ function studentLabel(student: TuitionInvoiceStudent): string { return `${student.user?.profile?.full_name ?? 'N/A'} - ${student.student_number}`; } +type StudentGroup = { value: string; items: TuitionInvoiceStudent[] }; + +function groupStudentsByDepartmentAndSemester( + students: TuitionInvoiceStudent[], +): StudentGroup[] { + const groups: StudentGroup[] = []; + let currentKey: string | null = null; + + for (const student of students) { + const key = `${student.department?.name ?? 'Tanpa Jurusan'} — Semester ${student.current_semester ?? 'Tidak ditentukan'}`; + + if (key !== currentKey) { + currentKey = key; + groups.push({ value: key, items: [] }); + } + + groups[groups.length - 1].items.push(student); + } + + return groups; +} + export default function TuitionInvoiceIndex({ invoices, summary, @@ -379,6 +404,7 @@ function CreateForm({ !student.invoiced_term_ids?.includes(Number(academicTermId)), ) : []; + const studentGroups = groupStudentsByDepartmentAndSemester(availableStudents); function reset() { setAcademicTermId(''); @@ -468,7 +494,7 @@ function CreateForm({ /> ))} - {(student: TuitionInvoiceStudent) => ( - ( + - {studentLabel(student)} - + + {group.value} + + + {( + student: TuitionInvoiceStudent, + ) => ( + + {studentLabel(student)} + + )} + + )} @@ -569,6 +609,7 @@ function EditForm({ const [student, setStudent] = useState( students.find((s) => s.id === editing?.student_id) ?? null, ); + const studentGroups = groupStudentsByDepartmentAndSemester(students); return ( - {(option: TuitionInvoiceStudent) => ( - ( + - {studentLabel(option)} - + + {group.value} + + + {( + option: TuitionInvoiceStudent, + ) => ( + + {studentLabel( + option, + )} + + )} + + )} diff --git a/resources/js/pages/admin/manage/course-registrations/columns.tsx b/resources/js/pages/admin/manage/course-registrations/columns.tsx index 49f6edf..b582bc4 100644 --- a/resources/js/pages/admin/manage/course-registrations/columns.tsx +++ b/resources/js/pages/admin/manage/course-registrations/columns.tsx @@ -26,11 +26,6 @@ export function createCourseRegistrationColumns(): ColumnDef Jurusan, - cell: ({ row }) => row.original.student?.department?.name ?? '-', - }, { id: 'actions', header: () => Aksi, diff --git a/resources/js/pages/admin/manage/course-registrations/index.tsx b/resources/js/pages/admin/manage/course-registrations/index.tsx index 8eaff7e..c779ced 100644 --- a/resources/js/pages/admin/manage/course-registrations/index.tsx +++ b/resources/js/pages/admin/manage/course-registrations/index.tsx @@ -79,6 +79,9 @@ export default function CourseRegistrationIndex({ onSearchChange={handleSearchChange} searchValue={search} searchKey="student" + groupBy={(row) => + row.student?.department?.name ?? 'Tanpa Jurusan' + } toolbar={