143 lines
5.7 KiB
TypeScript
143 lines
5.7 KiB
TypeScript
import { Head, Link } from '@inertiajs/react';
|
|
import type { FilterField } from '@/components/filter-dialog';
|
|
import { FilterDialog } from '@/components/filter-dialog';
|
|
import { PageHeader } from '@/components/page-header';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
|
import { useServerTable } from '@/hooks/use-server-table';
|
|
import {
|
|
index as attendanceIndex,
|
|
show,
|
|
} from '@/routes/admin/academic-classes/attendances';
|
|
import { formatAcademicTermLabel } from '@/types/academic-term';
|
|
import type { AttendanceCourseClassOverview } from '@/types/attendance';
|
|
|
|
type AcademicTermOption = {
|
|
id: number;
|
|
academic_year: string;
|
|
semester: string;
|
|
};
|
|
|
|
type Props = {
|
|
courseClasses: AttendanceCourseClassOverview[];
|
|
academicTerms: AcademicTermOption[];
|
|
filters: {
|
|
academic_term_id?: string;
|
|
};
|
|
};
|
|
|
|
function courseClassLabel(courseClass: AttendanceCourseClassOverview): string {
|
|
return `${courseClass.course?.code ?? ''} ${courseClass.course?.name ?? ''}`;
|
|
}
|
|
|
|
export default function AttendanceIndex({
|
|
courseClasses,
|
|
academicTerms,
|
|
filters,
|
|
}: Props) {
|
|
const filterFields: FilterField[] = [
|
|
{
|
|
key: 'academic_term_id',
|
|
label: 'Periode Akademik',
|
|
options: academicTerms.map((term) => ({
|
|
value: String(term.id),
|
|
label: formatAcademicTermLabel(term),
|
|
})),
|
|
},
|
|
];
|
|
|
|
const { applyFilters } = useServerTable({
|
|
route: () => attendanceIndex.url(),
|
|
pagination: {
|
|
current_page: 1,
|
|
last_page: 1,
|
|
per_page: 999999,
|
|
total: courseClasses.length,
|
|
},
|
|
filters,
|
|
});
|
|
|
|
function handleApplyFilters(newFilters: Record<string, string>) {
|
|
// Tanpa `academic_term_id` eksplisit, backend akan kembali ke
|
|
// periode aktif, jadi menghapus filter ini perlu dikirim eksplisit
|
|
// alih-alih hanya menghilangkan key-nya.
|
|
const clearedAcademicTerm =
|
|
Boolean(filters.academic_term_id) && !newFilters.academic_term_id;
|
|
|
|
applyFilters({
|
|
...newFilters,
|
|
...(clearedAcademicTerm ? { academic_term_id: '' } : {}),
|
|
});
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Head title="Kehadiran" />
|
|
|
|
<div className="flex h-full flex-1 flex-col gap-6 p-4 md:p-6">
|
|
<PageHeader
|
|
title="Kehadiran"
|
|
actions={
|
|
<FilterDialog
|
|
fields={filterFields}
|
|
activeFilters={filters}
|
|
onApply={handleApplyFilters}
|
|
/>
|
|
}
|
|
/>
|
|
|
|
{courseClasses.length === 0 ? (
|
|
<p className="rounded-md border border-dashed p-6 text-center text-sm text-muted-foreground">
|
|
Tidak ada kelas untuk periode ini.
|
|
</p>
|
|
) : (
|
|
<div className="grid grid-cols-1 gap-4 sm:grid-cols-2 xl:grid-cols-3">
|
|
{courseClasses.map((courseClass) => (
|
|
<Link
|
|
key={courseClass.id}
|
|
href={show(courseClass.id).url}
|
|
>
|
|
<Card className="h-full transition-colors hover:border-primary">
|
|
<CardHeader>
|
|
<CardTitle className="text-base leading-tight">
|
|
{courseClassLabel(courseClass)}
|
|
</CardTitle>
|
|
</CardHeader>
|
|
<CardContent className="flex flex-col gap-2">
|
|
<div className="flex flex-wrap items-center gap-1.5">
|
|
<Badge
|
|
variant="secondary"
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{courseClass.meetings_count}{' '}
|
|
Pertemuan
|
|
</Badge>
|
|
<Badge
|
|
variant="outline"
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{courseClass.enrollments_count}{' '}
|
|
Mahasiswa
|
|
</Badge>
|
|
{courseClass.academic_term && (
|
|
<Badge
|
|
variant="outline"
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{formatAcademicTermLabel(
|
|
courseClass.academic_term,
|
|
)}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
</Link>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</>
|
|
);
|
|
}
|