feat: add Course management functionality with CRUD operations and UI integration
Some checks failed
tests / ci (pull_request) Has been cancelled

This commit is contained in:
Yoga Pangestu 2026-08-30 12:46:43 +07:00
parent 1fe2b30156
commit a6d1fa2cf7
9 changed files with 47 additions and 25 deletions

View File

@ -7,8 +7,8 @@
use App\Http\Requests\PaginatedRequest;
use App\Models\CourseClass;
use App\Services\Admin\Manage\CourseClassService;
use App\Services\Admin\Manage\CourseService;
use App\Services\Admin\Master\AcademicTermService;
use App\Services\Admin\Master\CourseService;
use App\Services\Admin\Users\LecturerService;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;

View File

@ -1,12 +1,12 @@
<?php
namespace App\Http\Controllers\Admin\Manage;
namespace App\Http\Controllers\Admin\Master;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Manage\CourseRequest;
use App\Http\Requests\Admin\Master\CourseRequest;
use App\Http\Requests\PaginatedRequest;
use App\Models\Course;
use App\Services\Admin\Manage\CourseService;
use App\Services\Admin\Master\CourseService;
use App\Services\Admin\Master\DepartmentService;
use Illuminate\Http\RedirectResponse;
use Inertia\Inertia;
@ -21,13 +21,15 @@ public function __construct(
public function index(PaginatedRequest $request): Response
{
return Inertia::render('admin/manage/courses/index', [
return Inertia::render('admin/master/courses/index', [
'courses' => $this->service->paginated(
...$request->validatedWithDefaults(),
departmentId: $request->validated('department_id'),
semesterNumber: $request->validated('semester_number'),
),
'departments' => $this->departmentService->getAllForSelect(),
'filters' => $request->only(['department_id']),
'semesterNumbers' => $this->service->getSemesterNumbers(),
'filters' => $request->only(['department_id', 'semester_number']),
]);
}
@ -37,7 +39,7 @@ public function store(CourseRequest $request): RedirectResponse
Inertia::flash('toast', ['type' => 'success', 'message' => 'Mata kuliah berhasil ditambahkan.']);
return to_route('admin.manage.courses.index');
return to_route('admin.master.courses.index');
}
public function update(CourseRequest $request, Course $course): RedirectResponse
@ -46,7 +48,7 @@ public function update(CourseRequest $request, Course $course): RedirectResponse
Inertia::flash('toast', ['type' => 'success', 'message' => 'Mata kuliah berhasil diperbarui.']);
return to_route('admin.manage.courses.index');
return to_route('admin.master.courses.index');
}
public function destroy(Course $course): RedirectResponse

View File

@ -1,6 +1,6 @@
<?php
namespace App\Http\Requests\Admin\Manage;
namespace App\Http\Requests\Admin\Master;
use Illuminate\Foundation\Http\FormRequest;
use Illuminate\Validation\Rule;

View File

@ -25,6 +25,7 @@ public function rules(): array
'department_id' => ['nullable', 'integer'],
'status' => ['nullable', 'string'],
'enrollment_year' => ['nullable', 'integer'],
'semester_number' => ['nullable', 'integer'],
'semester' => ['nullable', 'string', Rule::in(Semester::values())],
'is_active' => ['nullable', Rule::in(['true', 'false'])],
'academic_term_id' => ['nullable', 'integer'],

View File

@ -1,6 +1,6 @@
<?php
namespace App\Services\Admin\Manage;
namespace App\Services\Admin\Master;
use App\Models\Course;
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@ -13,13 +13,25 @@ public function getAllForSelect(): Collection
return Course::select(['id', 'code', 'name', 'department_id'])->get();
}
public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null): LengthAwarePaginator
public function getSemesterNumbers(): array
{
return Course::query()
->select('semester_number')
->whereNotNull('semester_number')
->distinct()
->orderBy('semester_number')
->pluck('semester_number')
->all();
}
public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $semesterNumber = null): LengthAwarePaginator
{
return Course::query()
->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number'])
->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))
->latest()
->paginate($perPage);
}

View File

