226 lines
9.4 KiB
TypeScript
226 lines
9.4 KiB
TypeScript
import { format } from 'date-fns';
|
|
import {
|
|
Clock,
|
|
ClipboardList,
|
|
Pencil,
|
|
Trash2,
|
|
Upload,
|
|
User,
|
|
} from 'lucide-react';
|
|
import { AttachmentPreviewDialog } from '@/components/attachment-preview-dialog';
|
|
import { RowActions } from '@/components/row-actions';
|
|
import {
|
|
Accordion,
|
|
AccordionContent,
|
|
AccordionItem,
|
|
AccordionTrigger,
|
|
} from '@/components/ui/accordion';
|
|
import { Badge } from '@/components/ui/badge';
|
|
import { index as submissionsIndex } from '@/routes/admin/academic-classes/assignments/submissions';
|
|
import { formatAcademicTermLabel } from '@/types/academic-term';
|
|
import type { Assignment } from '@/types/assignment';
|
|
import { AssignmentStatusLabels } from '@/types/assignment';
|
|
import { deadlineTextClass, percentageOf } from './utils';
|
|
|
|
type CreateCardParams = {
|
|
handleEdit: (assignment: Assignment) => void;
|
|
handleDeleteClick: (assignment: Assignment) => void;
|
|
handleSubmitClick: (assignment: Assignment) => void;
|
|
canUpdate: boolean;
|
|
canDelete: boolean;
|
|
canSubmit: boolean;
|
|
canViewSubmissions: boolean;
|
|
};
|
|
|
|
export function createAssignmentCard(params: CreateCardParams) {
|
|
const {
|
|
handleEdit,
|
|
handleDeleteClick,
|
|
handleSubmitClick,
|
|
canUpdate,
|
|
canDelete,
|
|
canSubmit,
|
|
canViewSubmissions,
|
|
} = params;
|
|
|
|
return function AssignmentCard(assignment: Assignment) {
|
|
const mySubmission = assignment.submissions?.[0];
|
|
const lecturerName =
|
|
assignment.course_class?.lecturer?.user?.profile?.full_name ??
|
|
null;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-2">
|
|
<div className="flex items-start justify-between gap-2">
|
|
<div className="flex flex-col gap-1">
|
|
<p className="text-base leading-tight font-medium">
|
|
{assignment.title}
|
|
</p>
|
|
{assignment.course_class && (
|
|
<span className="text-xs text-muted-foreground">
|
|
{assignment.course_class.course?.code ?? ''}{' '}
|
|
{assignment.course_class.course?.name ?? ''}
|
|
</span>
|
|
)}
|
|
{lecturerName && (
|
|
<span className="flex items-center gap-1 text-xs text-muted-foreground">
|
|
<User className="h-3 w-3 shrink-0" />
|
|
{lecturerName}
|
|
</span>
|
|
)}
|
|
</div>
|
|
<RowActions
|
|
wrapperClassName="-mt-1 -mr-2 flex items-center gap-1"
|
|
actions={[
|
|
{
|
|
label:
|
|
mySubmission?.status === 'submitted'
|
|
? 'Kumpulkan Ulang'
|
|
: 'Kumpulkan Tugas',
|
|
icon: <Upload className="h-3.5 w-3.5" />,
|
|
show:
|
|
canSubmit && assignment.status === 'open',
|
|
onClick: () => handleSubmitClick(assignment),
|
|
},
|
|
{
|
|
label: 'Pengumpulan',
|
|
icon: (
|
|
<ClipboardList className="h-3.5 w-3.5" />
|
|
),
|
|
show: canViewSubmissions,
|
|
href: submissionsIndex.url(assignment.id),
|
|
},
|
|
{
|
|
label: 'Edit',
|
|
icon: <Pencil className="h-3.5 w-3.5" />,
|
|
show: canUpdate,
|
|
onClick: () => handleEdit(assignment),
|
|
},
|
|
{
|
|
label: 'Hapus',
|
|
icon: (
|
|
<Trash2 className="h-3.5 w-3.5 text-destructive" />
|
|
),
|
|
show: canDelete,
|
|
onClick: () => handleDeleteClick(assignment),
|
|
},
|
|
]}
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex flex-wrap items-center gap-1.5 text-xs text-muted-foreground">
|
|
<Badge
|
|
variant={
|
|
assignment.status === 'open'
|
|
? 'secondary'
|
|
: 'destructive'
|
|
}
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{AssignmentStatusLabels[assignment.status]}
|
|
</Badge>
|
|
{assignment.course_class?.academic_term && (
|
|
<Badge
|
|
variant="outline"
|
|
className="w-fit text-[10px] font-normal"
|
|
>
|
|
{formatAcademicTermLabel(
|
|
assignment.course_class.academic_term,
|
|
)}
|
|
</Badge>
|
|
)}
|
|
</div>
|
|
|
|
<span
|
|
className={`inline-flex items-center gap-1.5 text-xs ${deadlineTextClass(assignment.deadline)}`}
|
|
>
|
|
<Clock className="h-3.5 w-3.5 shrink-0" />
|
|
{format(new Date(assignment.deadline), 'd MMM yyyy, HH:mm')}
|
|
</span>
|
|
|
|
{assignment.attachment_url && assignment.attachment_name ? (
|
|
<div className="text-xs text-muted-foreground">
|
|
<AttachmentPreviewDialog
|
|
fileUrl={assignment.attachment_url}
|
|
fileName={assignment.attachment_name}
|
|
/>
|
|
</div>
|
|
) : null}
|
|
|
|
<div className="flex flex-wrap items-center gap-1.5 pt-1">
|
|
{canSubmit ? (
|
|
<Badge
|
|
variant={
|
|
mySubmission?.status === 'submitted'
|
|
? 'default'
|
|
: 'secondary'
|
|
}
|
|
>
|
|
{mySubmission?.status === 'submitted'
|
|
? 'Sudah Mengumpulkan'
|
|
: 'Belum Mengumpulkan'}
|
|
</Badge>
|
|
) : (
|
|
<>
|
|
<Badge variant="secondary">
|
|
{assignment.submissions_count.toLocaleString(
|
|
'id-ID',
|
|
)}{' '}
|
|
/{' '}
|
|
{(
|
|
assignment.course_class
|
|
?.enrollments_count ?? 0
|
|
).toLocaleString('id-ID')}{' '}
|
|
Pengumpulan (
|
|
{percentageOf(
|
|
assignment.submissions_count,
|
|
assignment.course_class
|
|
?.enrollments_count ?? 0,
|
|
)}
|
|
%)
|
|
</Badge>
|
|
<Badge variant="outline">
|
|
{assignment.graded_submissions_count.toLocaleString(
|
|
'id-ID',
|
|
)}{' '}
|
|
/{' '}
|
|
{assignment.submissions_count.toLocaleString(
|
|
'id-ID',
|
|
)}{' '}
|
|
Dinilai (
|
|
{percentageOf(
|
|
assignment.graded_submissions_count,
|
|
assignment.submissions_count,
|
|
)}
|
|
%)
|
|
</Badge>
|
|
</>
|
|
)}
|
|
</div>
|
|
|
|
{assignment.description && (
|
|
<Accordion
|
|
type="single"
|
|
collapsible
|
|
className="-mx-6 -mb-6 border-t"
|
|
>
|
|
<AccordionItem
|
|
value="description"
|
|
className="border-b-0"
|
|
>
|
|
<AccordionTrigger className="px-6 text-xs font-medium text-foreground hover:no-underline">
|
|
Deskripsi
|
|
</AccordionTrigger>
|
|
<AccordionContent className="px-6">
|
|
<p className="text-sm whitespace-pre-line text-foreground">
|
|
{assignment.description}
|
|
</p>
|
|
</AccordionContent>
|
|
</AccordionItem>
|
|
</Accordion>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
}
|