feat: refactor course registration logic to use department-specific courses and improve submission payload structure
This commit is contained in:
parent
86863ff2f8
commit
9892f60e5e
@ -7,7 +7,7 @@
|
||||
use App\Http\Requests\Admin\Manage\SaveCourseRegistrationRequest;
|
||||
use App\Http\Requests\Admin\Manage\SignCourseRegistrationRequest;
|
||||
use App\Http\Requests\PaginatedRequest;
|
||||
use App\Models\CourseClass;
|
||||
use App\Models\Course;
|
||||
use App\Models\CourseRegistrationSubmission;
|
||||
use App\Models\Student;
|
||||
use App\Services\Admin\Manage\CourseRegistrationService;
|
||||
@ -69,25 +69,31 @@ public function show(Student $student): Response
|
||||
? $this->service->availableCourseClasses($student->department_id, $activeTerm->id)
|
||||
: collect();
|
||||
|
||||
$semesters = $allCourseClasses
|
||||
->map(fn (CourseClass $courseClass) => $courseClass->course?->semester_number)
|
||||
$departmentCourses = $this->service->departmentCourses($student->department_id);
|
||||
|
||||
$semesters = $departmentCourses
|
||||
->map(fn (Course $course) => $course->semester_number)
|
||||
->unique()
|
||||
->sort()
|
||||
->values();
|
||||
|
||||
$submissions = $semesters->mapWithKeys(fn (?int $semesterNumber) => [
|
||||
CourseRegistrationSubmission::semesterKey($semesterNumber) => $this->service->buildSubmissionPayload(
|
||||
$student,
|
||||
$activeTerm,
|
||||
$semesterNumber,
|
||||
$allCourseClasses,
|
||||
),
|
||||
]);
|
||||
$submissions = $activeTerm
|
||||
? $semesters->mapWithKeys(fn (?int $semesterNumber) => [
|
||||
CourseRegistrationSubmission::semesterKey($semesterNumber) => $this->service->buildSubmissionPayload(
|
||||
$student,
|
||||
$activeTerm,
|
||||
$semesterNumber,
|
||||
$departmentCourses,
|
||||
$allCourseClasses,
|
||||
),
|
||||
])
|
||||
: collect();
|
||||
|
||||
return Inertia::render('admin/manage/course-registrations/show', [
|
||||
'submissions' => $submissions,
|
||||
'semesters' => $semesters->values(),
|
||||
'openSemesters' => $activeTerm?->open_semesters ?? [],
|
||||
'hasActiveTerm' => $activeTerm !== null,
|
||||
'backHref' => $backHref,
|
||||
'canReview' => $canReview,
|
||||
'canSignAsKaprodi' => $canSignAsKaprodi,
|
||||
|
||||
@ -6,6 +6,7 @@
|
||||
use App\Enums\StudentStatus;
|
||||
use App\Models\AcademicTerm;
|
||||
use App\Models\ClassEnrollment;
|
||||
use App\Models\Course;
|
||||
use App\Models\CourseClass;
|
||||
use App\Models\CourseRegistration;
|
||||
use App\Models\CourseRegistrationSubmission;
|
||||
@ -85,7 +86,7 @@ public function findOrCreateForSemester(Student $student, string $semester): Cou
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function buildSubmissionPayload(Student $student, ?AcademicTerm $activeTerm, ?int $semesterNumber, Collection $allCourseClasses): array
|
||||
public function buildSubmissionPayload(Student $student, AcademicTerm $activeTerm, ?int $semesterNumber, Collection $departmentCourses, Collection $allCourseClasses): array
|
||||
{
|
||||
$submission = $this->findOrCreateForStudent($student, $activeTerm, $semesterNumber);
|
||||
|
||||
@ -93,20 +94,41 @@ public function buildSubmissionPayload(Student $student, ?AcademicTerm $activeTe
|
||||
$payload = $data->toArray();
|
||||
|
||||
$registeredCourseClassIds = $data->courseRegistrations->pluck('course_class_id')->all();
|
||||
$courseClassesByCourseId = $allCourseClasses->keyBy('course_id');
|
||||
|
||||
$payload['has_registrations'] = ! empty($registeredCourseClassIds);
|
||||
$payload['course_registrations'] = $allCourseClasses
|
||||
->filter(fn (CourseClass $courseClass) => $courseClass->course?->semester_number === $semesterNumber)
|
||||
->map(fn (CourseClass $courseClass) => [
|
||||
'id' => $courseClass->id,
|
||||
'course_class' => ['id' => $courseClass->id, 'course' => $courseClass->course],
|
||||
'is_registered' => in_array($courseClass->id, $registeredCourseClassIds),
|
||||
])
|
||||
$payload['course_registrations'] = $departmentCourses
|
||||
->filter(fn (Course $course) => $course->semester_number === $semesterNumber)
|
||||
->map(function (Course $course) use ($courseClassesByCourseId, $registeredCourseClassIds) {
|
||||
$courseClass = $courseClassesByCourseId->get($course->id);
|
||||
|
||||
return [
|
||||
'id' => $courseClass?->id ?? -$course->id,
|
||||
'course' => $course,
|
||||
'course_class' => $courseClass ? ['id' => $courseClass->id] : null,
|
||||
'is_registered' => $courseClass !== null && in_array($courseClass->id, $registeredCourseClassIds, true),
|
||||
];
|
||||
})
|
||||
->values();
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
/**
|
||||
* The department's full course curriculum, independent of which classes
|
||||
* have been opened for the current academic term.
|
||||
*
|
||||
* @return Collection<int, Course>
|
||||
*/
|
||||
public function departmentCourses(int $departmentId): Collection
|
||||
{
|
||||
return Course::query()
|
||||
->where('department_id', $departmentId)
|
||||
->orderBy('semester_number')
|
||||
->orderBy('name')
|
||||
->get(['id', 'code', 'name', 'credits', 'semester_number']);
|
||||
}
|
||||
|
||||
public function withDetails(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
|
||||
{
|
||||
return $submission->load([
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Form, Head, Link, router } from '@inertiajs/react';
|
||||
import { format } from 'date-fns';
|
||||
import { ArrowLeft, Check, Send, X } from 'lucide-react';
|
||||
import { ArrowLeft, Check, Inbox, Send, X } from 'lucide-react';
|
||||
import { useState } from 'react';
|
||||
import { FormDialog } from '@/components/form-dialog';
|
||||
import InputError from '@/components/input-error';
|
||||
@ -265,10 +265,10 @@ function SemesterCard({
|
||||
(sum, registration) =>
|
||||
submission.has_registrations
|
||||
? registration.is_registered
|
||||
? sum + (registration.course_class?.course?.credits ?? 0)
|
||||
? sum + registration.course.credits
|
||||
: sum
|
||||
: isSemesterOpen
|
||||
? sum + (registration.course_class?.course?.credits ?? 0)
|
||||
: isSemesterOpen && registration.course_class
|
||||
? sum + registration.course.credits
|
||||
: sum,
|
||||
0,
|
||||
);
|
||||
@ -373,9 +373,7 @@ function SemesterCard({
|
||||
index,
|
||||
) => {
|
||||
const course =
|
||||
registration
|
||||
.course_class
|
||||
?.course;
|
||||
registration.course;
|
||||
const checked =
|
||||
submission.has_registrations
|
||||
? registration.is_registered
|
||||
@ -391,34 +389,42 @@ function SemesterCard({
|
||||
{index + 1}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{course?.name ??
|
||||
'N/A'}
|
||||
{course.name}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
{course?.code ??
|
||||
'-'}
|
||||
{course.code}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
{course?.credits ??
|
||||
'-'}
|
||||
{
|
||||
course.credits
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="text-center">
|
||||
<Checkbox
|
||||
name="course_class_ids[]"
|
||||
value={
|
||||
registration
|
||||
.course_class
|
||||
?.id
|
||||
}
|
||||
defaultChecked={
|
||||
checked
|
||||
}
|
||||
disabled={
|
||||
!isSemesterOpen ||
|
||||
isViewOnly ||
|
||||
isLocked
|
||||
}
|
||||
/>
|
||||
{registration.course_class ? (
|
||||
<Checkbox
|
||||
name="course_class_ids[]"
|
||||
value={
|
||||
registration
|
||||
.course_class
|
||||
.id
|
||||
}
|
||||
defaultChecked={
|
||||
checked
|
||||
}
|
||||
disabled={
|
||||
!isSemesterOpen ||
|
||||
isViewOnly ||
|
||||
isLocked
|
||||
}
|
||||
/>
|
||||
) : (
|
||||
<span
|
||||
className="text-xs text-muted-foreground"
|
||||
title="Kelas belum dibuka untuk periode ini"
|
||||
>
|
||||
—
|
||||
</span>
|
||||
)}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
@ -602,6 +608,7 @@ type Props = {
|
||||
submissions: Record<string, CourseRegistrationSubmission>;
|
||||
semesters: (number | null)[];
|
||||
openSemesters: number[];
|
||||
hasActiveTerm: boolean;
|
||||
backHref: string | null;
|
||||
canReview: boolean;
|
||||
canSignAsKaprodi: boolean;
|
||||
@ -612,6 +619,7 @@ export default function CourseRegistrationDetail({
|
||||
submissions,
|
||||
semesters,
|
||||
openSemesters,
|
||||
hasActiveTerm,
|
||||
backHref,
|
||||
canReview,
|
||||
canSignAsKaprodi,
|
||||
@ -670,7 +678,7 @@ export default function CourseRegistrationDetail({
|
||||
</Tabs>
|
||||
)}
|
||||
|
||||
{currentSubmission && (
|
||||
{currentSubmission ? (
|
||||
<SemesterCard
|
||||
key={currentKey}
|
||||
semester={currentSemester}
|
||||
@ -684,6 +692,40 @@ export default function CourseRegistrationDetail({
|
||||
canSignAsKaprodi={canSignAsKaprodi}
|
||||
canSignAsAdvisor={canSignAsAdvisor}
|
||||
/>
|
||||
) : (
|
||||
semesters.length > 0 &&
|
||||
!hasActiveTerm && (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center gap-2 py-12 text-center">
|
||||
<Inbox className="h-10 w-10 text-muted-foreground" />
|
||||
<p className="font-medium">
|
||||
Belum ada periode akademik aktif
|
||||
</p>
|
||||
<p className="max-w-sm text-sm text-muted-foreground">
|
||||
Registrasi KRS hanya dapat dilakukan saat
|
||||
ada periode akademik yang aktif. Silakan
|
||||
hubungi admin untuk informasi lebih
|
||||
lanjut.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
)}
|
||||
|
||||
{semesters.length === 0 && (
|
||||
<Card>
|
||||
<CardContent className="flex flex-col items-center gap-2 py-12 text-center">
|
||||
<Inbox className="h-10 w-10 text-muted-foreground" />
|
||||
<p className="font-medium">
|
||||
Belum ada mata kuliah terdaftar
|
||||
</p>
|
||||
<p className="max-w-sm text-sm text-muted-foreground">
|
||||
Belum ada mata kuliah yang terdaftar untuk
|
||||
jurusan ini. Silakan hubungi admin atau
|
||||
program studi.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
|
||||
@ -52,15 +52,15 @@ export type CourseRegistrationSubmissionStudent = {
|
||||
export type CourseRegistrationSubmissionEntry = {
|
||||
id: number;
|
||||
is_registered: boolean;
|
||||
course: {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
credits: number;
|
||||
semester_number: number | null;
|
||||
};
|
||||
course_class: {
|
||||
id: number;
|
||||
course: {
|
||||
id: number;
|
||||
code: string;
|
||||
name: string;
|
||||
credits: number;
|
||||
semester_number: number | null;
|
||||
} | null;
|
||||
} | null;
|
||||
};
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user