feat: refactor CourseRegistration management; update controller methods, add new request classes, and adjust routes for department leader role

This commit is contained in:
Yoga Pangestu 2026-09-04 08:29:03 +07:00
parent 80435b6cfa
commit 88319dac15
11 changed files with 115 additions and 95 deletions

View File

@ -2,10 +2,12 @@
namespace App\Http\Controllers\Admin\Manage; namespace App\Http\Controllers\Admin\Manage;
use App\Enums\UserRole;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\Manage\RejectCourseRegistrationRequest; use App\Http\Requests\Admin\Manage\CourseRegistration\RejectCourseRegistrationRequest;
use App\Http\Requests\Admin\Manage\SaveCourseRegistrationRequest; use App\Http\Requests\Admin\Manage\CourseRegistration\SignAsAdvisorCourseRegistrationRequest;
use App\Http\Requests\Admin\Manage\SignCourseRegistrationRequest; use App\Http\Requests\Admin\Manage\CourseRegistration\SignAsDepartmentLeaderCourseRegistrationRequest;
use App\Http\Requests\Admin\Manage\CourseRegistration\UpdateCourseRegistrationRequest;
use App\Http\Requests\PaginatedRequest; use App\Http\Requests\PaginatedRequest;
use App\Models\Course; use App\Models\Course;
use App\Models\CourseRegistrationSubmission; use App\Models\CourseRegistrationSubmission;
@ -25,21 +27,18 @@ public function __construct(
public function index(PaginatedRequest $request): Response|RedirectResponse public function index(PaginatedRequest $request): Response|RedirectResponse
{ {
$user = auth()->user(); $user = $request->user();
if ($user->hasRole('mahasiswa')) { if ($user->hasRole(UserRole::Mahasiswa->value)) {
return to_route('admin.manage.course-registrations.show', $user->student->id); return to_route('admin.manage.course-registrations.show', $user->student->id);
} }
$isKaprodi = $user->hasRole('kaprodi');
return Inertia::render('admin/manage/course-registrations/index', [ return Inertia::render('admin/manage/course-registrations/index', [
'departments' => $this->service->departmentSummary(), 'departments' => $this->service->departmentSummary(),
'registrations' => $this->service->paginated( 'registrations' => $this->service->paginated(
$user,
...$request->validatedWithDefaults(), ...$request->validatedWithDefaults(),
departmentId: $request->validated('department_id'), departmentId: $request->validated('department_id'),
advisorLecturerId: ! $isKaprodi && $user->hasRole('dosen') ? $user->lecturer?->id : null,
ledDepartmentIds: $isKaprodi ? $user->ledDepartmentIds() : null,
), ),
'filters' => $request->only(['department_id']), 'filters' => $request->only(['department_id']),
]); ]);
@ -47,8 +46,8 @@ public function index(PaginatedRequest $request): Response|RedirectResponse
public function show(Student $student): Response public function show(Student $student): Response
{ {
$user = auth()->user(); $user = request()->user();
$isStudent = $user->hasRole('mahasiswa'); $isStudent = $user->hasRole(UserRole::Mahasiswa->value);
abort_if($isStudent && $user->student->id !== $student->id, 403); abort_if($isStudent && $user->student->id !== $student->id, 403);
abort_if(! $isStudent && ! $user->canAccessCourseRegistrationOf($student), 403); abort_if(! $isStudent && ! $user->canAccessCourseRegistrationOf($student), 403);
@ -61,7 +60,7 @@ public function show(Student $student): Response
&& $user->can('approve-course-registrations') && $user->can('approve-course-registrations')
&& $user->canReviewCourseRegistrationOf($student); && $user->canReviewCourseRegistrationOf($student);
$canSignAsKaprodi = $user->isKaprodiOf($student); $canSignAsDepartmentLeader = $user->isDepartmentLeaderOf($student);
$canSignAsAdvisor = $user->isAdvisorOf($student); $canSignAsAdvisor = $user->isAdvisorOf($student);
$activeTerm = $this->academicTermService->getActive(); $activeTerm = $this->academicTermService->getActive();
@ -97,12 +96,12 @@ public function show(Student $student): Response
'hasActiveTerm' => $activeTerm !== null, 'hasActiveTerm' => $activeTerm !== null,
'backHref' => $backHref, 'backHref' => $backHref,
'canReview' => $canReview, 'canReview' => $canReview,
'canSignAsKaprodi' => $canSignAsKaprodi, 'canSignAsDepartmentLeader' => $canSignAsDepartmentLeader,
'canSignAsAdvisor' => $canSignAsAdvisor, 'canSignAsAdvisor' => $canSignAsAdvisor,
]); ]);
} }
public function save(Student $student, string $semester, SaveCourseRegistrationRequest $request): RedirectResponse public function update(Student $student, string $semester, UpdateCourseRegistrationRequest $request): RedirectResponse
{ {
$semesterNumber = CourseRegistrationSubmission::parseSemesterKey($semester); $semesterNumber = CourseRegistrationSubmission::parseSemesterKey($semester);
@ -138,23 +137,19 @@ public function save(Student $student, string $semester, SaveCourseRegistrationR
return to_route('admin.manage.course-registrations.show', $student->id); return to_route('admin.manage.course-registrations.show', $student->id);
} }
public function signAsKaprodi(Student $student, string $semester, SignCourseRegistrationRequest $request): RedirectResponse public function signAsDepartmentLeader(Student $student, string $semester, SignAsDepartmentLeaderCourseRegistrationRequest $request): RedirectResponse
{ {
abort_unless(auth()->user()->isKaprodiOf($student), 403);
$submission = $this->service->findOrCreateForSemester($student, $semester); $submission = $this->service->findOrCreateForSemester($student, $semester);
$this->service->signAsKaprodi($submission, $request->file('signature')); $this->service->signAsDepartmentLeader($submission, $request->file('signature'));
Inertia::flash('toast', ['type' => 'success', 'message' => 'Tanda tangan berhasil disimpan.']); Inertia::flash('toast', ['type' => 'success', 'message' => 'Tanda tangan berhasil disimpan.']);
return to_route('admin.manage.course-registrations.show', $student->id); return to_route('admin.manage.course-registrations.show', $student->id);
} }
public function signAsAdvisor(Student $student, string $semester, SignCourseRegistrationRequest $request): RedirectResponse public function signAsAdvisor(Student $student, string $semester, SignAsAdvisorCourseRegistrationRequest $request): RedirectResponse
{ {
abort_unless(auth()->user()->isAdvisorOf($student), 403);
$submission = $this->service->findOrCreateForSemester($student, $semester); $submission = $this->service->findOrCreateForSemester($student, $semester);
$this->service->signAsAdvisor($submission, $request->file('signature')); $this->service->signAsAdvisor($submission, $request->file('signature'));
@ -166,7 +161,11 @@ public function signAsAdvisor(Student $student, string $semester, SignCourseRegi
public function approve(CourseRegistrationSubmission $submission): RedirectResponse public function approve(CourseRegistrationSubmission $submission): RedirectResponse
{ {
abort_unless(auth()->user()->canReviewCourseRegistrationOf($submission->student), 403); abort_unless(
request()->user()->can('approve-course-registrations')
&& request()->user()->canReviewCourseRegistrationOf($submission->student),
403
);
$this->service->approve($submission); $this->service->approve($submission);
@ -175,8 +174,6 @@ public function approve(CourseRegistrationSubmission $submission): RedirectRespo
public function reject(RejectCourseRegistrationRequest $request, CourseRegistrationSubmission $submission): RedirectResponse public function reject(RejectCourseRegistrationRequest $request, CourseRegistrationSubmission $submission): RedirectResponse
{ {
abort_unless(auth()->user()->canReviewCourseRegistrationOf($submission->student), 403);
$this->service->reject($submission, $request->validated('reason')); $this->service->reject($submission, $request->validated('reason'));
return Inertia::flash('toast', ['type' => 'success', 'message' => 'KRS berhasil ditolak.'])->back(); return Inertia::flash('toast', ['type' => 'success', 'message' => 'KRS berhasil ditolak.'])->back();

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Http\Requests\Admin\Manage; namespace App\Http\Requests\Admin\Manage\CourseRegistration;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
@ -8,7 +8,8 @@ class RejectCourseRegistrationRequest extends FormRequest
{ {
public function authorize(): bool public function authorize(): bool
{ {
return $this->user()->can('reject-course-registrations'); return $this->user()->can('reject-course-registrations')
&& $this->user()->canReviewCourseRegistrationOf($this->route('submission')->student);
} }
public function rules(): array public function rules(): array
@ -17,11 +18,4 @@ public function rules(): array
'reason' => ['required', 'string', 'max:1000'], 'reason' => ['required', 'string', 'max:1000'],
]; ];
} }
public function messages(): array
{
return [
'reason.required' => 'Alasan penolakan wajib diisi.',
];
}
} }

View File

@ -1,14 +1,14 @@
<?php <?php
namespace App\Http\Requests\Admin\Manage; namespace App\Http\Requests\Admin\Manage\CourseRegistration;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class SignCourseRegistrationRequest extends FormRequest class SignAsAdvisorCourseRegistrationRequest extends FormRequest
{ {
public function authorize(): bool public function authorize(): bool
{ {
return true; return $this->user()->isAdvisorOf($this->route('student'));
} }
public function rules(): array public function rules(): array

View File

@ -0,0 +1,20 @@
<?php
namespace App\Http\Requests\Admin\Manage\CourseRegistration;
use Illuminate\Foundation\Http\FormRequest;
class SignAsDepartmentLeaderCourseRegistrationRequest extends FormRequest
{
public function authorize(): bool
{
return $this->user()->isDepartmentLeaderOf($this->route('student'));
}
public function rules(): array
{
return [
'signature' => ['required', 'file', 'image', 'max:2048'],
];
}
}

View File

@ -1,16 +1,17 @@
<?php <?php
namespace App\Http\Requests\Admin\Manage; namespace App\Http\Requests\Admin\Manage\CourseRegistration;
use App\Enums\UserRole;
use Illuminate\Foundation\Http\FormRequest; use Illuminate\Foundation\Http\FormRequest;
class SaveCourseRegistrationRequest extends FormRequest class UpdateCourseRegistrationRequest extends FormRequest
{ {
public function authorize(): bool public function authorize(): bool
{ {
$student = $this->route('student'); $student = $this->route('student');
return $this->user()->hasRole('mahasiswa') return $this->user()->hasRole(UserRole::Mahasiswa->value)
&& $this->user()->student?->id === $student?->id; && $this->user()->student?->id === $student?->id;
} }

View File

@ -13,7 +13,7 @@
use Spatie\MediaLibrary\InteractsWithMedia; use Spatie\MediaLibrary\InteractsWithMedia;
#[Guarded(['id'])] #[Guarded(['id'])]
#[Appends(['signature_url', 'kaprodi_signature_url', 'advisor_signature_url'])] #[Appends(['signature_url', 'department_leader_signature_url', 'advisor_signature_url'])]
class CourseRegistrationSubmission extends Model implements HasMedia class CourseRegistrationSubmission extends Model implements HasMedia
{ {
use InteractsWithMedia; use InteractsWithMedia;
@ -31,7 +31,7 @@ protected function casts(): array
public function registerMediaCollections(): void public function registerMediaCollections(): void
{ {
$this->addMediaCollection('signature')->singleFile(); $this->addMediaCollection('signature')->singleFile();
$this->addMediaCollection('kaprodi_signature')->singleFile(); $this->addMediaCollection('department_leader_signature')->singleFile();
$this->addMediaCollection('advisor_signature')->singleFile(); $this->addMediaCollection('advisor_signature')->singleFile();
} }
@ -67,10 +67,10 @@ protected function signatureUrl(): Attribute
); );
} }
protected function kaprodiSignatureUrl(): Attribute protected function departmentLeaderSignatureUrl(): Attribute
{ {
return Attribute::make( return Attribute::make(
get: fn () => $this->getFirstMediaUrl('kaprodi_signature') ?: null, get: fn () => $this->getFirstMediaUrl('department_leader_signature') ?: null,
); );
} }

View File

@ -2,6 +2,7 @@
namespace App\Models; namespace App\Models;
use App\Enums\UserRole;
use Illuminate\Database\Eloquent\Attributes\Appends; use Illuminate\Database\Eloquent\Attributes\Appends;
use Illuminate\Database\Eloquent\Attributes\Guarded; use Illuminate\Database\Eloquent\Attributes\Guarded;
use Illuminate\Database\Eloquent\Attributes\Hidden; use Illuminate\Database\Eloquent\Attributes\Hidden;
@ -110,38 +111,38 @@ public function uploads(): HasMany
public function isAdvisorOf(Student $student): bool public function isAdvisorOf(Student $student): bool
{ {
return $this->hasRole('dosen') && $this->lecturer && $student->academic_advisor_id === $this->lecturer->id; return $this->hasRole(UserRole::Dosen->value) && $this->lecturer && $student->academic_advisor_id === $this->lecturer->id;
} }
public function isKaprodiOf(Student $student): bool public function isDepartmentLeaderOf(Student $student): bool
{ {
return $this->hasRole('kaprodi') && $this->lecturer && $student->department?->currentLeader?->lecturer_id === $this->lecturer->id; return $this->hasRole(UserRole::Kaprodi->value) && $this->lecturer && $student->department?->currentLeader?->lecturer_id === $this->lecturer->id;
} }
/** /**
* Gates page access: the student's academic advisor, the kaprodi of * Gates page access: the student's academic advisor, the department
* their department, or staff roles may view a student's KRS. * leader of their department, or staff roles may view a student's KRS.
*/ */
public function canAccessCourseRegistrationOf(Student $student): bool public function canAccessCourseRegistrationOf(Student $student): bool
{ {
if ($this->hasRole('dosen') || $this->hasRole('kaprodi')) { if ($this->hasRole(UserRole::Dosen->value) || $this->hasRole(UserRole::Kaprodi->value)) {
return $this->isAdvisorOf($student) || $this->isKaprodiOf($student); return $this->isAdvisorOf($student) || $this->isDepartmentLeaderOf($student);
} }
return true; return true;
} }
/** /**
* Only the student's academic advisor may approve/reject; the kaprodi * Only the student's academic advisor may approve/reject; the department
* signs but does not decide. Other staff roles retain override access. * leader signs but does not decide. Other staff roles retain override access.
*/ */
public function canReviewCourseRegistrationOf(Student $student): bool public function canReviewCourseRegistrationOf(Student $student): bool
{ {
if ($this->hasRole('dosen')) { if ($this->hasRole(UserRole::Dosen->value)) {
return $this->isAdvisorOf($student); return $this->isAdvisorOf($student);
} }
if ($this->hasRole('kaprodi')) { if ($this->hasRole(UserRole::Kaprodi->value)) {
return false; return false;
} }

View File

@ -4,6 +4,7 @@
use App\Enums\RegistrationStatus; use App\Enums\RegistrationStatus;
use App\Enums\StudentStatus; use App\Enums\StudentStatus;
use App\Enums\UserRole;
use App\Models\AcademicTerm; use App\Models\AcademicTerm;
use App\Models\ClassEnrollment; use App\Models\ClassEnrollment;
use App\Models\Course; use App\Models\Course;
@ -12,6 +13,7 @@
use App\Models\CourseRegistrationSubmission; use App\Models\CourseRegistrationSubmission;
use App\Models\Department; use App\Models\Department;
use App\Models\Student; use App\Models\Student;
use App\Models\User;
use App\Services\Admin\Master\AcademicTermService; use App\Services\Admin\Master\AcademicTermService;
use Illuminate\Contracts\Pagination\LengthAwarePaginator; use Illuminate\Contracts\Pagination\LengthAwarePaginator;
use Illuminate\Http\UploadedFile; use Illuminate\Http\UploadedFile;
@ -24,22 +26,12 @@ public function __construct(
private readonly AcademicTermService $academicTermService, private readonly AcademicTermService $academicTermService,
) {} ) {}
/** public function paginated(User $user, int $perPage = 25, string $search = '', ?int $departmentId = null): LengthAwarePaginator
* @return Collection<int, Department>
*/
public function departmentSummary(): Collection
{ {
return Department::query() $isDepartmentLeader = $user->hasRole(UserRole::Kaprodi->value);
->select('id', 'name') $advisorLecturerId = ! $isDepartmentLeader && $user->hasRole(UserRole::Dosen->value) ? $user->lecturer?->id : null;
->orderBy('name') $ledDepartmentIds = $isDepartmentLeader ? $user->ledDepartmentIds() : null;
->get();
}
/**
* @param array<int, int>|null $ledDepartmentIds Restricts results to a kaprodi's own department(s).
*/
public function paginated(int $perPage = 25, string $search = '', ?int $departmentId = null, ?int $advisorLecturerId = null, ?array $ledDepartmentIds = null): LengthAwarePaginator
{
$paginator = Student::query() $paginator = Student::query()
->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id']) ->select(['students.id', 'students.user_id', 'students.student_number', 'students.department_id'])
->join('departments', 'departments.id', '=', 'students.department_id') ->join('departments', 'departments.id', '=', 'students.department_id')
@ -219,11 +211,11 @@ public function saveRegistrations(
}); });
} }
public function signAsKaprodi(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission public function signAsDepartmentLeader(CourseRegistrationSubmission $submission, UploadedFile $signature): CourseRegistrationSubmission
{ {
$this->assertSignable($submission); $this->assertSignable($submission);
$submission->addMedia($signature)->toMediaCollection('kaprodi_signature'); $submission->addMedia($signature)->toMediaCollection('department_leader_signature');
return $submission; return $submission;
} }
@ -246,7 +238,7 @@ private function assertSignable(CourseRegistrationSubmission $submission): void
public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission public function approve(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
{ {
abort_unless($submission->status === RegistrationStatus::Submitted, 403); abort_unless($submission->status === RegistrationStatus::Submitted, 403);
abort_if($submission->kaprodi_signature_url === null, 422, 'Ketua Program Studi belum menandatangani KRS ini.'); abort_if($submission->department_leader_signature_url === null, 422, 'Ketua Program Studi belum menandatangani KRS ini.');
abort_if($submission->advisor_signature_url === null, 422, 'Silakan tanda tangani KRS ini terlebih dahulu.'); abort_if($submission->advisor_signature_url === null, 422, 'Silakan tanda tangani KRS ini terlebih dahulu.');
return DB::transaction(function () use ($submission) { return DB::transaction(function () use ($submission) {
@ -299,4 +291,14 @@ public function reject(CourseRegistrationSubmission $submission, string $reason)
return $submission; return $submission;
}); });
} }
/**
* @return Collection<int, Department>
*/
public function departmentSummary(): Collection
{
return Department::query()
->orderBy('name')
->get(['id', 'name']);
}
} }

View File

@ -25,9 +25,9 @@ import { Textarea } from '@/components/ui/textarea';
import { import {
advisor_signature, advisor_signature,
approve, approve,
kaprodi_signature, department_leader_signature,
reject, reject,
save, update,
} from '@/routes/admin/manage/course-registrations'; } from '@/routes/admin/manage/course-registrations';
import type { import type {
CourseRegistrationSubmission, CourseRegistrationSubmission,
@ -238,7 +238,7 @@ function SemesterCard({
isSemesterOpen, isSemesterOpen,
backHref, backHref,
canReview, canReview,
canSignAsKaprodi, canSignAsDepartmentLeader,
canSignAsAdvisor, canSignAsAdvisor,
}: { }: {
semester: number | null; semester: number | null;
@ -247,14 +247,14 @@ function SemesterCard({
isSemesterOpen: boolean; isSemesterOpen: boolean;
backHref: string | null; backHref: string | null;
canReview: boolean; canReview: boolean;
canSignAsKaprodi: boolean; canSignAsDepartmentLeader: boolean;
canSignAsAdvisor: boolean; canSignAsAdvisor: boolean;
}) { }) {
const [rejecting, setRejecting] = useState(false); const [rejecting, setRejecting] = useState(false);
const student = submission.student; const student = submission.student;
const department = student?.department ?? null; const department = student?.department ?? null;
const kaprodi = department?.current_leader?.lecturer ?? null; const departmentLeader = department?.current_leader?.lecturer ?? null;
const advisor = student?.academic_advisor ?? null; const advisor = student?.academic_advisor ?? null;
const isViewOnly = backHref !== null; const isViewOnly = backHref !== null;
@ -267,11 +267,13 @@ function SemesterCard({
canReview && submission.status === 'submitted' && !notYetSubmitted; canReview && submission.status === 'submitted' && !notYetSubmitted;
const canReviewersSign = const canReviewersSign =
submission.status === 'submitted' && !notYetSubmitted; submission.status === 'submitted' && !notYetSubmitted;
const missingKaprodiSignature = !submission.kaprodi_signature_url; const missingDepartmentLeaderSignature =
!submission.department_leader_signature_url;
const missingOwnSignature = const missingOwnSignature =
(canSignAsKaprodi && !submission.kaprodi_signature_url) || (canSignAsDepartmentLeader &&
!submission.department_leader_signature_url) ||
(canSignAsAdvisor && !submission.advisor_signature_url); (canSignAsAdvisor && !submission.advisor_signature_url);
const cannotApprove = missingKaprodiSignature || missingOwnSignature; const cannotApprove = missingDepartmentLeaderSignature || missingOwnSignature;
const totalCredits = submission.course_registrations.reduce( const totalCredits = submission.course_registrations.reduce(
(sum, registration) => (sum, registration) =>
@ -291,7 +293,7 @@ function SemesterCard({
return ( return (
<Form <Form
action={save([studentId, semesterKey(semester)])} action={update([studentId, semesterKey(semester)])}
options={{ preserveScroll: true }} options={{ preserveScroll: true }}
className="flex flex-col gap-6" className="flex flex-col gap-6"
> >
@ -471,25 +473,28 @@ function SemesterCard({
<p>Ketua Program Studi,</p> <p>Ketua Program Studi,</p>
<ReviewerSignatureField <ReviewerSignatureField
signatureUrl={ signatureUrl={
submission.kaprodi_signature_url submission.department_leader_signature_url
} }
alt="Tanda tangan Ketua Program Studi" alt="Tanda tangan Ketua Program Studi"
canSign={ canSign={
canSignAsKaprodi && canSignAsDepartmentLeader &&
canReviewersSign canReviewersSign
} }
submitUrl={kaprodi_signature.url([ submitUrl={department_leader_signature.url(
studentId, [
semesterKey(semester), studentId,
])} semesterKey(semester),
],
)}
/> />
<p className="font-semibold"> <p className="font-semibold">
{kaprodi?.user?.profile {departmentLeader?.user?.profile
?.full_name ?? '-'} ?.full_name ?? '-'}
</p> </p>
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
NIDN.{' '} NIDN.{' '}
{kaprodi?.lecturer_number ?? '-'} {departmentLeader?.lecturer_number ??
'-'}
</p> </p>
</div> </div>
@ -579,7 +584,7 @@ function SemesterCard({
</div> </div>
{cannotApprove && ( {cannotApprove && (
<p className="text-xs text-muted-foreground"> <p className="text-xs text-muted-foreground">
{missingKaprodiSignature {missingDepartmentLeaderSignature
? 'Menunggu tanda tangan Ketua Program Studi sebelum dapat disetujui.' ? 'Menunggu tanda tangan Ketua Program Studi sebelum dapat disetujui.'
: 'Silakan tanda tangani KRS ini terlebih dahulu sebelum menyetujui.'} : 'Silakan tanda tangani KRS ini terlebih dahulu sebelum menyetujui.'}
</p> </p>
@ -628,7 +633,7 @@ type Props = {
hasActiveTerm: boolean; hasActiveTerm: boolean;
backHref: string | null; backHref: string | null;
canReview: boolean; canReview: boolean;
canSignAsKaprodi: boolean; canSignAsDepartmentLeader: boolean;
canSignAsAdvisor: boolean; canSignAsAdvisor: boolean;
}; };
@ -640,7 +645,7 @@ export default function CourseRegistrationDetail({
hasActiveTerm, hasActiveTerm,
backHref, backHref,
canReview, canReview,
canSignAsKaprodi, canSignAsDepartmentLeader,
canSignAsAdvisor, canSignAsAdvisor,
}: Props) { }: Props) {
const [activeSemester, setActiveSemester] = useState(() => const [activeSemester, setActiveSemester] = useState(() =>
@ -712,7 +717,7 @@ export default function CourseRegistrationDetail({
)} )}
backHref={backHref} backHref={backHref}
canReview={canReview} canReview={canReview}
canSignAsKaprodi={canSignAsKaprodi} canSignAsDepartmentLeader={canSignAsDepartmentLeader}
canSignAsAdvisor={canSignAsAdvisor} canSignAsAdvisor={canSignAsAdvisor}
/> />
) : ( ) : (

View File

@ -78,7 +78,7 @@ export type CourseRegistrationSubmission = {
rejection_reason: string | null; rejection_reason: string | null;
signed_at: string; signed_at: string;
signature_url: string | null; signature_url: string | null;
kaprodi_signature_url: string | null; department_leader_signature_url: string | null;
advisor_signature_url: string | null; advisor_signature_url: string | null;
has_registrations: boolean; has_registrations: boolean;
student: CourseRegistrationSubmissionStudent | null; student: CourseRegistrationSubmissionStudent | null;

View File

@ -107,8 +107,8 @@
Route::patch('submissions/{submission}/approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations'); Route::patch('submissions/{submission}/approve', [CourseRegistrationController::class, 'approve'])->name('approve')->middleware('permission:approve-course-registrations');
Route::patch('submissions/{submission}/reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations'); Route::patch('submissions/{submission}/reject', [CourseRegistrationController::class, 'reject'])->name('reject')->middleware('permission:reject-course-registrations');
Route::get('{student}', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations'); Route::get('{student}', [CourseRegistrationController::class, 'show'])->name('show')->middleware('permission:view-course-registrations');
Route::put('{student}/{semester}', [CourseRegistrationController::class, 'save'])->name('save')->middleware('permission:view-course-registrations'); Route::put('{student}/{semester}', [CourseRegistrationController::class, 'update'])->name('update')->middleware('permission:view-course-registrations');
Route::patch('{student}/{semester}/kaprodi-signature', [CourseRegistrationController::class, 'signAsKaprodi'])->name('kaprodi_signature')->middleware('permission:view-course-registrations'); Route::patch('{student}/{semester}/department-leader-signature', [CourseRegistrationController::class, 'signAsDepartmentLeader'])->name('department_leader_signature')->middleware('permission:view-course-registrations');
Route::patch('{student}/{semester}/advisor-signature', [CourseRegistrationController::class, 'signAsAdvisor'])->name('advisor_signature')->middleware('permission:approve-course-registrations'); Route::patch('{student}/{semester}/advisor-signature', [CourseRegistrationController::class, 'signAsAdvisor'])->name('advisor_signature')->middleware('permission:approve-course-registrations');
}); });