import type { ColumnDef } from '@tanstack/react-table'; import { CircleDollarSign, Pencil, Trash2, XCircle } from 'lucide-react'; import { RowActions } from '@/components/data-display'; import { Badge } from '@/components/ui/badge'; import { formatCurrency } from '@/lib/utils'; export type Payroll = { id: number; base_salary: number; formatted_base_salary: string; bonus_amount: number; formatted_bonus_amount: string; deduction_amount: number; formatted_deduction_amount: string; total_amount: number; formatted_total_amount: string; status: 'unpaid' | 'paid' | 'cancelled'; paid_at: string | null; employee: { user: { user_profile: { full_name: string; }; }; }; payroll_adjustments?: PayrollAdjustment[]; }; export type PayrollAdjustment = { id: number; type: 'bonus' | 'deduction'; amount: number; description: string; created_at: string; }; function getStatusBadge(status: string) { const statusConfig: Record = { unpaid: { label: 'Belum Dibayar', className: 'bg-yellow-100 text-yellow-800 hover:bg-yellow-100', }, paid: { label: 'Dibayar', className: 'bg-green-100 text-green-800 hover:bg-green-100', }, cancelled: { label: 'Dibatalkan', className: 'bg-red-100 text-red-800 hover:bg-red-100', }, }; const config = statusConfig[status] ?? statusConfig.unpaid; return ( {config.label} ); } type CreateColumnsParams = { handlePay: (payroll: Payroll) => void; handleCancel: (payroll: Payroll) => void; handleAddAdjustment: (payroll: Payroll) => void; handleDeleteAdjustment: ( adjustment: PayrollAdjustment, payrollId: number, ) => void; can: (permission: string) => boolean; }; export function createPayrollColumns( params: CreateColumnsParams, ): ColumnDef[] { const { handlePay, handleCancel, handleAddAdjustment, handleDeleteAdjustment, can, } = params; const hasAnyPayrollAction = can('payroll.adjust') || can('payroll.pay') || can('payroll.cancel'); const columns: ColumnDef[] = [ { id: 'employee_name', header: () => Nama Karyawan, cell: ({ row }) => { const employee = row.original.employee; return ( {employee?.user?.user_profile?.full_name ?? '-'} ); }, }, { accessorKey: 'formatted_base_salary', header: () => Gaji Pokok, cell: ({ row }) => ( {row.getValue('formatted_base_salary') as string} ), }, { accessorKey: 'formatted_bonus_amount', header: () => Bonus, cell: ({ row }) => { const value = row.original.bonus_amount; return ( 0 ? 'font-medium text-green-600' : '' } > {value > 0 ? '+ ' : ''} {row.getValue('formatted_bonus_amount') as string} ); }, }, { accessorKey: 'formatted_deduction_amount', header: () => Potongan, cell: ({ row }) => { const value = row.original.deduction_amount; return ( 0 ? 'font-medium text-red-600' : ''} > {value > 0 ? '- ' : ''} {row.getValue('formatted_deduction_amount') as string} ); }, }, { accessorKey: 'formatted_total_amount', header: () => Total, cell: ({ row }) => ( {row.getValue('formatted_total_amount') as string} ), }, { accessorKey: 'status', header: () => Status, cell: ({ row }) => ( {getStatusBadge(row.getValue('status') as string)} ), }, { id: 'adjustments', header: () => Penyesuaian, cell: ({ row }) => { const payroll = row.original; const adjustments = payroll.payroll_adjustments ?? []; if (adjustments.length === 0) { return -; } return (
{adjustments.map((adj: PayrollAdjustment) => (
{adj.type === 'bonus' ? '+' : '-'}{' '} {formatCurrency(adj.amount)} {adj.description} {can('payroll.adjust') && payroll.status === 'unpaid' && ( )}
))}
); }, }, ]; if (hasAnyPayrollAction) { columns.push({ id: 'actions', header: () => Aksi, meta: { className: 'w-[120px] text-center', headerClassName: 'w-[120px] text-center', }, cell: ({ row }) => { const payroll = row.original; return ( , show: can('payroll.adjust') && payroll.status === 'unpaid', onClick: () => handleAddAdjustment(payroll), }, { label: 'Tandai Dibayar', icon: ( ), show: can('payroll.pay') && payroll.status === 'unpaid', onClick: () => handlePay(payroll), }, { label: 'Batalkan', icon: ( ), show: can('payroll.cancel') && payroll.status === 'unpaid', onClick: () => handleCancel(payroll), }, ]} /> ); }, }); } return columns; }