From 86863ff2f8d00a12011a9ff0f9176be3d509047f Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Wed, 2 Sep 2026 13:38:40 +0700 Subject: [PATCH] feat: enhance course pagination and grouping by department and semester --- app/Services/Admin/Master/CourseService.php | 16 ++- resources/js/components/data-table.tsx | 136 +++++++++++------- .../js/pages/admin/master/courses/columns.tsx | 28 ---- .../js/pages/admin/master/courses/index.tsx | 3 + 4 files changed, 100 insertions(+), 83 deletions(-) diff --git a/app/Services/Admin/Master/CourseService.php b/app/Services/Admin/Master/CourseService.php index df7205e..6c8658e 100644 --- a/app/Services/Admin/Master/CourseService.php +++ b/app/Services/Admin/Master/CourseService.php @@ -29,14 +29,16 @@ public function paginated(int $perPage = 25, string $search = '', ?int $departme $user = auth()->user(); return Course::query() - ->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number']) + ->select(['courses.id', 'courses.code', 'courses.name', 'courses.credits', 'courses.department_id', 'courses.semester_number']) + ->join('departments', 'departments.id', '=', 'courses.department_id') ->with('department:id,name') - ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")) - ->when($departmentId, fn ($q) => $q->where('department_id', $departmentId)) - ->when($semesterNumber, fn ($q) => $q->where('semester_number', $semesterNumber)) - ->when($user->hasRole('mahasiswa'), fn ($q) => $q->where('department_id', $user->student?->department_id)) - ->when($user->hasRole('dosen'), fn ($q) => $q->whereIn('department_id', $user->lecturer?->departments()->pluck('departments.id') ?? [])) - ->latest() + ->when($search, fn ($q) => $q->where('courses.name', 'like', "%{$search}%")->orWhere('courses.code', 'like', "%{$search}%")) + ->when($departmentId, fn ($q) => $q->where('courses.department_id', $departmentId)) + ->when($semesterNumber, fn ($q) => $q->where('courses.semester_number', $semesterNumber)) + ->when($user->hasRole('mahasiswa'), fn ($q) => $q->where('courses.department_id', $user->student?->department_id)) + ->when($user->hasRole('dosen'), fn ($q) => $q->whereIn('courses.department_id', $user->lecturer?->departments()->pluck('departments.id') ?? [])) + ->orderBy('departments.name') + ->orderBy('courses.semester_number') ->paginate($perPage); } diff --git a/resources/js/components/data-table.tsx b/resources/js/components/data-table.tsx index fcea6e3..91bfb71 100644 --- a/resources/js/components/data-table.tsx +++ b/resources/js/components/data-table.tsx @@ -65,6 +65,7 @@ interface DataTableProps { toolbar?: React.ReactNode; renderSubRow?: (row: Row, searchValue?: string) => React.ReactNode; defaultExpanded?: boolean; + groupBy?: (item: TData) => string; pagination?: PaginationState; onPageChange?: (page: number) => void; onPerPageChange?: (perPage: number) => void; @@ -156,6 +157,7 @@ export function DataTable({ toolbar, renderSubRow, defaultExpanded = false, + groupBy, pagination, onPageChange, onPerPageChange, @@ -404,58 +406,96 @@ export function DataTable({ ) : ( - table.getRowModel().rows.map((row) => ( - - - {row - .getVisibleCells() - .map((cell) => ( - { + let previousGroup: string | null = + null; + + return table + .getRowModel() + .rows.map((row) => { + const group = groupBy + ? groupBy(row.original) + : null; + const showGroupHeader = + groupBy && + group !== previousGroup; + previousGroup = group; + + return ( + + {showGroupHeader && ( + + + {group} + + + )} + - {flexRender( - cell.column - .columnDef - .cell, - cell.getContext(), - )} - - ))} - - {renderSubRow && - row.getIsExpanded() && ( - - -
- {renderSubRow( - row, - localSearch, + {row + .getVisibleCells() + .map( + (cell) => ( + + {flexRender( + cell + .column + .columnDef + .cell, + cell.getContext(), + )} + + ), )} -
-
-
- )} -
- )) + + {renderSubRow && + row.getIsExpanded() && ( + + +
+ {renderSubRow( + row, + localSearch, + )} +
+
+
+ )} + + ); + }); + })() ) ) : ( diff --git a/resources/js/pages/admin/master/courses/columns.tsx b/resources/js/pages/admin/master/courses/columns.tsx index 6bcbfde..8cfc990 100644 --- a/resources/js/pages/admin/master/courses/columns.tsx +++ b/resources/js/pages/admin/master/courses/columns.tsx @@ -1,7 +1,6 @@ import type { ColumnDef } from '@tanstack/react-table'; import { Pencil, Trash2 } from 'lucide-react'; import { RowActions } from '@/components/row-actions'; -import { Badge } from '@/components/ui/badge'; import type { Course } from '@/types/course'; export type { Course } from '@/types/course'; @@ -33,11 +32,6 @@ export function createCourseColumns( header: () => Nama Mata Kuliah, cell: ({ row }) => {row.getValue('name') as string}, }, - { - accessorKey: 'department.name', - header: () => Jurusan, - cell: ({ row }) => row.original.department?.name ?? '-', - }, { accessorKey: 'credits', header: () => SKS, @@ -51,28 +45,6 @@ export function createCourseColumns( ), }, - { - accessorKey: 'semester_number', - header: () => Semester, - meta: { - className: 'w-[100px] text-center', - headerClassName: 'w-[100px] text-center', - }, - cell: ({ row }) => { - const semesterNumber = row.getValue('semester_number') as - number | null; - - return ( -
- {semesterNumber ? ( - {semesterNumber} - ) : ( - - - )} -
- ); - }, - }, ]; if (canUpdate || canDelete) { diff --git a/resources/js/pages/admin/master/courses/index.tsx b/resources/js/pages/admin/master/courses/index.tsx index df764a9..1417e6a 100644 --- a/resources/js/pages/admin/master/courses/index.tsx +++ b/resources/js/pages/admin/master/courses/index.tsx @@ -191,6 +191,9 @@ export default function CourseIndex({ onPerPageChange={handlePerPageChange} onSearchChange={handleSearchChange} searchValue={search} + groupBy={(course) => + `${course.department?.name ?? 'Tanpa Jurusan'} - Semester ${course.semester_number ?? 'Tidak ditentukan'}` + } toolbar={