dstpabuaran.com/resources/js/pages/admin/finance/payroll-period/columns.tsx
Yoga Pangestu 4a4c4ccad0 Refactor table column headers to remove sorting functionality
- Removed sorting buttons and associated logic from various table columns across payroll, employee, leave request, category, customer, product, supplier, and role management pages.
- Simplified header definitions to use static text instead of buttons for sorting.
- Cleaned up unused sort state management in related index components.
2026-08-01 12:11:36 +07:00

270 lines
9.2 KiB
TypeScript

import type { ColumnDef } from '@tanstack/react-table';
import { Eye, Lock, Unlock } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { formatCurrency } from '@/lib/utils';
import { Link } from '@inertiajs/react';
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;
};
const MONTH_NAMES = [
'',
'Januari',
'Februari',
'Maret',
'April',
'Mei',
'Juni',
'Juli',
'Agustus',
'September',
'Oktober',
'November',
'Desember',
];
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;
};
export function createPayrollPeriodColumns(
params: CreateColumnsParams,
): ColumnDef<PayrollPeriod>[] {
const { showUrl, handleClose, handleReopen } = params;
return [
{
id: 'no',
header: () => <span className="block text-center">No</span>,
cell: ({ row }) => (
<span className="block text-center">{row.index + 1}</span>
),
meta: {
className: 'w-[50px] text-center',
headerClassName: 'w-[50px] text-center',
},
},
{
accessorKey: 'year',
header: () => <span>Periode</span>,
cell: ({ row }) => (
<span className="font-medium">
{formatPeriod(row.original)}
</span>
),
},
{
accessorKey: 'payrolls_count',
header: () => <span>Jumlah Karyawan</span>,
cell: ({ row }) => (
<span className="text-center">
{row.getValue('payrolls_count') as number}
</span>
),
},
{
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>
);
},
},
{
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>
);
},
},
{
accessorKey: 'status',
header: () => <span>Status</span>,
cell: ({ row }) => (
<span>{getStatusBadge(row.getValue('status') as string)}</span>
),
},
{
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 (
<TooltipProvider>
<div className="flex items-center justify-center gap-1">
<Tooltip>
<TooltipTrigger asChild>
<Button variant="ghost" size="icon" asChild>
<Link href={showUrl(period.id)}>
<Eye className="h-4 w-4" />
</Link>
</Button>
</TooltipTrigger>
<TooltipContent side="top">
Lihat Detail
</TooltipContent>
</Tooltip>
{period.status === 'open' && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => handleClose(period)}
>
<Lock className="h-4 w-4 text-orange-600" />
</Button>
</TooltipTrigger>
<TooltipContent side="top">
Tutup Periode
</TooltipContent>
</Tooltip>
)}
{period.status === 'closed' && (
<Tooltip>
<TooltipTrigger asChild>
<Button
variant="ghost"
size="icon"
onClick={() => handleReopen(period)}
>
<Unlock className="h-4 w-4 text-blue-600" />
</Button>
</TooltipTrigger>
<TooltipContent side="top">
Buka Periode
</TooltipContent>
</Tooltip>
)}
</div>
</TooltipProvider>
);
},
},
];
}