siakad-itm/resources/js/pages/admin/finances/tuition-invoices/columns.tsx
Yoga Pangestu c4dbf54e4c
Some checks failed
tests / ci (pull_request) Has been cancelled
feat: enhance Tuition Invoice management with academic term formatting and validation
2026-08-30 15:22:32 +07:00

137 lines
5.0 KiB
TypeScript

import type { ColumnDef } from '@tanstack/react-table';
import { format } from 'date-fns';
import { CreditCard, Pencil, Trash2 } from 'lucide-react';
import { RowActions } from '@/components/row-actions';
import { Badge } from '@/components/ui/badge';
import { formatRupiah } from '@/lib/currency';
import { index as paymentsIndex } from '@/routes/admin/finances/tuition-invoices/payments';
import { formatAcademicTermLabel } from '@/types/academic-term';
import type { TuitionInvoice } from '@/types/tuition-invoice';
export type { TuitionInvoice } from '@/types/tuition-invoice';
type CreateColumnsParams = {
handleEdit: (invoice: TuitionInvoice) => void;
handleDeleteClick: (invoice: TuitionInvoice) => void;
};
export function createTuitionInvoiceColumns(
params: CreateColumnsParams,
): ColumnDef<TuitionInvoice>[] {
const { handleEdit, handleDeleteClick } = params;
return [
{
accessorKey: 'student.student_number',
header: () => <span>Mahasiswa</span>,
cell: ({ row }) => {
const student = row.original.student;
return (
<div>
<p className="font-medium">
{student?.user?.profile?.full_name ?? 'N/A'}
</p>
<p className="text-xs text-muted-foreground">
{student?.student_number} &middot;{' '}
{student?.department?.name ?? '-'}
</p>
</div>
);
},
},
{
accessorKey: 'academic_term.name',
header: () => <span>Periode</span>,
cell: ({ row }) => {
const term = row.original.academic_term;
return term ? formatAcademicTermLabel(term) : '-';
},
},
{
accessorKey: 'amount_due',
header: () => <span>Tagihan</span>,
cell: ({ row }) => formatRupiah(row.getValue('amount_due')),
},
{
accessorKey: 'paid_total',
header: () => <span>Status Bayar</span>,
cell: ({ row }) => {
const invoice = row.original;
const paidTotal = Number(invoice.paid_total ?? 0);
const amountDue = Number(invoice.amount_due);
const variant =
paidTotal >= amountDue && amountDue > 0
? 'default'
: paidTotal > 0
? 'secondary'
: 'outline';
const label =
paidTotal >= amountDue && amountDue > 0
? 'Lunas'
: paidTotal > 0
? 'Sebagian'
: 'Belum Bayar';
return (
<div className="flex flex-col gap-1">
<Badge variant={variant} className="w-fit">
{label}
</Badge>
<span className="text-xs text-muted-foreground">
{formatRupiah(paidTotal)} /{' '}
{formatRupiah(amountDue)}
</span>
</div>
);
},
},
{
accessorKey: 'due_date',
header: () => <span>Jatuh Tempo</span>,
cell: ({ row }) => {
const dueDate = row.getValue('due_date') as string | null;
return dueDate ? format(new Date(dueDate), 'd MMM yyyy') : '-';
},
},
{
id: 'actions',
header: () => <span className="block text-center">Aksi</span>,
meta: {
className: 'w-[130px] text-center',
headerClassName: 'w-[130px] text-center',
},
cell: ({ row }) => {
const invoice = row.original;
return (
<RowActions
actions={[
{
label: 'Pembayaran',
icon: <CreditCard className="h-4 w-4" />,
href: paymentsIndex.url(invoice.id),
},
{
label: 'Edit',
icon: <Pencil className="h-4 w-4" />,
onClick: () => handleEdit(invoice),
},
{
label: 'Hapus',
icon: (
<Trash2 className="h-4 w-4 text-destructive" />
),
onClick: () => handleDeleteClick(invoice),
},
]}
/>
);
},
},
];
}