import { Head, router } from '@inertiajs/react'; import { Plus } from 'lucide-react'; import { useState } from 'react'; import { DataCards } from '@/components/data-cards'; import type { PaginationState } from '@/components/data-table'; import { DataTable } from '@/components/data-table'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; import { FileUploadField } from '@/components/file-upload-field'; import type { FilterOptionGroup } from '@/components/filter-dialog'; import { FilterDialog } from '@/components/filter-dialog'; import { FormDialog } from '@/components/form-dialog'; import InputError from '@/components/input-error'; import { PageHeader } from '@/components/page-header'; import { Button } from '@/components/ui/button'; import { Combobox, ComboboxCollection, ComboboxContent, ComboboxEmpty, ComboboxGroup, ComboboxInput, ComboboxItem, ComboboxLabel, ComboboxList, } from '@/components/ui/combobox'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Textarea } from '@/components/ui/textarea'; import type { ViewMode } from '@/components/view-toggle'; import { ViewToggle } from '@/components/view-toggle'; import { usePermissions } from '@/hooks/use-permissions'; import { useServerTable } from '@/hooks/use-server-table'; import { index as materialIndex, destroy, store, update, } from '@/routes/admin/academic-classes/materials'; import { formatAcademicTermLabel } from '@/types/academic-term'; import type { Material } from '@/types/material'; import { createMaterialCard } from './card'; import { createMaterialColumns } from './columns'; type CourseClassOption = { id: number; course: { id: number; code: string; name: string; semester_number: number | null; department: { id: number; name: string } | null; } | null; }; type CourseClassGroup = { value: string; items: CourseClassOption[] }; type AcademicTermOption = { id: number; academic_year: string; semester: string; }; type Props = { materials: { data: Material[]; current_page: number; last_page: number; per_page: number; total: number; }; courseClasses: CourseClassOption[]; filterCourseClasses: CourseClassOption[]; academicTerms: AcademicTermOption[]; highlight?: number; filters: { course_class_id?: string; academic_term_id?: string; }; }; function courseClassLabel(courseClass: CourseClassOption): string { return `${courseClass.course?.code ?? ''} - ${courseClass.course?.name ?? ''}`; } function groupCourseClassesByDepartment( options: CourseClassOption[], ): CourseClassGroup[] { const groups: CourseClassGroup[] = []; let currentKey: string | null = null; for (const option of options) { const key = `${option.course?.department?.name ?? 'Tanpa Jurusan'} — Semester ${option.course?.semester_number ?? 'Tidak ditentukan'}`; if (key !== currentKey) { currentKey = key; groups.push({ value: key, items: [] }); } groups[groups.length - 1].items.push(option); } return groups; } function CourseClassField({ courseClasses, value, onChange, }: { courseClasses: CourseClassOption[]; value: CourseClassOption | null; onChange: (value: CourseClassOption | null) => void; }) { const groups = groupCourseClassesByDepartment(courseClasses); return ( a.id === b.id} > Kelas tidak ditemukan. {(group: CourseClassGroup) => ( {group.value} {(option: CourseClassOption) => ( {courseClassLabel(option)} )} )} ); } function courseClassFilterGroups( courseClasses: CourseClassOption[], ): FilterOptionGroup[] { return groupCourseClassesByDepartment(courseClasses).map((group) => ({ label: group.value, options: group.items.map((option) => ({ value: String(option.id), label: courseClassLabel(option), })), })); } export default function MaterialIndex({ materials, courseClasses, filterCourseClasses, academicTerms, highlight, filters, }: Props) { const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); const [view, setView] = useState('table'); const { hasPermission } = usePermissions(); const canCreate = hasPermission('create-materials'); const canUpdate = hasPermission('update-materials'); const canDelete = hasPermission('delete-materials'); const filterFields = [ { key: 'academic_term_id', label: 'Periode Akademik', options: academicTerms.map((term) => ({ value: String(term.id), label: formatAcademicTermLabel(term), })), }, { key: 'course_class_id', label: 'Kelas', type: 'combobox' as const, groups: courseClassFilterGroups(filterCourseClasses), disabled: filterCourseClasses.length === 0, placeholder: filterCourseClasses.length === 0 ? 'Pilih periode akademik dulu' : undefined, }, ]; const pagination: PaginationState = { current_page: materials.current_page, last_page: materials.last_page, per_page: materials.per_page, total: materials.total, }; const { search, handleSearchChange, applyFilters } = useServerTable({ route: () => materialIndex.url(), pagination, filters, resetKeys: ['materials'], }); function handleApplyFilters(newFilters: Record) { const clearedAcademicTerm = Boolean(filters.academic_term_id) && !newFilters.academic_term_id; const nextFilters: Record = { ...newFilters, ...(clearedAcademicTerm ? { academic_term_id: '0' } : {}), }; // The class list depends on the selected term, so a previously // picked class is no longer relevant once the term changes. const academicTermChanged = (nextFilters.academic_term_id ?? '0') !== (filters.academic_term_id ?? '0'); if (academicTermChanged) { delete nextFilters.course_class_id; } applyFilters(nextFilters); } // '0' explicitly means "all terms" — treated as no active filter from // FilterDialog's point of view, so its Select shows "Semua" (not blank) // as selected. const filterDialogActiveFilters = { ...filters, academic_term_id: filters.academic_term_id === '0' ? undefined : filters.academic_term_id, }; function handleDelete() { if (!deleting) { return; } router.delete(destroy(deleting.id), { onSuccess: () => setDeleting(null), }); } const columns = createMaterialColumns({ handleEdit: (material) => setEditing(material), handleDeleteClick: (material) => setDeleting(material), canUpdate, canDelete, }); const renderMaterialCard = createMaterialCard({ handleEdit: (material) => setEditing(material), handleDeleteClick: (material) => setDeleting(material), canUpdate, canDelete, }); const toolbar = ( <> ); return ( <>
Menampilkan materi dari notifikasi.

) } actions={ canCreate && ( ) } /> { if (!open) { setEditing(null); } }} editing={editing} courseClasses={courseClasses} /> {view === 'table' ? ( ) : ( material.id} searchKey="title" pagination={pagination} onSearchChange={handleSearchChange} searchValue={search} infiniteScroll={{ propName: 'materials' }} toolbar={toolbar} /> )} { if (!open) { setDeleting(null); } }} title="Hapus Materi" description={(material) => `Apakah Anda yakin ingin menghapus materi "${material.title}"? Tindakan ini tidak dapat dibatalkan.` } onConfirm={handleDelete} />
); } function CreateForm({ open, onOpenChange, courseClasses, }: { open: boolean; onOpenChange: (open: boolean) => void; courseClasses: CourseClassOption[]; }) { const [courseClass, setCourseClass] = useState( null, ); return ( { onOpenChange(false); setCourseClass(null); }} > {({ errors }) => (