151 lines
8.5 KiB
TypeScript
151 lines
8.5 KiB
TypeScript
import { Head } from '@inertiajs/react';
|
|
import { format } from 'date-fns';
|
|
import { PageHeader } from '@/components/page-header';
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from '@/components/ui/accordion';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { formatAcademicTermLabel } from '@/types/academic-term';
|
|
import type { AttendanceClassSummary } from '@/types/attendance';
|
|
import { AttendanceStatusLabels } from '@/types/attendance';
|
|
|
|
type Props = {
|
|
summaries: AttendanceClassSummary[];
|
|
};
|
|
|
|
function courseClassLabel(
|
|
courseClass: AttendanceClassSummary['course_class'],
|
|
): string {
|
|
return `${courseClass.course?.code ?? ''} ${courseClass.course?.name ?? ''}`;
|
|
}
|
|
|
|
function percentageOf(part: number, total: number): number {
|
|
return total > 0 ? Math.round((part / total) * 100) : 0;
|
|
}
|
|
|
|
export default function MyAttendance({ summaries }: Props) {
|
|
return (
|
|
<>
|
|
<Head title="Riwayat Kehadiran" />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-6 p-4 md:p-6">
|
|
<PageHeader title="Riwayat Kehadiran" />
|
|
|
|
{summaries.length === 0 ? (
|
|
<p className="rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground">
|
|
Belum ada kelas yang terdaftar.
|
|
</p>
|
|
) : (
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
|
{summaries.map((summary) => (
|
|
<Card key={summary.course_class.id}>
|
|
<CardHeader>
|
|
<CardTitle className="text-base leading-tight">
|
|
{courseClassLabel(summary.course_class)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-2">
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<Badge
|
|
variant={
|
|
percentageOf(
|
|
summary.present_count,
|
|
summary.total_count,
|
|
) >= 75
|
|
? 'secondary'
|
|
: 'destructive'
|
|
}
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
Hadir {summary.present_count} /{' '}
|
|
{summary.total_count} (
|
|
{percentageOf(
|
|
summary.present_count,
|
|
summary.total_count,
|
|
)}
|
|
%)
|
|
</Badge>
|
|
{summary.course_class.academic_term && (
|
|
<Badge
|
|
variant="outline"
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{formatAcademicTermLabel(
|
|
summary.course_class
|
|
.academic_term,
|
|
)}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
{summary.records.length > 0 && (
|
|
<Accordion
|
|
type="single"
|
|
collapsible
|
|
className="-mx-6 -mb-6 border-t"
|
|
>
|
|
<AccordionItem
|
|
value="records"
|
|
className="border-b-0"
|
|
>
|
|
<AccordionTrigger className="px-6 text-xs font-medium text-foreground hover:no-underline">
|
|
Detail Pertemuan
|
|
</AccordionTrigger>
|
|
<AccordionContent className="px-6">
|
|
<div className="flex flex-col divide-y">
|
|
{summary.records.map(
|
|
(record) => (
|
|
<div
|
|
key={`${record.course_class_id}-${record.meeting_number}`}
|
|
className="flex items-center justify-between gap-2 py-2 text-sm"
|
|
>
|
|
<span className="text-muted-foreground">
|
|
Pertemuan
|
|
ke-
|
|
{record.meeting_number ??
|
|
'-'}{' '}
|
|
·{' '}
|
|
{format(
|
|
new Date(
|
|
record.date,
|
|
),
|
|
'd MMM yyyy',
|
|
)}
|
|
</span>
|
|
<Badge
|
|
variant={
|
|
record.status ===
|
|
'present'
|
|
? 'default'
|
|
: 'secondary'
|
|
}
|
|
>
|
|
{
|
|
AttendanceStatusLabels[
|
|
record
|
|
.status
|
|
]
|
|
}
|
|
</Badge>
|
|
</div>
|
|
),
|
|
)}
|
|
</div>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
)}
|
|
</CardContent>
|
|
</Card>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|