From a6d1fa2cf72145f41ea0d4519cc7294c0a37e4a9 Mon Sep 17 00:00:00 2001 From: Yoga Pangestu Date: Sun, 30 Aug 2026 12:46:43 +0700 Subject: [PATCH] feat: add Course management functionality with CRUD operations and UI integration --- .../Admin/Manage/CourseClassController.php | 2 +- .../{Manage => Master}/CourseController.php | 16 +++++++++------- .../Admin/{Manage => Master}/CourseRequest.php | 2 +- app/Http/Requests/PaginatedRequest.php | 1 + .../Admin/{Manage => Master}/CourseService.php | 16 ++++++++++++++-- resources/js/components/app-sidebar.tsx | 18 +++++++----------- .../{manage => master}/courses/columns.tsx | 0 .../admin/{manage => master}/courses/index.tsx | 13 ++++++++++++- routes/admin.php | 4 ++-- 9 files changed, 47 insertions(+), 25 deletions(-) rename app/Http/Controllers/Admin/{Manage => Master}/CourseController.php (74%) rename app/Http/Requests/Admin/{Manage => Master}/CourseRequest.php (94%) rename app/Services/Admin/{Manage => Master}/CourseService.php (72%) rename resources/js/pages/admin/{manage => master}/courses/columns.tsx (100%) rename resources/js/pages/admin/{manage => master}/courses/index.tsx (97%) diff --git a/app/Http/Controllers/Admin/Manage/CourseClassController.php b/app/Http/Controllers/Admin/Manage/CourseClassController.php index 068d46f..5265235 100644 --- a/app/Http/Controllers/Admin/Manage/CourseClassController.php +++ b/app/Http/Controllers/Admin/Manage/CourseClassController.php @@ -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; diff --git a/app/Http/Controllers/Admin/Manage/CourseController.php b/app/Http/Controllers/Admin/Master/CourseController.php similarity index 74% rename from app/Http/Controllers/Admin/Manage/CourseController.php rename to app/Http/Controllers/Admin/Master/CourseController.php index c3246c7..7d577fd 100644 --- a/app/Http/Controllers/Admin/Manage/CourseController.php +++ b/app/Http/Controllers/Admin/Master/CourseController.php @@ -1,12 +1,12 @@ $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 diff --git a/app/Http/Requests/Admin/Manage/CourseRequest.php b/app/Http/Requests/Admin/Master/CourseRequest.php similarity index 94% rename from app/Http/Requests/Admin/Manage/CourseRequest.php rename to app/Http/Requests/Admin/Master/CourseRequest.php index 40bbe84..c90c6fa 100644 --- a/app/Http/Requests/Admin/Manage/CourseRequest.php +++ b/app/Http/Requests/Admin/Master/CourseRequest.php @@ -1,6 +1,6 @@ ['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'], diff --git a/app/Services/Admin/Manage/CourseService.php b/app/Services/Admin/Master/CourseService.php similarity index 72% rename from app/Services/Admin/Manage/CourseService.php rename to app/Services/Admin/Master/CourseService.php index c0dd828..5abea5d 100644 --- a/app/Services/Admin/Manage/CourseService.php +++ b/app/Services/Admin/Master/CourseService.php @@ -1,6 +1,6 @@ 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); } diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx index 3dd46d2..229d5a4 100644 --- a/resources/js/components/app-sidebar.tsx +++ b/resources/js/components/app-sidebar.tsx @@ -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(), diff --git a/resources/js/pages/admin/manage/courses/columns.tsx b/resources/js/pages/admin/master/courses/columns.tsx similarity index 100% rename from resources/js/pages/admin/manage/courses/columns.tsx rename to resources/js/pages/admin/master/courses/columns.tsx diff --git a/resources/js/pages/admin/manage/courses/index.tsx b/resources/js/pages/admin/master/courses/index.tsx similarity index 97% rename from resources/js/pages/admin/manage/courses/index.tsx rename to resources/js/pages/admin/master/courses/index.tsx index 05a5dae..132cb59 100644 --- a/resources/js/pages/admin/manage/courses/index.tsx +++ b/resources/js/pages/admin/master/courses/index.tsx @@ -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 = { diff --git a/routes/admin.php b/routes/admin.php index d3600bc..bb06339 100644 --- a/routes/admin.php +++ b/routes/admin.php @@ -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 () {