siakad-itm/resources/js/pages/admin/finances/tuition-invoices/columns.tsx
Yoga Pangestu 5994f38f01 feat: implement permission checks for academic terms, courses, departments, and user management
- Added permission checks for creating, updating, and deleting academic terms, courses, and departments.
- Updated the routes to enforce permissions for various actions in the admin panel.
- Enhanced user management by adding permissions for administrators, lecturers, and students.
- Refactored components to conditionally render actions based on user permissions.
- Updated the auth type to include optional permissions array for user roles.
2026-08-30 23:27:31 +07:00

169 lines
6.0 KiB
TypeScript

import type { ColumnDef } from '@tanstack/react-table';
import { format, isBefore, startOfDay } 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 { cn } from '@/lib/utils';
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;
canUpdate: boolean;
canDelete: boolean;
canViewPayments: boolean;
};
export function createTuitionInvoiceColumns(
params: CreateColumnsParams,
): ColumnDef<TuitionInvoice>[] {
const {
handleEdit,
handleDeleteClick,
canUpdate,
canDelete,
canViewPayments,
} = 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 invoice = row.original;
const dueDate = row.getValue('due_date') as string | null;
if (!dueDate) {
return '-';
}
const paidTotal = Number(invoice.paid_total ?? 0);
const amountDue = Number(invoice.amount_due);
const isOverdue =
paidTotal < amountDue &&
isBefore(new Date(dueDate), startOfDay(new Date()));
return (
<span
className={cn(
isOverdue && 'font-medium text-destructive',
)}
>
{format(new Date(dueDate), 'd MMM yyyy')}
</span>
);
},
},
{
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" />,
show: canViewPayments,
href: paymentsIndex.url(invoice.id),
},
{
label: 'Edit',
icon: <Pencil className="h-4 w-4" />,
show: canUpdate,
onClick: () => handleEdit(invoice),
},
{
label: 'Hapus',
icon: (
<Trash2 className="h-4 w-4 text-destructive" />
),
show: canDelete,
onClick: () => handleDeleteClick(invoice),
},
]}
/>
);
},
},
];
}