import { Form, Head, Link, router } from '@inertiajs/react';
import { format } from 'date-fns';
import { ArrowLeft, Check, Send, X } from 'lucide-react';
import { useState } from 'react';
import { FormDialog } from '@/components/form-dialog';
import InputError from '@/components/input-error';
import { PageHeader } from '@/components/page-header';
import { SignaturePad } from '@/components/signature-pad';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Checkbox } from '@/components/ui/checkbox';
import { Label } from '@/components/ui/label';
import {
Table,
TableBody,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import { Tabs, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Textarea } from '@/components/ui/textarea';
import {
advisor_signature,
approve,
kaprodi_signature,
reject,
save,
} from '@/routes/admin/manage/course-registrations';
import type {
CourseRegistrationSubmission,
CourseRegistrationSubmissionEntry,
CourseRegistrationSubmissionLog,
} from '@/types/course-registration';
import {
RegistrationStatusLabels,
semesterKey,
} from '@/types/course-registration';
const RegistrationStatusVariants: Record<
CourseRegistrationSubmission['status'],
'default' | 'secondary' | 'destructive' | 'outline'
> = {
submitted: 'outline',
approved: 'default',
rejected: 'destructive',
};
function InfoRow({ label, value }: { label: string; value: React.ReactNode }) {
return (
{label}
: {value}
);
}
function SignatureBox({
signatureUrl,
alt,
error,
editable,
}: {
signatureUrl: string | null;
alt: string;
error?: string;
editable: boolean;
}) {
if (!editable) {
if (!signatureUrl) {
return ;
}
return (
);
}
return ;
}
function ReviewerSignatureField({
signatureUrl,
alt,
canSign,
submitUrl,
}: {
signatureUrl: string | null;
alt: string;
canSign: boolean;
submitUrl: string;
}) {
const [file, setFile] = useState(null);
const [processing, setProcessing] = useState(false);
if (signatureUrl) {
return (
);
}
if (!canSign) {
return ;
}
function handleSave() {
if (!file) {
return;
}
setProcessing(true);
router.patch(
submitUrl,
{ signature: file },
{
preserveScroll: true,
onFinish: () => setProcessing(false),
},
);
}
return (
);
}
function LogBubble({
log,
isResubmission,
}: {
log: CourseRegistrationSubmissionLog;
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 (
{message}
{format(new Date(log.created_at), 'd MMM yyyy, HH:mm')}
);
}
function RejectDialog({
open,
onOpenChange,
submissionId,
}: {
open: boolean;
onOpenChange: (open: boolean) => void;
submissionId: number;
}) {
return (
onOpenChange(false)}
>
{({ errors }) => (
)}
);
}
function SemesterCard({
semester,
submission,
studentId,
isSemesterOpen,
backHref,
canReview,
canSignAsKaprodi,
canSignAsAdvisor,
}: {
semester: number | null;
submission: CourseRegistrationSubmission;
studentId: number;
isSemesterOpen: boolean;
backHref: string | null;
canReview: boolean;
canSignAsKaprodi: boolean;
canSignAsAdvisor: boolean;
}) {
const [rejecting, setRejecting] = useState(false);
const student = submission.student;
const department = student?.department ?? null;
const kaprodi = department?.current_leader?.lecturer ?? null;
const advisor = student?.academic_advisor ?? null;
const isViewOnly = backHref !== null;
const notYetSubmitted =
submission.status === 'submitted' && !submission.has_registrations;
const isLocked =
submission.status === 'approved' ||
(submission.status === 'submitted' && submission.has_registrations);
const canDecide =
canReview && submission.status === 'submitted' && !notYetSubmitted;
const canReviewersSign =
submission.status === 'submitted' && !notYetSubmitted;
const missingKaprodiSignature = !submission.kaprodi_signature_url;
const missingOwnSignature =
(canSignAsKaprodi && !submission.kaprodi_signature_url) ||
(canSignAsAdvisor && !submission.advisor_signature_url);
const cannotApprove = missingKaprodiSignature || missingOwnSignature;
const totalCredits = submission.course_registrations.reduce(
(sum, registration) =>
submission.has_registrations
? registration.is_registered
? sum + (registration.course_class?.course?.credits ?? 0)
: sum
: isSemesterOpen
? sum + (registration.course_class?.course?.credits ?? 0)
: sum,
0,
);
function handleApprove() {
router.patch(approve.url(submission.id), {}, { preserveScroll: true });
}
return (
);
}
type Props = {
submissions: Record;
semesters: (number | null)[];
openSemesters: number[];
backHref: string | null;
canReview: boolean;
canSignAsKaprodi: boolean;
canSignAsAdvisor: boolean;
};
export default function CourseRegistrationDetail({
submissions,
semesters,
openSemesters,
backHref,
canReview,
canSignAsKaprodi,
canSignAsAdvisor,
}: Props) {
const [activeSemester, setActiveSemester] = useState(() =>
semesterKey(semesters[0] ?? null),
);
const currentKey = semesters.some(
(semester) => semesterKey(semester) === activeSemester,
)
? activeSemester
: semesterKey(semesters[0] ?? null);
const currentSemester =
semesters.find((semester) => semesterKey(semester) === currentKey) ??
null;
const currentSubmission = submissions[currentKey];
const studentId = Object.values(submissions)[0]?.student?.id ?? 0;
return (
<>
Kembali
)
}
/>
{semesters.length > 0 && (
{semesters.map((semester) => (
{semester !== null
? `Semester ${semester}`
: 'Lainnya'}
{!openSemesters.includes(semester ?? -1) &&
' (Ditutup)'}
))}
)}
{currentSubmission && (
)}
>
);
}