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\SaveCourseRegistrationRequest;
|
||||||
use App\Http\Requests\Admin\Manage\SignCourseRegistrationRequest;
|
use App\Http\Requests\Admin\Manage\SignCourseRegistrationRequest;
|
||||||
use App\Http\Requests\PaginatedRequest;
|
use App\Http\Requests\PaginatedRequest;
|
||||||
use App\Models\CourseClass;
|
use App\Models\Course;
|
||||||
use App\Models\CourseRegistrationSubmission;
|
use App\Models\CourseRegistrationSubmission;
|
||||||
use App\Models\Student;
|
use App\Models\Student;
|
||||||
use App\Services\Admin\Manage\CourseRegistrationService;
|
use App\Services\Admin\Manage\CourseRegistrationService;
|
||||||
@ -69,25 +69,31 @@ public function show(Student $student): Response
|
|||||||
? $this->service->availableCourseClasses($student->department_id, $activeTerm->id)
|
? $this->service->availableCourseClasses($student->department_id, $activeTerm->id)
|
||||||
: collect();
|
: collect();
|
||||||
|
|
||||||
$semesters = $allCourseClasses
|
$departmentCourses = $this->service->departmentCourses($student->department_id);
|
||||||
->map(fn (CourseClass $courseClass) => $courseClass->course?->semester_number)
|
|
||||||
|
$semesters = $departmentCourses
|
||||||
|
->map(fn (Course $course) => $course->semester_number)
|
||||||
->unique()
|
->unique()
|
||||||
->sort()
|
->sort()
|
||||||
->values();
|
->values();
|
||||||
|
|
||||||
$submissions = $semesters->mapWithKeys(fn (?int $semesterNumber) => [
|
$submissions = $activeTerm
|
||||||
CourseRegistrationSubmission::semesterKey($semesterNumber) => $this->service->buildSubmissionPayload(
|
? $semesters->mapWithKeys(fn (?int $semesterNumber) => [
|
||||||
$student,
|
CourseRegistrationSubmission::semesterKey($semesterNumber) => $this->service->buildSubmissionPayload(
|
||||||
$activeTerm,
|
$student,
|
||||||
$semesterNumber,
|
$activeTerm,
|
||||||
$allCourseClasses,
|
$semesterNumber,
|
||||||
),
|
$departmentCourses,
|
||||||
]);
|
$allCourseClasses,
|
||||||
|
),
|
||||||
|
])
|
||||||
|
: collect();
|
||||||
|
|
||||||
return Inertia::render('admin/manage/course-registrations/show', [
|
return Inertia::render('admin/manage/course-registrations/show', [
|
||||||
'submissions' => $submissions,
|
'submissions' => $submissions,
|
||||||
'semesters' => $semesters->values(),
|
'semesters' => $semesters->values(),
|
||||||
'openSemesters' => $activeTerm?->open_semesters ?? [],
|
'openSemesters' => $activeTerm?->open_semesters ?? [],
|
||||||
|
'hasActiveTerm' => $activeTerm !== null,
|
||||||
'backHref' => $backHref,
|
'backHref' => $backHref,
|
||||||
'canReview' => $canReview,
|
'canReview' => $canReview,
|
||||||
'canSignAsKaprodi' => $canSignAsKaprodi,
|
'canSignAsKaprodi' => $canSignAsKaprodi,
|
||||||
|
|||||||
@ -6,6 +6,7 @@
|
|||||||
use App\Enums\StudentStatus;
|
use App\Enums\StudentStatus;
|
||||||
use App\Models\AcademicTerm;
|
use App\Models\AcademicTerm;
|
||||||
use App\Models\ClassEnrollment;
|
use App\Models\ClassEnrollment;
|
||||||
|
use App\Models\Course;
|
||||||
use App\Models\CourseClass;
|
use App\Models\CourseClass;
|
||||||
use App\Models\CourseRegistration;
|
use App\Models\CourseRegistration;
|
||||||
use App\Models\CourseRegistrationSubmission;
|
use App\Models\CourseRegistrationSubmission;
|
||||||
@ -85,7 +86,7 @@ public function findOrCreateForSemester(Student $student, string $semester): Cou
|
|||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @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);
|
$submission = $this->findOrCreateForStudent($student, $activeTerm, $semesterNumber);
|
||||||
|
|
||||||
@ -93,20 +94,41 @@ public function buildSubmissionPayload(Student $student, ?AcademicTerm $activeTe
|
|||||||
$payload = $data->toArray();
|
$payload = $data->toArray();
|
||||||
|
|
||||||
$registeredCourseClassIds = $data->courseRegistrations->pluck('course_class_id')->all();
|
$registeredCourseClassIds = $data->courseRegistrations->pluck('course_class_id')->all();
|
||||||
|
$courseClassesByCourseId = $allCourseClasses->keyBy('course_id');
|
||||||
|
|
||||||
$payload['has_registrations'] = ! empty($registeredCourseClassIds);
|
$payload['has_registrations'] = ! empty($registeredCourseClassIds);
|
||||||
$payload['course_registrations'] = $allCourseClasses
|
$payload['course_registrations'] = $departmentCourses
|
||||||
->filter(fn (CourseClass $courseClass) => $courseClass->course?->semester_number === $semesterNumber)
|
->filter(fn (Course $course) => $course->semester_number === $semesterNumber)
|
||||||
->map(fn (CourseClass $courseClass) => [
|
->map(function (Course $course) use ($courseClassesByCourseId, $registeredCourseClassIds) {
|
||||||
'id' => $courseClass->id,
|
$courseClass = $courseClassesByCourseId->get($course->id);
|
||||||
'course_class' => ['id' => $courseClass->id, 'course' => $courseClass->course],
|
|
||||||
'is_registered' => in_array($courseClass->id, $registeredCourseClassIds),
|
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();
|
->values();
|
||||||
|
|
||||||
return $payload;
|
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
|
public function withDetails(CourseRegistrationSubmission $submission): CourseRegistrationSubmission
|
||||||
{
|
{
|
||||||
return $submission->load([
|
return $submission->load([
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { Form, Head, Link, router } from '@inertiajs/react';
|
import { Form, Head, Link, router } from '@inertiajs/react';
|
||||||
import { format } from 'date-fns';
|
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 { useState } from 'react';
|
||||||
import { FormDialog } from '@/components/form-dialog';
|
import { FormDialog } from '@/components/form-dialog';
|
||||||
import InputError from '@/components/input-error';
|
import InputError from '@/components/input-error';
|
||||||
@ -265,10 +265,10 @@ function SemesterCard({
|
|||||||
(sum, registration) =>
|
(sum, registration) =>
|
||||||
submission.has_registrations
|
submission.has_registrations
|
||||||
? registration.is_registered
|
? registration.is_registered
|
||||||
? sum + (registration.course_class?.course?.credits ?? 0)
|
? sum + registration.course.credits
|
||||||
: sum
|
: sum
|
||||||
: isSemesterOpen
|
: isSemesterOpen && registration.course_class
|
||||||
? sum + (registration.course_class?.course?.credits ?? 0)
|
? sum + registration.course.credits
|
||||||
: sum,
|
: sum,
|
||||||
0,
|
0,
|
||||||
);
|
);
|
||||||
@ -373,9 +373,7 @@ function SemesterCard({
|
|||||||
index,
|
index,
|
||||||
) => {
|
) => {
|
||||||
const course =
|
const course =
|
||||||
registration
|
registration.course;
|
||||||
.course_class
|
|
||||||
?.course;
|
|
||||||
const checked =
|
const checked =
|
||||||
submission.has_registrations
|
submission.has_registrations
|
||||||
? registration.is_registered
|
? registration.is_registered
|
||||||
@ -391,34 +389,42 @@ function SemesterCard({
|
|||||||
{index + 1}
|
{index + 1}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{course?.name ??
|
{course.name}
|
||||||
'N/A'}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
{course?.code ??
|
{course.code}
|
||||||
'-'}
|
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
{course?.credits ??
|
{
|
||||||
'-'}
|
course.credits
|
||||||
|
}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-center">
|
<TableCell className="text-center">
|
||||||
<Checkbox
|
{registration.course_class ? (
|
||||||
name="course_class_ids[]"
|
<Checkbox
|
||||||
value={
|
name="course_class_ids[]"
|
||||||
registration
|
value={
|
||||||
.course_class
|
registration
|
||||||
?.id
|
.course_class
|
||||||
}
|
.id
|
||||||
defaultChecked={
|
}
|
||||||
checked
|
defaultChecked={
|
||||||
}
|
checked
|
||||||
disabled={
|
}
|
||||||
!isSemesterOpen ||
|
disabled={
|
||||||
isViewOnly ||
|
!isSemesterOpen ||
|
||||||
isLocked
|
isViewOnly ||
|
||||||
}
|
isLocked
|
||||||
/>
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
className="text-xs text-muted-foreground"
|
||||||
|
title="Kelas belum dibuka untuk periode ini"
|
||||||
|
>
|
||||||
|
—
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
);
|
);
|
||||||
@ -602,6 +608,7 @@ type Props = {
|
|||||||
submissions: Record<string, CourseRegistrationSubmission>;
|
submissions: Record<string, CourseRegistrationSubmission>;
|
||||||
semesters: (number | null)[];
|
semesters: (number | null)[];
|
||||||
openSemesters: number[];
|
openSemesters: number[];
|
||||||
|
hasActiveTerm: boolean;
|
||||||
backHref: string | null;
|
backHref: string | null;
|
||||||
canReview: boolean;
|
canReview: boolean;
|
||||||
canSignAsKaprodi: boolean;
|
canSignAsKaprodi: boolean;
|
||||||
@ -612,6 +619,7 @@ export default function CourseRegistrationDetail({
|
|||||||
submissions,
|
submissions,
|
||||||
semesters,
|
semesters,
|
||||||
openSemesters,
|
openSemesters,
|
||||||
|
hasActiveTerm,
|
||||||
backHref,
|
backHref,
|
||||||
canReview,
|
canReview,
|
||||||
canSignAsKaprodi,
|
canSignAsKaprodi,
|
||||||
@ -670,7 +678,7 @@ export default function CourseRegistrationDetail({
|
|||||||
</Tabs>
|
</Tabs>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{currentSubmission && (
|
{currentSubmission ? (
|
||||||
<SemesterCard
|
<SemesterCard
|
||||||
key={currentKey}
|
key={currentKey}
|
||||||
semester={currentSemester}
|
semester={currentSemester}
|
||||||
@ -684,6 +692,40 @@ export default function CourseRegistrationDetail({
|
|||||||
canSignAsKaprodi={canSignAsKaprodi}
|
canSignAsKaprodi={canSignAsKaprodi}
|
||||||
canSignAsAdvisor={canSignAsAdvisor}
|
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>
|
</div>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -52,15 +52,15 @@ export type CourseRegistrationSubmissionStudent = {
|
|||||||
export type CourseRegistrationSubmissionEntry = {
|
export type CourseRegistrationSubmissionEntry = {
|
||||||
id: number;
|
id: number;
|
||||||
is_registered: boolean;
|
is_registered: boolean;
|
||||||
|
course: {
|
||||||
|
id: number;
|
||||||
|
code: string;
|
||||||
|
name: string;
|
||||||
|
credits: number;
|
||||||
|
semester_number: number | null;
|
||||||
|
};
|
||||||
course_class: {
|
course_class: {
|
||||||
id: number;
|
id: number;
|
||||||
course: {
|
|
||||||
id: number;
|
|
||||||
code: string;
|
|
||||||
name: string;
|
|
||||||
credits: number;
|
|
||||||
semester_number: number | null;
|
|
||||||
} | null;
|
|
||||||
} | null;
|
} | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user