Merge pull request 'feat: add Course management functionality with CRUD operations and UI integration' (#59) from refactor/move-course-form-manage-to-master into dev

Reviewed-on: #59
This commit is contained in:
pangestu 2026-08-30 13:47:11 +08:00
commit 6c28782484
9 changed files with 47 additions and 25 deletions

View File

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

View File

@ -1,12 +1,12 @@
<?php <?php
namespace App\Http\Controllers\Admin\Manage; namespace App\Http\Controllers\Admin\Master;
use App\Http\Controllers\Controller; 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\Http\Requests\PaginatedRequest;
use App\Models\Course; use App\Models\Course;
use App\Services\Admin\Manage\CourseService; use App\Services\Admin\Master\CourseService;
use App\Services\Admin\Master\DepartmentService; use App\Services\Admin\Master\DepartmentService;
use Illuminate\Http\RedirectResponse; use Illuminate\Http\RedirectResponse;
use Inertia\Inertia; use Inertia\Inertia;
@ -21,13 +21,15 @@ public function __construct(
public function index(PaginatedRequest $request): Response public function index(PaginatedRequest $request): Response
{ {
return Inertia::render('admin/manage/courses/index', [ return Inertia::render('admin/master/courses/index', [
'courses' => $this->service->paginated( 'courses' => $this->service->paginated(
...$request->validatedWithDefaults(), ...$request->validatedWithDefaults(),
departmentId: $request->validated('department_id'), departmentId: $request->validated('department_id'),
semesterNumber: $request->validated('semester_number'),
), ),
'departments' => $this->departmentService->getAllForSelect(), '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.']); 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 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.']); 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 public function destroy(Course $course): RedirectResponse

View File

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

View File

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

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Services\Admin\Manage; namespace App\Services\Admin\Master;
use App\Models\Course; use App\Models\Course;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
@ -13,13 +13,25 @@ public function getAllForSelect(): Collection
return Course::select(['id', 'code', 'name', 'department_id'])->get(); 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() return Course::query()
->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number']) ->select(['id', 'code', 'name', 'credits', 'department_id', 'semester_number'])
->with('department:id,name') ->with('department:id,name')
->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%")) ->when($search, fn ($q) => $q->where('name', 'like', "%{$search}%")->orWhere('code', 'like', "%{$search}%"))
->when($departmentId, fn ($q) => $q->where('department_id', $departmentId)) ->when($departmentId, fn ($q) => $q->where('department_id', $departmentId))
->when($semesterNumber, fn ($q) => $q->where('semester_number', $semesterNumber))
->latest() ->latest()
->paginate($perPage); ->paginate($perPage);
} }

View File

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

View File

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

View File

@ -12,8 +12,8 @@
use App\Http\Controllers\Admin\Finances\TuitionPaymentController; use App\Http\Controllers\Admin\Finances\TuitionPaymentController;
use App\Http\Controllers\Admin\Manage\ClassEnrollmentController; use App\Http\Controllers\Admin\Manage\ClassEnrollmentController;
use App\Http\Controllers\Admin\Manage\CourseClassController; 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\AcademicTermController;
use App\Http\Controllers\Admin\Master\CourseController;
use App\Http\Controllers\Admin\Master\DepartmentController; use App\Http\Controllers\Admin\Master\DepartmentController;
use App\Http\Controllers\Admin\Services\AcademicAdvisingLogController; use App\Http\Controllers\Admin\Services\AcademicAdvisingLogController;
use App\Http\Controllers\Admin\Services\LetterRequestController; use App\Http\Controllers\Admin\Services\LetterRequestController;
@ -29,6 +29,7 @@
Route::resource('academic-terms', AcademicTermController::class)->except(['create', 'edit', 'show']); 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::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('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 () { 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::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::resource('course-classes', CourseClassController::class)->except(['create', 'edit', 'show']);
Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () { Route::prefix('course-classes/{course_class}/enrollments')->name('course-classes.enrollments.')->group(function () {