import { Head, router } from '@inertiajs/react'; import { format } from 'date-fns'; import type { LucideIcon } from 'lucide-react'; import { CircleDollarSign, FileText, Plus, UserRoundX, Wallet, } from 'lucide-react'; import { useState } from 'react'; import type { PaginationState } from '@/components/data-table'; import { DataTable } from '@/components/data-table'; import { DatePicker } from '@/components/date-picker'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; import type { FilterField } 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 { RupiahInput } from '@/components/rupiah-input'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle, } from '@/components/ui/card'; import { Combobox, ComboboxChip, ComboboxChips, ComboboxChipsInput, ComboboxContent, ComboboxEmpty, ComboboxInput, ComboboxItem, ComboboxList, useComboboxAnchor, } from '@/components/ui/combobox'; import { Label } from '@/components/ui/label'; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from '@/components/ui/select'; import { useServerTable } from '@/hooks/use-server-table'; import { formatRupiah } from '@/lib/currency'; import { destroy, store, index as tuitionInvoiceIndex, update, } from '@/routes/admin/finances/tuition-invoices'; import { formatAcademicTermLabel } from '@/types/academic-term'; import type { TuitionInvoice, TuitionInvoiceStudent, } from '@/types/tuition-invoice'; import { PaymentMethodLabels, PaymentMethods, PaymentStatusLabels, PaymentStatuses, } from '@/types/tuition-payment'; import { createTuitionInvoiceColumns } from './columns'; type AcademicTermOption = { id: number; name: string; semester: string }; type InvoiceSummary = { unpaid_students: number; partial_students: number; paid_students: number; total_outstanding: number; total_collected: number; total_invoices: number; }; type Props = { invoices: { data: TuitionInvoice[]; current_page: number; last_page: number; per_page: number; total: number; }; summary: InvoiceSummary; summaryAcademicTerm: AcademicTermOption | null; students: TuitionInvoiceStudent[]; academicTerms: AcademicTermOption[]; highlight?: number; filters: { academic_term_id?: string; status?: string; payment_method?: string; }; }; function studentLabel(student: TuitionInvoiceStudent): string { return `${student.user?.profile?.full_name ?? 'N/A'} - ${student.student_number}`; } export default function TuitionInvoiceIndex({ invoices, summary, summaryAcademicTerm, students, academicTerms, highlight, filters, }: Props) { const [createOpen, setCreateOpen] = useState(false); const [editing, setEditing] = useState(null); const [deleting, setDeleting] = useState(null); const filterFields: FilterField[] = [ { key: 'academic_term_id', label: 'Periode Akademik', options: academicTerms.map((term) => ({ value: String(term.id), label: formatAcademicTermLabel(term), })), }, { key: 'status', label: 'Status Bayar', options: PaymentStatuses.map((status) => ({ value: status, label: PaymentStatusLabels[status], })), }, { key: 'payment_method', label: 'Metode Pembayaran', options: PaymentMethods.map((method) => ({ value: method, label: PaymentMethodLabels[method], })), }, ]; const pagination: PaginationState = { current_page: invoices.current_page, last_page: invoices.last_page, per_page: invoices.per_page, total: invoices.total, }; const { search, handlePageChange, handlePerPageChange, handleSearchChange, applyFilters, } = useServerTable({ route: () => tuitionInvoiceIndex.url(), pagination, filters, }); function handleDelete() { if (!deleting) { return; } router.delete(destroy(deleting.id), { onSuccess: () => setDeleting(null), }); } const columns = createTuitionInvoiceColumns({ handleEdit: (invoice) => setEditing(invoice), handleDeleteClick: (invoice) => setDeleting(invoice), }); return ( <>
Menampilkan tagihan dari notifikasi.

) } actions={ } />

Ringkasan berikut hanya menampilkan periode{' '} {summaryAcademicTerm ? formatAcademicTermLabel(summaryAcademicTerm) : '-'}{' '} dan bisa disesuaikan lewat filter.

{ if (!open) { setEditing(null); } }} editing={editing} students={students} academicTerms={academicTerms} /> } /> { if (!open) { setDeleting(null); } }} title="Hapus Tagihan" description={(invoice) => `Apakah Anda yakin ingin menghapus tagihan untuk "${invoice.student?.user?.profile?.full_name ?? 'mahasiswa ini'}"? Tindakan ini tidak dapat dibatalkan.` } onConfirm={handleDelete} />
); } function SummaryCard({ icon: Icon, label, value, }: { icon: LucideIcon; label: string; value: string; }) { return ( {label} {value} ); } function StudentStatusSummaryCard({ summary }: { summary: InvoiceSummary }) { const items: { label: string; value: number }[] = [ { label: 'Belum Bayar', value: summary.unpaid_students }, { label: 'Sebagian', value: summary.partial_students }, { label: 'Lunas', value: summary.paid_students }, ]; return ( Status Mahasiswa {items.map((item) => (

{item.value.toLocaleString('id-ID')}

{item.label}

))}
); } function CreateForm({ open, onOpenChange, students, academicTerms, }: { open: boolean; onOpenChange: (open: boolean) => void; students: TuitionInvoiceStudent[]; academicTerms: AcademicTermOption[]; }) { const [academicTermId, setAcademicTermId] = useState(''); const [dueDate, setDueDate] = useState(); const [selectedStudents, setSelectedStudents] = useState< TuitionInvoiceStudent[] >([]); const studentAnchor = useComboboxAnchor(); const availableStudents = academicTermId ? students.filter( (student) => !student.invoiced_term_ids?.includes(Number(academicTermId)), ) : []; function reset() { setAcademicTermId(''); setDueDate(undefined); setSelectedStudents([]); } return ( { onOpenChange(false); reset(); }} > {({ errors }) => (
{selectedStudents.map((student) => ( ))} a.id === b.id} > {selectedStudents.map((student) => ( {studentLabel(student)} ))} 0 ? '' : academicTermId ? 'Pilih mahasiswa (aktif)' : 'Pilih periode akademik terlebih dahulu' } /> Mahasiswa tidak ditemukan. {(student: TuitionInvoiceStudent) => ( {studentLabel(student)} )}
)}
); } function EditForm({ open, onOpenChange, editing, students, academicTerms, }: { open: boolean; onOpenChange: (open: boolean) => void; editing: TuitionInvoice | null; students: TuitionInvoiceStudent[]; academicTerms: AcademicTermOption[]; }) { const [dueDate, setDueDate] = useState( editing?.due_date ? new Date(editing.due_date) : undefined, ); const [student, setStudent] = useState( students.find((s) => s.id === editing?.student_id) ?? null, ); return ( onOpenChange(false)} > {({ errors }) => editing && (
a.id === b.id} > Mahasiswa tidak ditemukan. {(option: TuitionInvoiceStudent) => ( {studentLabel(option)} )}
) }
); }