feat: enhance course pagination and grouping by department and semester
This commit is contained in:
parent
9823b879d8
commit
86863ff2f8
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -65,6 +65,7 @@ interface DataTableProps<TData, TValue> {
|
||||
toolbar?: React.ReactNode;
|
||||
renderSubRow?: (row: Row<TData>, 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<TData, TValue>({
|
||||
toolbar,
|
||||
renderSubRow,
|
||||
defaultExpanded = false,
|
||||
groupBy,
|
||||
pagination,
|
||||
onPageChange,
|
||||
onPerPageChange,
|
||||
@ -404,58 +406,96 @@ export function DataTable<TData, TValue>({
|
||||
</SortableContext>
|
||||
</DndContext>
|
||||
) : (
|
||||
table.getRowModel().rows.map((row) => (
|
||||
<React.Fragment key={row.id}>
|
||||
<TableRow
|
||||
data-state={
|
||||
row.getIsSelected() &&
|
||||
'selected'
|
||||
}
|
||||
>
|
||||
{row
|
||||
.getVisibleCells()
|
||||
.map((cell) => (
|
||||
<TableCell
|
||||
key={cell.id}
|
||||
className={
|
||||
(
|
||||
cell.column
|
||||
.columnDef
|
||||
.meta as {
|
||||
className?: string;
|
||||
(() => {
|
||||
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 (
|
||||
<React.Fragment
|
||||
key={row.id}
|
||||
>
|
||||
{showGroupHeader && (
|
||||
<TableRow className="bg-muted/50 hover:bg-muted/50">
|
||||
<TableCell
|
||||
colSpan={
|
||||
visibleColumns.length
|
||||
}
|
||||
)?.className
|
||||
className="py-2 text-sm font-semibold"
|
||||
>
|
||||
{group}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
<TableRow
|
||||
data-state={
|
||||
row.getIsSelected() &&
|
||||
'selected'
|
||||
}
|
||||
>
|
||||
{flexRender(
|
||||
cell.column
|
||||
.columnDef
|
||||
.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
{renderSubRow &&
|
||||
row.getIsExpanded() && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={
|
||||
visibleColumns.length
|
||||
}
|
||||
className="bg-muted/50 p-0"
|
||||
>
|
||||
<div className="p-4">
|
||||
{renderSubRow(
|
||||
row,
|
||||
localSearch,
|
||||
{row
|
||||
.getVisibleCells()
|
||||
.map(
|
||||
(cell) => (
|
||||
<TableCell
|
||||
key={
|
||||
cell.id
|
||||
}
|
||||
className={
|
||||
(
|
||||
cell
|
||||
.column
|
||||
.columnDef
|
||||
.meta as {
|
||||
className?: string;
|
||||
}
|
||||
)
|
||||
?.className
|
||||
}
|
||||
>
|
||||
{flexRender(
|
||||
cell
|
||||
.column
|
||||
.columnDef
|
||||
.cell,
|
||||
cell.getContext(),
|
||||
)}
|
||||
</TableCell>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
))
|
||||
</TableRow>
|
||||
{renderSubRow &&
|
||||
row.getIsExpanded() && (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={
|
||||
visibleColumns.length
|
||||
}
|
||||
className="bg-muted/50 p-0"
|
||||
>
|
||||
<div className="p-4">
|
||||
{renderSubRow(
|
||||
row,
|
||||
localSearch,
|
||||
)}
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</React.Fragment>
|
||||
);
|
||||
});
|
||||
})()
|
||||
)
|
||||
) : (
|
||||
<TableRow>
|
||||
|
||||
@ -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: () => <span>Nama Mata Kuliah</span>,
|
||||
cell: ({ row }) => <span>{row.getValue('name') as string}</span>,
|
||||
},
|
||||
{
|
||||
accessorKey: 'department.name',
|
||||
header: () => <span>Jurusan</span>,
|
||||
cell: ({ row }) => row.original.department?.name ?? '-',
|
||||
},
|
||||
{
|
||||
accessorKey: 'credits',
|
||||
header: () => <span className="block text-center">SKS</span>,
|
||||
@ -51,28 +45,6 @@ export function createCourseColumns(
|
||||
</div>
|
||||
),
|
||||
},
|
||||
{
|
||||
accessorKey: 'semester_number',
|
||||
header: () => <span className="block text-center">Semester</span>,
|
||||
meta: {
|
||||
className: 'w-[100px] text-center',
|
||||
headerClassName: 'w-[100px] text-center',
|
||||
},
|
||||
cell: ({ row }) => {
|
||||
const semesterNumber = row.getValue('semester_number') as
|
||||
number | null;
|
||||
|
||||
return (
|
||||
<div className="flex justify-center">
|
||||
{semesterNumber ? (
|
||||
<Badge variant="secondary">{semesterNumber}</Badge>
|
||||
) : (
|
||||
<span className="text-muted-foreground">-</span>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
if (canUpdate || canDelete) {
|
||||
|
||||
@ -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={
|
||||
<FilterDialog
|
||||
fields={filterFields}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user