feat: refactor column definitions to conditionally include action buttons based on user permissions

This commit is contained in:
Yoga Pangestu 2026-08-06 22:48:00 +07:00
parent 5ec0e9ff3b
commit 9bbb00c69f
20 changed files with 423 additions and 331 deletions

View File

@ -20,7 +20,7 @@ export function createCashAccountColumns(
): ColumnDef<CashAccount>[] { ): ColumnDef<CashAccount>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<CashAccount>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: () => <span>Nama</span>, header: () => <span>Nama</span>,
@ -39,7 +39,10 @@ export function createCashAccountColumns(
</span> </span>
), ),
}, },
{ ];
if (can('cash.update') || can('cash.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -70,6 +73,8 @@ export function createCashAccountColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -47,7 +47,7 @@ export function createTransactionColumns(
): ColumnDef<CashTransaction>[] { ): ColumnDef<CashTransaction>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<CashTransaction>[] = [
{ {
accessorKey: 'formatted_created_at', accessorKey: 'formatted_created_at',
header: () => <span>Tanggal</span>, header: () => <span>Tanggal</span>,
@ -136,7 +136,10 @@ export function createTransactionColumns(
return <span>{createdBy?.user_profile?.full_name ?? '-'}</span>; return <span>{createdBy?.user_profile?.full_name ?? '-'}</span>;
}, },
}, },
{ ];
if (can('cash.update') || can('cash.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -174,6 +177,8 @@ export function createTransactionColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -91,7 +91,7 @@ export function createEmployeeAdvanceColumns(
const { handleEdit, handleDeleteClick, handleApprove, handlePay, handleShowPayments, can, authUserId } = const { handleEdit, handleDeleteClick, handleApprove, handlePay, handleShowPayments, can, authUserId } =
params; params;
return [ const columns: ColumnDef<EmployeeAdvance>[] = [
{ {
accessorKey: 'formatted_created_at', accessorKey: 'formatted_created_at',
header: () => <span>Tanggal</span>, header: () => <span>Tanggal</span>,
@ -162,7 +162,16 @@ export function createEmployeeAdvanceColumns(
<span>{getStatusBadge(row.getValue('status') as string)}</span> <span>{getStatusBadge(row.getValue('status') as string)}</span>
), ),
}, },
{ ];
if (
can('employee_advances.verify') ||
can('employee_advances.pay') ||
can('employee_advances.view_payments') ||
can('employee_advances.update') ||
can('employee_advances.delete')
) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -226,6 +235,8 @@ export function createEmployeeAdvanceColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -30,7 +30,7 @@ export function createExpenseColumns(
): ColumnDef<Expense>[] { ): ColumnDef<Expense>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<Expense>[] = [
{ {
accessorKey: 'formatted_created_at', accessorKey: 'formatted_created_at',
header: () => <span>Tanggal</span>, header: () => <span>Tanggal</span>,
@ -83,7 +83,10 @@ export function createExpenseColumns(
return <span>{createdBy?.user_profile?.full_name ?? '-'}</span>; return <span>{createdBy?.user_profile?.full_name ?? '-'}</span>;
}, },
}, },
{ ];
if (can('expenses.update') || can('expenses.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -114,6 +117,8 @@ export function createExpenseColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -174,15 +174,15 @@ export function createPayrollPeriodColumns(
}); });
} }
columns.push( columns.push({
{
accessorKey: 'status', accessorKey: 'status',
header: () => <span>Status</span>, header: () => <span>Status</span>,
cell: ({ row }) => ( cell: ({ row }) => (
<span>{getStatusBadge(row.getValue('status') as string)}</span> <span>{getStatusBadge(row.getValue('status') as string)}</span>
), ),
}, });
{
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -224,8 +224,7 @@ export function createPayrollPeriodColumns(
/> />
); );
}, },
}, });
);
return columns; return columns;
} }

View File

