siakad-itm/resources/js/pages/admin/manage/course-registrations/show.tsx
Yoga Pangestu 8336504032
Some checks failed
tests / ci (pull_request) Has been cancelled
feat: add course registration management for admin and student
- 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.
2026-08-30 20:50:27 +07:00

185 lines
7.5 KiB
TypeScript

import { Head, Link } from '@inertiajs/react';
import { format } from 'date-fns';
import { ArrowLeft } from 'lucide-react';
import { PageHeader } from '@/components/page-header';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { index as courseRegistrationIndex } from '@/routes/admin/manage/course-registrations';
import { formatAcademicTermLabel } from '@/types/academic-term';
import type { CourseRegistrationSubmission } from '@/types/course-registration';
import { RegistrationStatusLabels } from '@/types/course-registration';
type Props = {
submission: CourseRegistrationSubmission;
};
export default function CourseRegistrationShow({ submission }: Props) {
const student = submission.student;
return (
<>
<Head title="Detail Registrasi KRS" />
<div className="flex h-full flex-1 flex-col gap-6 overflow-x-auto p-4 md:p-6">
<PageHeader
title="Detail Registrasi KRS"
actions={
<Button variant="outline" asChild>
<Link href={courseRegistrationIndex.url()}>
<ArrowLeft className="h-4 w-4" />
Kembali
</Link>
</Button>
}
/>
<Card>
<CardHeader className="flex flex-row items-start justify-between gap-4">
<div>
<CardTitle>
{student?.user?.profile?.full_name ?? 'N/A'}
</CardTitle>
<p className="mt-1 text-sm text-muted-foreground">
{student?.student_number} &middot;{' '}
{student?.department?.name ?? '-'} &middot;
Semester {student?.current_semester ?? '-'}
</p>
<p className="mt-1 text-sm text-muted-foreground">
Periode:{' '}
{submission.academic_term
? formatAcademicTermLabel(
submission.academic_term,
)
: '-'}
</p>
</div>
<Badge
variant={
submission.status === 'approved'
? 'default'
: submission.status === 'rejected'
? 'destructive'
: 'outline'
}
>
{RegistrationStatusLabels[submission.status]}
</Badge>
</CardHeader>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">
Tanda Tangan
</CardTitle>
</CardHeader>
<CardContent>
{submission.signature_url ? (
<div className="w-fit rounded-md border bg-white p-2">
<img
src={submission.signature_url}
alt="Tanda tangan"
className="h-24"
/>
</div>
) : (
<p className="text-sm text-muted-foreground">
Tidak ada tanda tangan.
</p>
)}
<p className="mt-2 text-xs text-muted-foreground">
Ditandatangani pada{' '}
{format(
new Date(submission.signed_at),
'd MMM yyyy, HH:mm',
)}
</p>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">
Mata Kuliah (
{submission.course_registrations.length})
</CardTitle>
</CardHeader>
<CardContent className="grid gap-2">
{submission.course_registrations.map((registration) => (
<div
key={registration.id}
className="rounded-md border p-3"
>
<p className="font-medium">
{registration.course_class?.course?.name ??
'N/A'}
</p>
<p className="text-xs text-muted-foreground">
{registration.course_class?.course?.code ??
'-'}
</p>
</div>
))}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="text-base">Riwayat</CardTitle>
</CardHeader>
<CardContent className="grid gap-2">
{submission.logs.map((log, index) => (
<LogBubble
key={log.id}
log={log}
isResubmission={
log.status === 'submitted' &&
submission.logs
.slice(0, index)
.some((l) => l.status === 'submitted')
}
/>
))}
</CardContent>
</Card>
</div>
</>
);
}
function LogBubble({
log,
isResubmission,
}: {
log: CourseRegistrationSubmission['logs'][number];
isResubmission: boolean;
}) {
const actorName = log.actor?.profile?.full_name ?? 'Sistem';
const message =
log.status === 'approved'
? `${actorName} menyetujui KRS ini.`
: log.status === 'rejected'
? `${actorName} menolak KRS ini dengan alasan: "${log.reason}"`
: isResubmission
? `${actorName} mengajukan ulang KRS.`
: `${actorName} mengajukan KRS.`;
const colorClasses =
log.status === 'approved'
? 'border-primary/30 bg-primary/10'
: log.status === 'rejected'
? 'border-destructive/30 bg-destructive/10'
: 'border-border bg-muted';
return (
<div className={`rounded-lg border px-3 py-2 ${colorClasses}`}>
<p className="text-sm">{message}</p>
<p className="mt-1 text-xs text-muted-foreground">
{format(new Date(log.created_at), 'd MMM yyyy, HH:mm')}
</p>
</div>
);
}