dstpabuaran.com/resources/js/pages/admin/finance/payroll-period/columns.tsx

231 lines
7.3 KiB
TypeScript

import type { ColumnDef } from '@tanstack/react-table';
import { Eye, Lock, Unlock } from 'lucide-react';
import { RowActions } from '@/components/row-actions';
import { Badge } from '@/components/ui/badge';
import { MONTH_NAMES } from '@/lib/constants';
import { formatCurrency } from '@/lib/utils';
export type PayrollPeriod = {
id: number;
year: number;
month: number;
status: 'open' | 'closed';
closed_at: string | null;
created_at: string;
payrolls_count: number;
paid_count: number;
cancelled_count: number;
payrolls_sum_total_amount: number | null;
payrolls_sum_bonus_amount: number | null;
payrolls_sum_deduction_amount: number | null;
};
function formatPeriod(period: PayrollPeriod): string {
return `${MONTH_NAMES[period.month]} ${period.year}`;
}
function getStatusBadge(status: string) {
const statusConfig: Record<string, { label: string; className: string }> = {
open: {
label: 'Terbuka',
className: 'bg-green-100 text-green-800 hover:bg-green-100',
},
closed: {
label: 'Ditutup',
className: 'bg-gray-100 text-gray-800 hover:bg-gray-100',
},
};
const config = statusConfig[status] ?? statusConfig.open;
return (
<Badge variant="secondary" className={config.className}>
{config.label}
</Badge>
);
}
type CreateColumnsParams = {
showUrl: (id: number) => string;
handleClose: (period: PayrollPeriod) => void;
handleReopen: (period: PayrollPeriod) => void;
can: (permission: string) => boolean;
canViewAll: boolean;
};
export function createPayrollPeriodColumns(
params: CreateColumnsParams,
): ColumnDef<PayrollPeriod>[] {
const { showUrl, handleClose, handleReopen, can, canViewAll } = params;
const columns: ColumnDef<PayrollPeriod>[] = [
{
accessorKey: 'year',
header: () => <span>Periode</span>,
cell: ({ row }) => (
<span className="font-medium">
{formatPeriod(row.original)}
</span>
),
},
];
if (canViewAll) {
columns.push({
accessorKey: 'payrolls_count',
header: () => <span>Jumlah Karyawan</span>,
cell: ({ row }) => (
<span className="text-center">
{row.getValue('payrolls_count') as number}
</span>
),
});
}
columns.push(
{
accessorKey: 'payrolls_sum_total_amount',
header: () => <span>Total Gaji</span>,
cell: ({ row }) => (
<span className="font-medium">
{formatCurrency(
(row.getValue('payrolls_sum_total_amount') as number) ??
0,
)}
</span>
),
},
{
accessorKey: 'payrolls_sum_bonus_amount',
header: () => <span>Bonus</span>,
cell: ({ row }) => {
const value =
(row.getValue('payrolls_sum_bonus_amount') as number) ?? 0;
return (
<span
className={
value > 0
? 'font-medium text-green-600'
: 'text-muted-foreground'
}
>
{value > 0 ? '+ ' : ''}
{formatCurrency(value)}
</span>
);
},
},
{
accessorKey: 'payrolls_sum_deduction_amount',
header: () => <span>Potongan</span>,
cell: ({ row }) => {
const value =
(row.getValue('payrolls_sum_deduction_amount') as number) ??
0;
return (
<span
className={
value > 0
? 'font-medium text-red-600'
: 'text-muted-foreground'
}
>
{value > 0 ? '- ' : ''}
{formatCurrency(value)}
</span>
);
},
},
);
if (canViewAll) {
columns.push({
id: 'payment_status',
header: () => <span>Status Bayar</span>,
cell: ({ row }) => {
const period = row.original;
const paid = period.paid_count ?? 0;
const cancelled = period.cancelled_count ?? 0;
const total = period.payrolls_count ?? 0;
const unpaid = total - paid - cancelled;
return (
<div className="flex flex-col gap-0.5 text-xs">
{paid > 0 && (
<span className="text-green-600">
{paid} dibayar
</span>
)}
{unpaid > 0 && (
<span className="text-yellow-600">
{unpaid} menunggu
</span>
)}
{cancelled > 0 && (
<span className="text-red-600">
{cancelled} dibatalkan
</span>
)}
</div>
);
},
});
}
columns.push({
accessorKey: 'status',
header: () => <span>Status</span>,
cell: ({ row }) => (
<span>{getStatusBadge(row.getValue('status') as string)}</span>
),
});
columns.push({
id: 'actions',
header: () => <span className="block text-center">Aksi</span>,
meta: {
className: 'w-[100px] text-center',
headerClassName: 'w-[100px] text-center',
},
cell: ({ row }) => {
const period = row.original;
return (
<RowActions
actions={[
{
label: 'Lihat Detail',
icon: <Eye className="h-4 w-4" />,
href: showUrl(period.id),
},
{
label: 'Tutup Periode',
icon: (
<Lock className="h-4 w-4 text-orange-600" />
),
show:
can('payroll.adjust') &&
period.status === 'open',
onClick: () => handleClose(period),
},
{
label: 'Buka Periode',
icon: (
<Unlock className="h-4 w-4 text-blue-600" />
),
show:
can('payroll.adjust') &&
period.status === 'closed',
onClick: () => handleReopen(period),
},
]}
/>
);
},
});
return columns;
}