@ -54,7 +54,7 @@ export function createEmployeeColumns(
canViewAll, canViewAll,
} = params; } = params;
return [ const columns: ColumnDef<Employee>[] = [
{ {
accessorKey: 'user_profile.full_name', accessorKey: 'user_profile.full_name',
id: 'full_name', id: 'full_name',
@ -154,7 +154,14 @@ export function createEmployeeColumns(
); );
}, },
}, },
{ ];
if (
can('employees.update') ||
can('employees.reset_password') ||
can('employees.delete')
) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -193,6 +200,8 @@ export function createEmployeeColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -64,7 +64,7 @@ export function createLeaveRequestColumns(
const { handleEdit, handleDeleteClick, handleApprove, handleReject, can } = const { handleEdit, handleDeleteClick, handleApprove, handleReject, can } =
params; params;
return [ const columns: ColumnDef<LeaveRequest>[] = [
{ {
id: 'employee_name', id: 'employee_name',
header: () => <span>Oleh</span>, header: () => <span>Oleh</span>,
@ -112,7 +112,14 @@ export function createLeaveRequestColumns(
<span>{getStatusBadge(row.getValue('status') as string)}</span> <span>{getStatusBadge(row.getValue('status') as string)}</span>
), ),
}, },
{ ];
if (
can('leave_requests.verify') ||
can('leave_requests.update') ||
can('leave_requests.delete')
) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -167,6 +174,8 @@ export function createLeaveRequestColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -139,6 +139,7 @@ export function CuttingCardRow({
)} )}
</div> </div>
{(can('cuttings.update') || can('cuttings.delete')) && (
<RowActions <RowActions
actions={[ actions={[
{ {
@ -158,6 +159,7 @@ export function CuttingCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -126,6 +126,7 @@ export function PurchaseCardRow({
)} )}
</div> </div>
{(can('purchases.update') || can('purchases.delete')) && (
<RowActions <RowActions
actions={[ actions={[
{ {
@ -145,6 +146,7 @@ export function PurchaseCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -135,6 +135,7 @@ export function RestockCardRow({
</div> </div>
</div> </div>
{(can('restocks.update') || can('restocks.delete')) && (
<RowActions <RowActions
actions={[ actions={[
{ {
@ -154,6 +155,7 @@ export function RestockCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -183,6 +183,7 @@ export function TransactionCardRow({
</div> </div>
</div> </div>
{(can('orders.update') || can('orders.delete')) && (
<RowActions <RowActions
actions={[ actions={[
...(transaction.status === 'pending' && can('orders.update') ...(transaction.status === 'pending' && can('orders.update')
@ -234,6 +235,7 @@ export function TransactionCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -18,7 +18,7 @@ export function createCategoryColumns(
): ColumnDef<Category>[] { ): ColumnDef<Category>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<Category>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: () => <span>Nama</span>, header: () => <span>Nama</span>,
@ -28,7 +28,10 @@ export function createCategoryColumns(
</span> </span>
), ),
}, },
{ ];
if (can('categories.update') || can('categories.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -59,6 +62,8 @@ export function createCategoryColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -20,7 +20,7 @@ export function createCustomerColumns(
): ColumnDef<Customer>[] { ): ColumnDef<Customer>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<Customer>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: () => <span>Nama</span>, header: () => <span>Nama</span>,
@ -46,7 +46,10 @@ export function createCustomerColumns(
</span> </span>
), ),
}, },
{ ];
if (can('customers.update') || can('customers.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -77,6 +80,8 @@ export function createCustomerColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -90,7 +90,7 @@ export function createProductColumns(
can, can,
} = params; } = params;
return [ const columns: ColumnDef<Product>[] = [
{ {
id: 'expand', id: 'expand',
header: '', header: '',
@ -338,7 +338,10 @@ export function createProductColumns(
); );
}, },
}, },
{ ];
if (can('products.update') || can('products.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -369,6 +372,8 @@ export function createProductColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -144,6 +144,7 @@ export function ProductCardRow({
</div> </div>
</div> </div>
{(can('products.update') || can('products.delete')) && (
<RowActions <RowActions
actions={[ actions={[
{ {
@ -163,6 +164,7 @@ export function ProductCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -34,6 +34,8 @@ export function VariantSubRow({
variant: ProductVariant; variant: ProductVariant;
} | null>(null); } | null>(null);
const canAnyAction = can('products.transfer_stock') || can('products.view_stock_mutations') || can('products.update') || can('products.delete');
return ( return (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
@ -52,16 +54,18 @@ export function VariantSubRow({
</TableHead> </TableHead>
<TableHead className="text-center">Stok Ecer</TableHead> <TableHead className="text-center">Stok Ecer</TableHead>
<TableHead>Harga</TableHead> <TableHead>Harga</TableHead>
{canAnyAction && (
<TableHead className="w-[120px] text-center"> <TableHead className="w-[120px] text-center">
Aksi Aksi
</TableHead> </TableHead>
)}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{variants.length === 0 ? ( {variants.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={8} colSpan={canAnyAction ? 8 : 7}
className="text-center text-muted-foreground" className="text-center text-muted-foreground"
> >
Tidak ada varian. Tidak ada varian.
@ -116,6 +120,7 @@ export function VariantSubRow({
'-' '-'
)} )}
</TableCell> </TableCell>
{canAnyAction && (
<TableCell> <TableCell>
<RowActions <RowActions
actions={[ actions={[
@ -173,6 +178,7 @@ export function VariantSubRow({
]} ]}
/> />
</TableCell> </TableCell>
)}
</TableRow> </TableRow>
)) ))
)} )}

View File

@ -94,6 +94,7 @@ export function RawMaterialCardRow({
</div> </div>
</div> </div>
{(can('raw_materials.update') || can('raw_materials.delete')) && (
<RowActions <RowActions
actions={[ actions={[
{ {
@ -113,6 +114,7 @@ export function RawMaterialCardRow({
]} ]}
wrapperClassName="flex shrink-0 items-center gap-1" wrapperClassName="flex shrink-0 items-center gap-1"
/> />
)}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>

View File

@ -47,6 +47,8 @@ export function RawMaterialVariantSubRow({
); );
} }
const canAnyAction = can('raw_materials.update') || can('raw_materials.delete');
return ( return (
<div className="overflow-x-auto"> <div className="overflow-x-auto">
<Table> <Table>
@ -59,16 +61,18 @@ export function RawMaterialVariantSubRow({
<TableHead>Nama Varian</TableHead> <TableHead>Nama Varian</TableHead>
<TableHead>Harga</TableHead> <TableHead>Harga</TableHead>
<TableHead className="text-center">Stok</TableHead> <TableHead className="text-center">Stok</TableHead>
{canAnyAction && (
<TableHead className="w-[100px] text-center"> <TableHead className="w-[100px] text-center">
Aksi Aksi
</TableHead> </TableHead>
)}
</TableRow> </TableRow>
</TableHeader> </TableHeader>
<TableBody> <TableBody>
{variants.length === 0 ? ( {variants.length === 0 ? (
<TableRow> <TableRow>
<TableCell <TableCell
colSpan={6} colSpan={canAnyAction ? 6 : 5}
className="text-center text-muted-foreground" className="text-center text-muted-foreground"
> >
Tidak ada varian. Tidak ada varian.
@ -101,6 +105,7 @@ export function RawMaterialVariantSubRow({
<TableCell className="text-center"> <TableCell className="text-center">
{formatNumber(variant.stock)} {formatNumber(variant.stock)}
</TableCell> </TableCell>
{canAnyAction && (
<TableCell> <TableCell>
<RowActions <RowActions
actions={[ actions={[
@ -132,6 +137,7 @@ export function RawMaterialVariantSubRow({
]} ]}
/> />
</TableCell> </TableCell>
)}
</TableRow> </TableRow>
)) ))
)} )}

View File

@ -20,7 +20,7 @@ export function createSupplierColumns(
): ColumnDef<Supplier>[] { ): ColumnDef<Supplier>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<Supplier>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: () => <span>Nama</span>, header: () => <span>Nama</span>,
@ -46,7 +46,10 @@ export function createSupplierColumns(
</span> </span>
), ),
}, },
{ ];
if (can('suppliers.update') || can('suppliers.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -77,6 +80,8 @@ export function createSupplierColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }

View File

@ -19,7 +19,7 @@ export function createRoleColumns(
): ColumnDef<Role>[] { ): ColumnDef<Role>[] {
const { handleEdit, handleDeleteClick, can } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ const columns: ColumnDef<Role>[] = [
{ {
accessorKey: 'name', accessorKey: 'name',
header: () => <span>Nama Role</span>, header: () => <span>Nama Role</span>,
@ -44,7 +44,10 @@ export function createRoleColumns(
</span> </span>
), ),
}, },
{ ];
if (can('roles.update') || can('roles.delete')) {
columns.push({
id: 'actions', id: 'actions',
header: () => <span className="block text-center">Aksi</span>, header: () => <span className="block text-center">Aksi</span>,
meta: { meta: {
@ -75,6 +78,8 @@ export function createRoleColumns(
/> />
); );
}, },
}, });
]; }
return columns;
} }