- 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.
169 lines
6.0 KiB
TypeScript
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} ·{' '}
|
|
{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),
|
|
},
|
|
]}
|
|
/>
|
|
);
|
|
},
|
|
},
|
|
];
|
|
}
|