Some checks failed
tests / ci (pull_request) Has been cancelled
- Implemented CourseRegistrationShow component for admin to view registration details. - Added course registration creation and listing for students. - Enhanced student registration edit and create forms with improved error handling. - Introduced new columns for course registration submissions in student view. - Updated routes for course registration management in admin and student contexts. - Added logging and status handling for course registration submissions. - Improved type definitions for course registration and user roles.
260 lines
11 KiB
TypeScript
260 lines
11 KiB
TypeScript
import { Form, Head, Link } from '@inertiajs/react';
|
|
import { ArrowLeft, XCircle } from 'lucide-react';
|
|
import { useState } from 'react';
|
|
import InputError from '@/components/input-error';
|
|
import { PageHeader } from '@/components/page-header';
|
|
import { SignaturePad } from '@/components/signature-pad';
|
|
import { Button } from '@/components/ui/button';
|
|
import {
|
|
Card,
|
|
CardContent,
|
|
CardDescription,
|
|
CardFooter,
|
|
CardHeader,
|
|
CardTitle,
|
|
} from '@/components/ui/card';
|
|
import { Checkbox } from '@/components/ui/checkbox';
|
|
import { index, store } from '@/routes/student/course-registrations';
|
|
import type { AcademicTerm } from '@/types/academic-term';
|
|
import { formatAcademicTermLabel } from '@/types/academic-term';
|
|
|
|
type Lecturer = {
|
|
user: { profile: { full_name: string } | null } | null;
|
|
};
|
|
|
|
type Course = {
|
|
id: number;
|
|
code: string;
|
|
name: string;
|
|
credits: number;
|
|
};
|
|
|
|
type CourseClass = {
|
|
id: number;
|
|
lecturer: Lecturer | null;
|
|
};
|
|
|
|
type Student = {
|
|
student_number: string;
|
|
current_semester: number;
|
|
department: { name: string } | null;
|
|
user: { profile: { full_name: string } | null } | null;
|
|
academic_advisor: {
|
|
user: { profile: { full_name: string } | null } | null;
|
|
} | null;
|
|
};
|
|
|
|
type AvailableCourse = {
|
|
course: Course;
|
|
course_class: CourseClass | null;
|
|
};
|
|
|
|
type Registration = {
|
|
id: number;
|
|
course_class: { id: number };
|
|
};
|
|
|
|
type Submission = {
|
|
status: 'submitted' | 'approved' | 'rejected';
|
|
rejection_reason: string | null;
|
|
course_registrations: Registration[];
|
|
};
|
|
|
|
type Props = {
|
|
student: Student;
|
|
academicTerm: AcademicTerm;
|
|
submission: Submission | null;
|
|
availableCourses: AvailableCourse[];
|
|
};
|
|
|
|
export default function CourseRegistrationCreate({
|
|
student,
|
|
academicTerm,
|
|
submission,
|
|
availableCourses,
|
|
}: Props) {
|
|
const [selected, setSelected] = useState<number[]>(() => {
|
|
if (submission && submission.course_registrations.length > 0) {
|
|
return submission.course_registrations.map(
|
|
(registration) => registration.course_class.id,
|
|
);
|
|
}
|
|
|
|
return availableCourses
|
|
.filter((item) => item.course_class)
|
|
.map((item) => item.course_class!.id);
|
|
});
|
|
|
|
function toggle(courseClassId: number, checked: boolean) {
|
|
setSelected((prev) =>
|
|
checked
|
|
? [...prev, courseClassId]
|
|
: prev.filter((id) => id !== courseClassId),
|
|
);
|
|
}
|
|
|
|
const totalCredits = availableCourses
|
|
.filter(
|
|
(item) =>
|
|
item.course_class && selected.includes(item.course_class.id),
|
|
)
|
|
.reduce((sum, item) => sum + item.course.credits, 0);
|
|
|
|
return (
|
|
<>
|
|
<Head title="Isi KRS" />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
|
|
<PageHeader
|
|
title="Isi Kartu Rencana Studi"
|
|
actions={
|
|
<Button variant="outline" asChild>
|
|
<Link href={index.url()}>
|
|
<ArrowLeft className="h-4 w-4" />
|
|
Kembali
|
|
</Link>
|
|
</Button>
|
|
}
|
|
/>
|
|
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
{student.user?.profile?.full_name ?? '-'}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
{student.student_number} ·{' '}
|
|
{student.department?.name ?? '-'} · Semester{' '}
|
|
{student.current_semester}
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-1 text-sm text-muted-foreground">
|
|
<p>
|
|
Periode Akademik:{' '}
|
|
{formatAcademicTermLabel(academicTerm)}
|
|
</p>
|
|
<p>
|
|
Dosen Pembimbing Akademik:{' '}
|
|
{student.academic_advisor?.user?.profile
|
|
?.full_name ?? '-'}
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{submission?.status === 'rejected' && (
|
|
<Card className="border-destructive/50">
|
|
<CardContent className="pt-6 text-sm">
|
|
<p className="flex items-center gap-2 font-medium text-destructive">
|
|
<XCircle className="h-4 w-4" />
|
|
KRS Anda ditolak
|
|
</p>
|
|
<p className="mt-1 text-muted-foreground">
|
|
{submission.rejection_reason ??
|
|
'Tidak ada alasan yang diberikan.'}
|
|
</p>
|
|
<p className="mt-1 text-muted-foreground">
|
|
Silakan perbaiki pilihan mata kuliah Anda dan
|
|
ajukan kembali di bawah ini.
|
|
</p>
|
|
</CardContent>
|
|
</Card>
|
|
)}
|
|
|
|
<Form action={store()}>
|
|
{({ errors, processing }) => (
|
|
<Card>
|
|
<CardHeader>
|
|
<CardTitle>
|
|
Pilih Mata Kuliah Semester{' '}
|
|
{student.current_semester}
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Total {totalCredits} SKS dipilih
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="grid gap-3">
|
|
{availableCourses.length === 0 && (
|
|
<p className="text-sm text-muted-foreground">
|
|
Belum ada mata kuliah/kelas untuk
|
|
semester ini pada periode aktif.
|
|
</p>
|
|
)}
|
|
{availableCourses.map(
|
|
({ course, course_class }) => (
|
|
<label
|
|
key={course.id}
|
|
className="flex items-start gap-3 rounded-md border p-3 has-disabled:opacity-50"
|
|
>
|
|
<Checkbox
|
|
className="mt-0.5"
|
|
checked={
|
|
course_class
|
|
? selected.includes(
|
|
course_class.id,
|
|
)
|
|
: false
|
|
}
|
|
disabled={!course_class}
|
|
onCheckedChange={(checked) =>
|
|
course_class &&
|
|
toggle(
|
|
course_class.id,
|
|
checked === true,
|
|
)
|
|
}
|
|
/>
|
|
<div>
|
|
<p className="font-medium">
|
|
{course.code} -{' '}
|
|
{course.name}
|
|
</p>
|
|
<p className="text-xs text-muted-foreground">
|
|
{course.credits} SKS
|
|
·{' '}
|
|
{course_class
|
|
? (course_class.lecturer
|
|
?.user?.profile
|
|
?.full_name ??
|
|
'Dosen belum ditentukan')
|
|
: 'Kelas belum dibuka'}
|
|
</p>
|
|
</div>
|
|
</label>
|
|
),
|
|
)}
|
|
{selected.map((courseClassId) => (
|
|
<input
|
|
key={courseClassId}
|
|
type="hidden"
|
|
name="course_class_ids[]"
|
|
value={courseClassId}
|
|
/>
|
|
))}
|
|
<InputError message={errors.course_class_ids} />
|
|
</CardContent>
|
|
<CardContent className="border-t pt-6">
|
|
<SignaturePad
|
|
name="signature"
|
|
error={errors.signature}
|
|
/>
|
|
</CardContent>
|
|
<CardFooter className="justify-end">
|
|
<Button
|
|
type="submit"
|
|
disabled={
|
|
processing || selected.length === 0
|
|
}
|
|
>
|
|
{submission?.status === 'rejected'
|
|
? 'Ajukan Ulang KRS'
|
|
: 'Tanda Tangan KRS'}
|
|
</Button>
|
|
</CardFooter>
|
|
</Card>
|
|
)}
|
|
</Form>
|
|
</div>
|
|
</>
|
|
);
|
|
}
|