@ -1,10 +1,6 @@
import { usePage } from '@inertiajs/react';
import type { Icon } from '@tabler/icons-react';
import {
IconGridDots,
IconHelp,
IconMessageDots
} from '@tabler/icons-react';
import { IconGridDots, IconHelp, IconMessageDots } from '@tabler/icons-react';
import {
BookOpen,
Building2,
@ -49,8 +45,8 @@ import { index as announcementsRoute } from '@/routes/admin/announcements';
import { index as feedbackRoute } from '@/routes/admin/feedback';
import { index as tuitionInvoicesRoute } from '@/routes/admin/finances/tuition-invoices';
import { index as courseClassesRoute } from '@/routes/admin/manage/course-classes';
import { index as coursesRoute } from '@/routes/admin/manage/courses';
import { index as academicTerm } from '@/routes/admin/master/academic-terms';
import { index as coursesRoute } from '@/routes/admin/master/courses';
import { index as departmentsRoute } from '@/routes/admin/master/departments';
import { index as academicAdvisingLogsRoute } from '@/routes/admin/services/academic-advising-logs';
import { index as letterRequestsRoute } from '@/routes/admin/services/letter-requests';
@ -104,6 +100,11 @@ const data: {
url: departmentsRoute.url(),
icon: Building2,
},
{
name: 'Mata Kuliah',
url: coursesRoute.url(),
icon: BookOpen,
},
],
},
{
@ -139,11 +140,6 @@ const data: {
{
label: 'Kelola',
items: [
{
name: 'Mata Kuliah',
url: coursesRoute.url(),
icon: BookOpen,
},
{
name: 'Kelas Mata Kuliah',
url: courseClassesRoute.url(),

View File

@ -25,7 +25,7 @@ import {
destroy,
store,
update,
} from '@/routes/admin/manage/courses';
} from '@/routes/admin/master/courses';
import type { Course } from '@/types/course';
import { formatDepartmentLabel } from '@/types/department';
import { createCourseColumns } from './columns';
@ -41,15 +41,18 @@ type Props = {
total: number;
};
departments: Department[];
semesterNumbers: number[];
highlight?: number;
filters: {
department_id?: string;
semester_number?: string;
};
};
export default function CourseIndex({
courses,
departments,
semesterNumbers,
highlight,
filters,
}: Props) {
@ -66,6 +69,14 @@ export default function CourseIndex({
label: formatDepartmentLabel(department),
})),
},
{
key: 'semester_number',
label: 'Semester',
options: semesterNumbers.map((semester) => ({
value: String(semester),
label: String(semester),
})),
},
];
const pagination: PaginationState = {

View File

@ -12,8 +12,8 @@
use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
use App\Http\Controllers\Admin\Manage\ClassEnrollmentController;
use App\Http\Controllers\Admin\Manage\CourseClassController;
use App\Http\Controllers\Admin\Manage\CourseController;
use App\Http\Controllers\Admin\Master\AcademicTermController;
use App\Http\Controllers\Admin\Master\CourseController;
use App\Http\Controllers\Admin\Master\DepartmentController;
use App\Http\Controllers\Admin\Services\AcademicAdvisingLogController;
use App\Http\Controllers\Admin\Services\LetterRequestController;
@ -29,6 +29,7 @@
Route::resource('academic-terms', AcademicTermController::class)->except(['create', 'edit', 'show']);
Route::patch('academic-terms/{academic_term}/status', [AcademicTermController::class, 'updateStatus'])->name('academic-terms.update_status');
Route::resource('departments', DepartmentController::class)->except(['create', 'edit', 'show']);
Route::resource('courses', CourseController::class)->except(['create', 'edit', 'show']);
});
Route::prefix('admin/academic-classes')->name('admin.academic-classes.')->group(function () {
@ -62,7 +63,6 @@
});
Route::prefix('admin/manage')->name('admin.manage.')->group(function () {
Route::resource('courses', CourseController::class)->except(['create', 'edit', 'show']);
Route::resource('course-classes', CourseClassController::class)->except(['create', 'edit', 'show']);
Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () {