feat: add permission checks for various actions across admin pages

- Integrated permission checks using `can` function in payroll period management to control visibility of adjustment, pay, and cancel actions.
- Updated employee management to conditionally render edit, reset password, and delete actions based on permissions.
- Enhanced leave request management with permission checks for approve, reject, edit, and delete actions.
- Implemented permission checks in cutting management for edit and delete actions.
- Added permission checks for purchase management actions including create, edit, and delete.
- Integrated permission checks in restock management for create, edit, and delete actions.
- Updated transaction management to conditionally render create, edit, and delete actions based on permissions.
- Enhanced category management with permission checks for edit and delete actions.
- Integrated permission checks in customer management for edit and delete actions.
- Updated product management to conditionally render create, edit, and delete actions based on permissions.
- Enhanced raw material management with permission checks for edit and delete actions.
- Integrated permission checks in supplier management for edit and delete actions.
- Updated role management to conditionally render edit and delete actions based on permissions.
This commit is contained in:
Yoga Pangestu 2026-08-05 22:52:35 +07:00
parent fc1c84f420
commit 4ac83c8e78
38 changed files with 305 additions and 136 deletions

View File

@ -12,12 +12,13 @@ export type CashAccount = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (cashAccount: CashAccount) => void; handleEdit: (cashAccount: CashAccount) => void;
handleDeleteClick: (cashAccount: CashAccount) => void; handleDeleteClick: (cashAccount: CashAccount) => void;
can: (permission: string) => boolean;
}; };
export function createCashAccountColumns( export function createCashAccountColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<CashAccount>[] { ): ColumnDef<CashAccount>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -54,6 +55,7 @@ export function createCashAccountColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('cash.update'),
onClick: () => handleEdit(cashAccount), onClick: () => handleEdit(cashAccount),
}, },
{ {
@ -61,6 +63,7 @@ export function createCashAccountColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('cash.delete'),
onClick: () => handleDeleteClick(cashAccount), onClick: () => handleDeleteClick(cashAccount),
}, },
]} ]}

View File

@ -21,6 +21,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import { import {
@ -69,6 +70,7 @@ export default function CashAccountIndex({
filters, filters,
filterOptions, filterOptions,
}: Props) { }: Props) {
const { can } = useCan();
const [depositOpen, setDepositOpen] = useState(false); const [depositOpen, setDepositOpen] = useState(false);
const [withdrawalOpen, setWithdrawalOpen] = useState(false); const [withdrawalOpen, setWithdrawalOpen] = useState(false);
const [editing, setEditing] = useState<CashTransaction | null>(null); const [editing, setEditing] = useState<CashTransaction | null>(null);
@ -137,6 +139,7 @@ export default function CashAccountIndex({
setEditReceiptKey(transaction.receipt_key ?? null); setEditReceiptKey(transaction.receipt_key ?? null);
}, },
handleDeleteClick: (transaction) => setDeleting(transaction), handleDeleteClick: (transaction) => setDeleting(transaction),
can,
}); });
const filterToolbar = ( const filterToolbar = (
@ -180,20 +183,24 @@ export default function CashAccountIndex({
title="Kas Toko" title="Kas Toko"
actions={ actions={
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button {can('cash.deposit') && (
variant="outline" <Button
onClick={() => setDepositOpen(true)} variant="outline"
> onClick={() => setDepositOpen(true)}
<ArrowDownToLine className="h-4 w-4" /> >
Deposit <ArrowDownToLine className="h-4 w-4" />
</Button> Deposit
<Button </Button>
variant="outline" )}
onClick={() => setWithdrawalOpen(true)} {can('cash.withdraw') && (
> <Button
<ArrowUpFromLine className="h-4 w-4" /> variant="outline"
Withdrawal onClick={() => setWithdrawalOpen(true)}
</Button> >
<ArrowUpFromLine className="h-4 w-4" />
Withdrawal
</Button>
)}
</div> </div>
} }
/> />

View File

@ -50,12 +50,13 @@ function getReferenceLabel(type: string): string {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (transaction: CashTransaction) => void; handleEdit: (transaction: CashTransaction) => void;
handleDeleteClick: (transaction: CashTransaction) => void; handleDeleteClick: (transaction: CashTransaction) => void;
can: (permission: string) => boolean;
}; };
export function createTransactionColumns( export function createTransactionColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<CashTransaction>[] { ): ColumnDef<CashTransaction>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -174,6 +175,7 @@ export function createTransactionColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('cash.update'),
onClick: () => handleEdit(transaction), onClick: () => handleEdit(transaction),
}, },
{ {
@ -181,6 +183,7 @@ export function createTransactionColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('cash.delete'),
onClick: () => handleDeleteClick(transaction), onClick: () => handleDeleteClick(transaction),
}, },
]} ]}

View File

@ -62,12 +62,14 @@ type CreateColumnsParams = {
handleDeleteClick: (employeeAdvance: EmployeeAdvance) => void; handleDeleteClick: (employeeAdvance: EmployeeAdvance) => void;
handleApprove: (employeeAdvance: EmployeeAdvance) => void; handleApprove: (employeeAdvance: EmployeeAdvance) => void;
handlePay: (employeeAdvance: EmployeeAdvance) => void; handlePay: (employeeAdvance: EmployeeAdvance) => void;
can: (permission: string) => boolean;
}; };
export function createEmployeeAdvanceColumns( export function createEmployeeAdvanceColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<EmployeeAdvance>[] { ): ColumnDef<EmployeeAdvance>[] {
const { handleEdit, handleDeleteClick, handleApprove, handlePay } = params; const { handleEdit, handleDeleteClick, handleApprove, handlePay, can } =
params;
return [ return [
{ {
@ -142,7 +144,9 @@ export function createEmployeeAdvanceColumns(
icon: ( icon: (
<CheckCircle className="h-4 w-4 text-green-600" /> <CheckCircle className="h-4 w-4 text-green-600" />
), ),
show: employeeAdvance.status === 'pending', show:
can('employee_advances.verify') &&
employeeAdvance.status === 'pending',
onClick: () => handleApprove(employeeAdvance), onClick: () => handleApprove(employeeAdvance),
}, },
{ {
@ -150,12 +154,15 @@ export function createEmployeeAdvanceColumns(
icon: ( icon: (
<CircleDollarSign className="h-4 w-4 text-blue-600" /> <CircleDollarSign className="h-4 w-4 text-blue-600" />
), ),
show: employeeAdvance.status === 'approved', show:
can('employee_advances.pay') &&
employeeAdvance.status === 'approved',
onClick: () => handlePay(employeeAdvance), onClick: () => handlePay(employeeAdvance),
}, },
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('employee_advances.update'),
onClick: () => handleEdit(employeeAdvance), onClick: () => handleEdit(employeeAdvance),
}, },
{ {
@ -163,6 +170,7 @@ export function createEmployeeAdvanceColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('employee_advances.delete'),
onClick: () => onClick: () =>
handleDeleteClick(employeeAdvance), handleDeleteClick(employeeAdvance),
}, },

View File

@ -12,6 +12,7 @@ import { RupiahInput } from '@/components/rupiah-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -35,6 +36,7 @@ type Props = {
}; };
export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) { export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<EmployeeAdvance | null>(null); const [editing, setEditing] = useState<EmployeeAdvance | null>(null);
const [deleting, setDeleting] = useState<EmployeeAdvance | null>(null); const [deleting, setDeleting] = useState<EmployeeAdvance | null>(null);
@ -113,6 +115,7 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
handleDeleteClick: (employeeAdvance) => setDeleting(employeeAdvance), handleDeleteClick: (employeeAdvance) => setDeleting(employeeAdvance),
handleApprove: (employeeAdvance) => setApproving(employeeAdvance), handleApprove: (employeeAdvance) => setApproving(employeeAdvance),
handlePay: (employeeAdvance) => setPaying(employeeAdvance), handlePay: (employeeAdvance) => setPaying(employeeAdvance),
can,
}); });
return ( return (
@ -123,15 +126,17 @@ export default function EmployeeAdvanceIndex({ employeeAdvances }: Props) {
<PageHeader <PageHeader
title="Kasbon" title="Kasbon"
actions={ actions={
<Button asChild> can('employee_advances.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -22,12 +22,13 @@ export type Expense = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (expense: Expense) => void; handleEdit: (expense: Expense) => void;
handleDeleteClick: (expense: Expense) => void; handleDeleteClick: (expense: Expense) => void;
can: (permission: string) => boolean;
}; };
export function createExpenseColumns( export function createExpenseColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<Expense>[] { ): ColumnDef<Expense>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -98,6 +99,7 @@ export function createExpenseColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('expenses.update'),
onClick: () => handleEdit(expense), onClick: () => handleEdit(expense),
}, },
{ {
@ -105,6 +107,7 @@ export function createExpenseColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('expenses.delete'),
onClick: () => handleDeleteClick(expense), onClick: () => handleDeleteClick(expense),
}, },
]} ]}

View File

@ -12,6 +12,7 @@ import { RupiahInput } from '@/components/rupiah-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -33,6 +34,7 @@ type Props = {
}; };
export default function ExpenseIndex({ expenses }: Props) { export default function ExpenseIndex({ expenses }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Expense | null>(null); const [editing, setEditing] = useState<Expense | null>(null);
const [deleting, setDeleting] = useState<Expense | null>(null); const [deleting, setDeleting] = useState<Expense | null>(null);
@ -84,6 +86,7 @@ export default function ExpenseIndex({ expenses }: Props) {
setEditReceiptKey(expense.receipt_key ?? null); setEditReceiptKey(expense.receipt_key ?? null);
}, },
handleDeleteClick: (expense) => setDeleting(expense), handleDeleteClick: (expense) => setDeleting(expense),
can,
}); });
return ( return (
@ -94,15 +97,17 @@ export default function ExpenseIndex({ expenses }: Props) {
<PageHeader <PageHeader
title="Pengeluaran" title="Pengeluaran"
actions={ actions={
<Button asChild> can('expenses.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -49,12 +49,13 @@ type CreateColumnsParams = {
showUrl: (id: number) => string; showUrl: (id: number) => string;
handleClose: (period: PayrollPeriod) => void; handleClose: (period: PayrollPeriod) => void;
handleReopen: (period: PayrollPeriod) => void; handleReopen: (period: PayrollPeriod) => void;
can: (permission: string) => boolean;
}; };
export function createPayrollPeriodColumns( export function createPayrollPeriodColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<PayrollPeriod>[] { ): ColumnDef<PayrollPeriod>[] {
const { showUrl, handleClose, handleReopen } = params; const { showUrl, handleClose, handleReopen, can } = params;
return [ return [
{ {
@ -191,7 +192,9 @@ export function createPayrollPeriodColumns(
icon: ( icon: (
<Lock className="h-4 w-4 text-orange-600" /> <Lock className="h-4 w-4 text-orange-600" />
), ),
show: period.status === 'open', show:
can('payroll.adjust') &&
period.status === 'open',
onClick: () => handleClose(period), onClick: () => handleClose(period),
}, },
{ {
@ -199,7 +202,9 @@ export function createPayrollPeriodColumns(
icon: ( icon: (
<Unlock className="h-4 w-4 text-blue-600" /> <Unlock className="h-4 w-4 text-blue-600" />
), ),
show: period.status === 'closed', show:
can('payroll.adjust') &&
period.status === 'closed',
onClick: () => handleReopen(period), onClick: () => handleReopen(period),
}, },
]} ]}

View File

@ -4,6 +4,7 @@ import type { PaginationState } from '@/components/data-table';
import { DataTable } from '@/components/data-table'; import { DataTable } from '@/components/data-table';
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { MONTH_NAMES } from '@/lib/constants'; import { MONTH_NAMES } from '@/lib/constants';
import { import {
@ -26,6 +27,7 @@ type Props = {
}; };
export default function PayrollPeriodIndex({ payrollPeriods }: Props) { export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
const { can } = useCan();
const [closing, setClosing] = useState<PayrollPeriod | null>(null); const [closing, setClosing] = useState<PayrollPeriod | null>(null);
const [reopening, setReopening] = useState<PayrollPeriod | null>(null); const [reopening, setReopening] = useState<PayrollPeriod | null>(null);
@ -78,6 +80,7 @@ export default function PayrollPeriodIndex({ payrollPeriods }: Props) {
showUrl: (id) => payrollPeriodShow(id).url, showUrl: (id) => payrollPeriodShow(id).url,
handleClose: (period) => setClosing(period), handleClose: (period) => setClosing(period),
handleReopen: (period) => setReopening(period), handleReopen: (period) => setReopening(period),
can,
}); });
return ( return (

View File

@ -67,6 +67,7 @@ type CreateColumnsParams = {
adjustment: PayrollAdjustment, adjustment: PayrollAdjustment,
payrollId: number, payrollId: number,
) => void; ) => void;
can: (permission: string) => boolean;
}; };
export function createPayrollColumns( export function createPayrollColumns(
@ -77,6 +78,7 @@ export function createPayrollColumns(
handleCancel, handleCancel,
handleAddAdjustment, handleAddAdjustment,
handleDeleteAdjustment, handleDeleteAdjustment,
can,
} = params; } = params;
return [ return [
@ -218,7 +220,9 @@ export function createPayrollColumns(
{ {
label: 'Tambah Adjustment', label: 'Tambah Adjustment',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: payroll.status === 'unpaid', show:
can('payroll.adjust') &&
payroll.status === 'unpaid',
onClick: () => handleAddAdjustment(payroll), onClick: () => handleAddAdjustment(payroll),
}, },
{ {
@ -226,7 +230,9 @@ export function createPayrollColumns(
icon: ( icon: (
<CircleDollarSign className="h-4 w-4 text-green-600" /> <CircleDollarSign className="h-4 w-4 text-green-600" />
), ),
show: payroll.status === 'unpaid', show:
can('payroll.pay') &&
payroll.status === 'unpaid',
onClick: () => handlePay(payroll), onClick: () => handlePay(payroll),
}, },
{ {
@ -234,7 +240,9 @@ export function createPayrollColumns(
icon: ( icon: (
<XCircle className="h-4 w-4 text-destructive" /> <XCircle className="h-4 w-4 text-destructive" />
), ),
show: payroll.status === 'unpaid', show:
can('payroll.cancel') &&
payroll.status === 'unpaid',
onClick: () => handleCancel(payroll), onClick: () => handleCancel(payroll),
}, },
]} ]}

View File

@ -16,6 +16,7 @@ import {
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group'; import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
import { useCan } from '@/hooks/use-can';
import { MONTH_NAMES } from '@/lib/constants'; import { MONTH_NAMES } from '@/lib/constants';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import { destroy as adjustmentDestroy } from '@/routes/admin/finance/payroll-adjustments'; import { destroy as adjustmentDestroy } from '@/routes/admin/finance/payroll-adjustments';
@ -39,6 +40,7 @@ type Props = {
}; };
export default function PayrollPeriodShow({ payrollPeriod }: Props) { export default function PayrollPeriodShow({ payrollPeriod }: Props) {
const { can } = useCan();
const [paying, setPaying] = useState<Payroll | null>(null); const [paying, setPaying] = useState<Payroll | null>(null);
const [cancelling, setCancelling] = useState<Payroll | null>(null); const [cancelling, setCancelling] = useState<Payroll | null>(null);
const [addingAdjustment, setAddingAdjustment] = useState<Payroll | null>( const [addingAdjustment, setAddingAdjustment] = useState<Payroll | null>(
@ -98,6 +100,7 @@ export default function PayrollPeriodShow({ payrollPeriod }: Props) {
handleDeleteAdjustment: (adjustment, payrollId) => { handleDeleteAdjustment: (adjustment, payrollId) => {
setDeletingAdjustment({ adjustment, payrollId }); setDeletingAdjustment({ adjustment, payrollId });
}, },
can,
}); });
const totalBaseSalary = payrollPeriod.payrolls.reduce( const totalBaseSalary = payrollPeriod.payrolls.reduce(

View File

@ -38,6 +38,7 @@ type CreateColumnsParams = {
handleDeleteClick: (employee: Employee) => void; handleDeleteClick: (employee: Employee) => void;
handleResetPassword: (employee: Employee) => void; handleResetPassword: (employee: Employee) => void;
toggleActiveUrl: (id: number) => string; toggleActiveUrl: (id: number) => string;
can: (permission: string) => boolean;
}; };
export function createEmployeeColumns( export function createEmployeeColumns(
@ -48,6 +49,7 @@ export function createEmployeeColumns(
handleDeleteClick, handleDeleteClick,
handleResetPassword, handleResetPassword,
toggleActiveUrl, toggleActiveUrl,
can,
} = params; } = params;
return [ return [
@ -161,6 +163,7 @@ export function createEmployeeColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('employees.update'),
onClick: () => handleEdit(employee), onClick: () => handleEdit(employee),
}, },
{ {
@ -168,6 +171,7 @@ export function createEmployeeColumns(
icon: ( icon: (
<KeyRound className="h-4 w-4 text-muted-foreground" /> <KeyRound className="h-4 w-4 text-muted-foreground" />
), ),
show: can('employees.reset_password'),
onClick: () => handleResetPassword(employee), onClick: () => handleResetPassword(employee),
}, },
{ {
@ -175,6 +179,7 @@ export function createEmployeeColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('employees.delete'),
onClick: () => handleDeleteClick(employee), onClick: () => handleDeleteClick(employee),
}, },
]} ]}

View File

@ -14,6 +14,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -42,6 +43,7 @@ type Props = {
}; };
export default function EmployeeIndex({ employees, filters }: Props) { export default function EmployeeIndex({ employees, filters }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Employee | null>(null); const [deleting, setDeleting] = useState<Employee | null>(null);
const [resetPasswordTarget, setResetPasswordTarget] = const [resetPasswordTarget, setResetPasswordTarget] =
useState<Employee | null>(null); useState<Employee | null>(null);
@ -99,6 +101,7 @@ export default function EmployeeIndex({ employees, filters }: Props) {
handleDeleteClick: (employee) => setDeleting(employee), handleDeleteClick: (employee) => setDeleting(employee),
handleResetPassword: (employee) => setResetPasswordTarget(employee), handleResetPassword: (employee) => setResetPasswordTarget(employee),
toggleActiveUrl: (id) => toggleActive.url(id), toggleActiveUrl: (id) => toggleActive.url(id),
can,
}); });
const filterToolbar = ( const filterToolbar = (
@ -183,12 +186,14 @@ export default function EmployeeIndex({ employees, filters }: Props) {
<PageHeader <PageHeader
title="Pegawai" title="Pegawai"
actions={ actions={
<Button asChild> can('employees.create') ? (
<Link href={employeeCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={employeeCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -55,12 +55,13 @@ type CreateColumnsParams = {
handleDeleteClick: (leaveRequest: LeaveRequest) => void; handleDeleteClick: (leaveRequest: LeaveRequest) => void;
handleApprove: (leaveRequest: LeaveRequest) => void; handleApprove: (leaveRequest: LeaveRequest) => void;
handleReject: (leaveRequest: LeaveRequest) => void; handleReject: (leaveRequest: LeaveRequest) => void;
can: (permission: string) => boolean;
}; };
export function createLeaveRequestColumns( export function createLeaveRequestColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<LeaveRequest>[] { ): ColumnDef<LeaveRequest>[] {
const { handleEdit, handleDeleteClick, handleApprove, handleReject } = const { handleEdit, handleDeleteClick, handleApprove, handleReject, can } =
params; params;
return [ return [
@ -129,7 +130,9 @@ export function createLeaveRequestColumns(
icon: ( icon: (
<CheckCircle className="h-4 w-4 text-green-600" /> <CheckCircle className="h-4 w-4 text-green-600" />
), ),
show: leaveRequest.status === 'pending', show:
can('leave_requests.verify') &&
leaveRequest.status === 'pending',
onClick: () => handleApprove(leaveRequest), onClick: () => handleApprove(leaveRequest),
}, },
{ {
@ -137,12 +140,15 @@ export function createLeaveRequestColumns(
icon: ( icon: (
<XCircle className="h-4 w-4 text-red-600" /> <XCircle className="h-4 w-4 text-red-600" />
), ),
show: leaveRequest.status === 'pending', show:
can('leave_requests.verify') &&
leaveRequest.status === 'pending',
onClick: () => handleReject(leaveRequest), onClick: () => handleReject(leaveRequest),
}, },
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('leave_requests.update'),
onClick: () => handleEdit(leaveRequest), onClick: () => handleEdit(leaveRequest),
}, },
{ {
@ -150,6 +156,7 @@ export function createLeaveRequestColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('leave_requests.delete'),
onClick: () => handleDeleteClick(leaveRequest), onClick: () => handleDeleteClick(leaveRequest),
}, },
]} ]}

View File

@ -18,6 +18,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
approve, approve,
@ -52,6 +53,7 @@ type Props = {
}; };
export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) { export default function LeaveRequestIndex({ leaveRequests, filters, filterOptions }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<LeaveRequest | null>(null); const [editing, setEditing] = useState<LeaveRequest | null>(null);
const [deleting, setDeleting] = useState<LeaveRequest | null>(null); const [deleting, setDeleting] = useState<LeaveRequest | null>(null);
@ -141,6 +143,7 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
handleDeleteClick: (leaveRequest) => setDeleting(leaveRequest), handleDeleteClick: (leaveRequest) => setDeleting(leaveRequest),
handleApprove: (leaveRequest) => setApproving(leaveRequest), handleApprove: (leaveRequest) => setApproving(leaveRequest),
handleReject: (leaveRequest) => setRejecting(leaveRequest), handleReject: (leaveRequest) => setRejecting(leaveRequest),
can,
}); });
const filterToolbar = ( const filterToolbar = (
@ -181,15 +184,17 @@ export default function LeaveRequestIndex({ leaveRequests, filters, filterOption
<PageHeader <PageHeader
title="Cuti" title="Cuti"
actions={ actions={
<Button asChild> can('leave_requests.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { ImagePreviewButton } from '@/components/image-preview-button';
import { RowActions } from '@/components/row-actions'; import { RowActions } from '@/components/row-actions';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format'; import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import type { Cutting } from './columns'; import type { Cutting } from './columns';
@ -24,6 +25,7 @@ export function CuttingCardRow({
onEdit, onEdit,
onDelete, onDelete,
}: CuttingCardRowParams) { }: CuttingCardRowParams) {
const { can } = useCan();
const items = cutting.cutting_materials ?? []; const items = cutting.cutting_materials ?? [];
const result = cutting.cutting_results?.[0]; const result = cutting.cutting_results?.[0];
const materialCount = items.length; const materialCount = items.length;
@ -126,6 +128,7 @@ export function CuttingCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('cuttings.update'),
onClick: () => onEdit(cutting), onClick: () => onEdit(cutting),
}, },
{ {
@ -133,6 +136,7 @@ export function CuttingCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('cuttings.delete'),
onClick: () => onDelete(cutting), onClick: () => onDelete(cutting),
}, },
]} ]}

View File

@ -6,6 +6,7 @@ import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand'; import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -28,6 +29,7 @@ type Props = {
}; };
export default function CuttingIndex({ cuttings }: Props) { export default function CuttingIndex({ cuttings }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Cutting | null>(null); const [deleting, setDeleting] = useState<Cutting | null>(null);
const expand = useCardTableExpand(true); const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function CuttingIndex({ cuttings }: Props) {
<PageHeader <PageHeader
title="Cutting" title="Cutting"
actions={ actions={
<Button asChild> can('cuttings.create') ? (
<Link href={cuttingCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={cuttingCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -6,6 +6,7 @@ import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand'; import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -28,6 +29,7 @@ type Props = {
}; };
export default function PurchaseIndex({ purchases }: Props) { export default function PurchaseIndex({ purchases }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Purchase | null>(null); const [deleting, setDeleting] = useState<Purchase | null>(null);
const expand = useCardTableExpand(true); const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function PurchaseIndex({ purchases }: Props) {
<PageHeader <PageHeader
title="Belanja" title="Belanja"
actions={ actions={
<Button asChild> can('purchases.create') ? (
<Link href={purchaseCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={purchaseCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { ImagePreviewButton } from '@/components/image-preview-button';
import { RowActions } from '@/components/row-actions'; import { RowActions } from '@/components/row-actions';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format'; import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import type { Purchase } from './columns'; import type { Purchase } from './columns';
@ -24,6 +25,7 @@ export function PurchaseCardRow({
onEdit, onEdit,
onDelete, onDelete,
}: PurchaseCardRowParams) { }: PurchaseCardRowParams) {
const { can } = useCan();
const items = purchase.purchase_items ?? []; const items = purchase.purchase_items ?? [];
const variantCount = items.length; const variantCount = items.length;
const rawMaterialName = const rawMaterialName =
@ -129,6 +131,7 @@ export function PurchaseCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('purchases.update'),
onClick: () => onEdit(purchase), onClick: () => onEdit(purchase),
}, },
{ {
@ -136,6 +139,7 @@ export function PurchaseCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('purchases.delete'),
onClick: () => onDelete(purchase), onClick: () => onDelete(purchase),
}, },
]} ]}

View File

@ -6,6 +6,7 @@ import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { useCardTableExpand } from '@/components/hooks/use-card-table-expand'; import { useCardTableExpand } from '@/components/hooks/use-card-table-expand';
import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -28,6 +29,7 @@ type Props = {
}; };
export default function RestockIndex({ restocks }: Props) { export default function RestockIndex({ restocks }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Restock | null>(null); const [deleting, setDeleting] = useState<Restock | null>(null);
const expand = useCardTableExpand(true); const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function RestockIndex({ restocks }: Props) {
<PageHeader <PageHeader
title="Restock" title="Restock"
actions={ actions={
<Button asChild> can('restocks.create') ? (
<Link href={restockCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={restockCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { RowActions } from '@/components/row-actions';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format'; import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import type { Restock, RestockStockType } from './columns'; import type { Restock, RestockStockType } from './columns';
@ -38,6 +39,7 @@ export function RestockCardRow({
onEdit, onEdit,
onDelete, onDelete,
}: RestockCardRowParams) { }: RestockCardRowParams) {
const { can } = useCan();
const items = restock.restock_items ?? []; const items = restock.restock_items ?? [];
const variantCount = items.length; const variantCount = items.length;
const productNames = [ const productNames = [
@ -138,6 +140,7 @@ export function RestockCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('restocks.update'),
onClick: () => onEdit(restock), onClick: () => onEdit(restock),
}, },
{ {
@ -145,6 +148,7 @@ export function RestockCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('restocks.delete'),
onClick: () => onDelete(restock), onClick: () => onDelete(restock),
}, },
]} ]}

View File

@ -22,6 +22,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -73,6 +74,7 @@ export default function TransactionIndex({
filters, filters,
filterOptions, filterOptions,
}: Props) { }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Transaction | null>(null); const [deleting, setDeleting] = useState<Transaction | null>(null);
const expand = useCardTableExpand(true); const expand = useCardTableExpand(true);
@ -320,12 +322,14 @@ export default function TransactionIndex({
<PageHeader <PageHeader
title="Transaksi" title="Transaksi"
actions={ actions={
<Button asChild> can('orders.create') ? (
<Link href={transactionCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={transactionCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { RowActions } from '@/components/row-actions';
import { Badge } from '@/components/ui/badge'; import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format'; import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import type { Transaction } from './columns'; import type { Transaction } from './columns';
@ -32,6 +33,7 @@ export function TransactionCardRow({
onEdit, onEdit,
onDelete, onDelete,
}: TransactionCardRowParams) { }: TransactionCardRowParams) {
const { can } = useCan();
const items = transaction.order_items ?? []; const items = transaction.order_items ?? [];
const variantCount = items.length; const variantCount = items.length;
const productNames = [ const productNames = [
@ -176,6 +178,7 @@ export function TransactionCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('orders.update'),
onClick: () => onEdit(transaction), onClick: () => onEdit(transaction),
}, },
{ {
@ -183,6 +186,7 @@ export function TransactionCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('orders.delete'),
onClick: () => onDelete(transaction), onClick: () => onDelete(transaction),
}, },
]} ]}

View File

@ -10,12 +10,13 @@ export type Category = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (category: Category) => void; handleEdit: (category: Category) => void;
handleDeleteClick: (category: Category) => void; handleDeleteClick: (category: Category) => void;
can: (permission: string) => boolean;
}; };
export function createCategoryColumns( export function createCategoryColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<Category>[] { ): ColumnDef<Category>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -43,6 +44,7 @@ export function createCategoryColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('categories.update'),
onClick: () => handleEdit(category), onClick: () => handleEdit(category),
}, },
{ {
@ -50,6 +52,7 @@ export function createCategoryColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('categories.delete'),
onClick: () => handleDeleteClick(category), onClick: () => handleDeleteClick(category),
}, },
]} ]}

View File

@ -10,6 +10,7 @@ import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -32,6 +33,7 @@ type Props = {
}; };
export default function CategoryIndex({ categories, highlight }: Props) { export default function CategoryIndex({ categories, highlight }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Category | null>(null); const [editing, setEditing] = useState<Category | null>(null);
const [deleting, setDeleting] = useState<Category | null>(null); const [deleting, setDeleting] = useState<Category | null>(null);
@ -66,6 +68,7 @@ export default function CategoryIndex({ categories, highlight }: Props) {
const columns = createCategoryColumns({ const columns = createCategoryColumns({
handleEdit: (category) => setEditing(category), handleEdit: (category) => setEditing(category),
handleDeleteClick: (category) => setDeleting(category), handleDeleteClick: (category) => setDeleting(category),
can,
}); });
return ( return (
@ -98,15 +101,17 @@ export default function CategoryIndex({ categories, highlight }: Props) {
) )
} }
actions={ actions={
<Button asChild> can('categories.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -12,12 +12,13 @@ export type Customer = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (customer: Customer) => void; handleEdit: (customer: Customer) => void;
handleDeleteClick: (customer: Customer) => void; handleDeleteClick: (customer: Customer) => void;
can: (permission: string) => boolean;
}; };
export function createCustomerColumns( export function createCustomerColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<Customer>[] { ): ColumnDef<Customer>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -61,6 +62,7 @@ export function createCustomerColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('customers.update'),
onClick: () => handleEdit(customer), onClick: () => handleEdit(customer),
}, },
{ {
@ -68,6 +70,7 @@ export function createCustomerColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('customers.delete'),
onClick: () => handleDeleteClick(customer), onClick: () => handleDeleteClick(customer),
}, },
]} ]}

View File

@ -11,6 +11,7 @@ import { PhoneNumberInput } from '@/components/phone-number-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -32,6 +33,7 @@ type Props = {
}; };
export default function CustomerIndex({ customers }: Props) { export default function CustomerIndex({ customers }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Customer | null>(null); const [editing, setEditing] = useState<Customer | null>(null);
const [deleting, setDeleting] = useState<Customer | null>(null); const [deleting, setDeleting] = useState<Customer | null>(null);
@ -66,6 +68,7 @@ export default function CustomerIndex({ customers }: Props) {
const columns = createCustomerColumns({ const columns = createCustomerColumns({
handleEdit: (customer) => setEditing(customer), handleEdit: (customer) => setEditing(customer),
handleDeleteClick: (customer) => setDeleting(customer), handleDeleteClick: (customer) => setDeleting(customer),
can,
}); });
return ( return (
@ -76,15 +79,17 @@ export default function CustomerIndex({ customers }: Props) {
<PageHeader <PageHeader
title="Customer" title="Customer"
actions={ actions={
<Button asChild> can('customers.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -75,6 +75,7 @@ type CreateColumnsParams = {
variant: ProductVariant, variant: ProductVariant,
) => void; ) => void;
toggleStatusUrl: (id: number) => string; toggleStatusUrl: (id: number) => string;
can: (permission: string) => boolean;
}; };
export function createProductColumns( export function createProductColumns(
@ -86,6 +87,7 @@ export function createProductColumns(
handleVariantEdit, handleVariantEdit,
handleVariantDeleteClick, handleVariantDeleteClick,
toggleStatusUrl, toggleStatusUrl,
can,
} = params; } = params;
return [ return [
@ -352,6 +354,7 @@ export function createProductColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('products.update'),
onClick: () => handleEdit(product), onClick: () => handleEdit(product),
}, },
{ {
@ -359,6 +362,7 @@ export function createProductColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('products.delete'),
onClick: () => handleDeleteClick(product), onClick: () => handleDeleteClick(product),
}, },
]} ]}

View File

@ -20,6 +20,7 @@ import {
SelectTrigger, SelectTrigger,
SelectValue, SelectValue,
} from '@/components/ui/select'; } from '@/components/ui/select';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -32,7 +33,7 @@ import {
destroy as variantDestroy, destroy as variantDestroy,
edit as variantEdit, edit as variantEdit,
} from '@/routes/admin/master/products/variants'; } from '@/routes/admin/master/products/variants';
import { Head, router } from '@inertiajs/react'; import { Head } from '@inertiajs/react';
import { Link, Plus } from 'lucide-react'; import { Link, Plus } from 'lucide-react';
import { useMemo, useState } from 'react'; import { useMemo, useState } from 'react';
import type { Product, ProductVariant } from './columns'; import type { Product, ProductVariant } from './columns';
@ -60,6 +61,7 @@ type Props = {
}; };
export default function ProductIndex({ products, categories, filters }: Props) { export default function ProductIndex({ products, categories, filters }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Product | null>(null); const [deleting, setDeleting] = useState<Product | null>(null);
const [deletingVariant, setDeletingVariant] = useState<{ const [deletingVariant, setDeletingVariant] = useState<{
product: Product; product: Product;
@ -249,12 +251,14 @@ export default function ProductIndex({ products, categories, filters }: Props) {
<PageHeader <PageHeader
title="Produk" title="Produk"
actions={ actions={
<Button asChild> can('products.create') ? (
<Link href={productCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={productCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { RowActions } from '@/components/row-actions';
import { ToggleStatus } from '@/components/toggle-status'; import { ToggleStatus } from '@/components/toggle-status';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import type { Product } from './columns'; import type { Product } from './columns';
@ -45,6 +46,7 @@ export function ProductCardRow({
onDelete, onDelete,
toggleStatusUrl, toggleStatusUrl,
}: ProductCardRowParams) { }: ProductCardRowParams) {
const { can } = useCan();
const variants = product.product_variants ?? []; const variants = product.product_variants ?? [];
const totalStock = variants.reduce((sum, v) => sum + (v.stock ?? 0), 0); const totalStock = variants.reduce((sum, v) => sum + (v.stock ?? 0), 0);
const totalReject = variants.reduce( const totalReject = variants.reduce(
@ -147,6 +149,7 @@ export function ProductCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('products.update'),
onClick: () => onEdit(product), onClick: () => onEdit(product),
}, },
{ {
@ -154,6 +157,7 @@ export function ProductCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('products.delete'),
onClick: () => onDelete(product), onClick: () => onDelete(product),
}, },
]} ]}

View File

@ -11,6 +11,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { useCan } from '@/hooks/use-can';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import { stockMutations } from '@/routes/admin/master/products/variants'; import { stockMutations } from '@/routes/admin/master/products/variants';
@ -26,6 +27,7 @@ export function VariantSubRow({
onEditVariant: (product: Product, variant: ProductVariant) => void; onEditVariant: (product: Product, variant: ProductVariant) => void;
onDeleteVariantClick: (product: Product, variant: ProductVariant) => void; onDeleteVariantClick: (product: Product, variant: ProductVariant) => void;
}) { }) {
const { can } = useCan();
const variants = product.product_variants ?? []; const variants = product.product_variants ?? [];
const [transferVariant, setTransferVariant] = useState<{ const [transferVariant, setTransferVariant] = useState<{
product: Product; product: Product;
@ -122,6 +124,7 @@ export function VariantSubRow({
icon: ( icon: (
<ArrowRightLeft className="h-4 w-4" /> <ArrowRightLeft className="h-4 w-4" />
), ),
show: can('stocks.view'),
onClick: () => onClick: () =>
setTransferVariant({ setTransferVariant({
product, product,
@ -133,6 +136,7 @@ export function VariantSubRow({
icon: ( icon: (
<ScrollText className="h-4 w-4" /> <ScrollText className="h-4 w-4" />
), ),
show: can('stocks.view'),
onClick: () => { onClick: () => {
router.visit( router.visit(
stockMutations.url({ stockMutations.url({
@ -147,6 +151,7 @@ export function VariantSubRow({
icon: ( icon: (
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
), ),
show: can('products.update'),
onClick: () => onClick: () =>
onEditVariant( onEditVariant(
product, product,
@ -158,6 +163,7 @@ export function VariantSubRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('products.delete'),
onClick: () => onClick: () =>
onDeleteVariantClick( onDeleteVariantClick(
product, product,

View File

@ -28,6 +28,7 @@ import { useState } from 'react';
import type { RawMaterial, RawMaterialVariant } from './columns'; import type { RawMaterial, RawMaterialVariant } from './columns';
import { RawMaterialCardRow } from './raw-material-card'; import { RawMaterialCardRow } from './raw-material-card';
import { RawMaterialVariantSubRow } from './variant/sub-row'; import { RawMaterialVariantSubRow } from './variant/sub-row';
import { useCan } from '@/hooks/use-can';
type Props = { type Props = {
rawMaterials: { rawMaterials: {
@ -44,6 +45,7 @@ type Props = {
}; };
export default function RawMaterialIndex({ rawMaterials, filters }: Props) { export default function RawMaterialIndex({ rawMaterials, filters }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<RawMaterial | null>(null); const [deleting, setDeleting] = useState<RawMaterial | null>(null);
const [deletingVariant, setDeletingVariant] = useState<{ const [deletingVariant, setDeletingVariant] = useState<{
rawMaterial: RawMaterial; rawMaterial: RawMaterial;
@ -154,12 +156,14 @@ export default function RawMaterialIndex({ rawMaterials, filters }: Props) {
<PageHeader <PageHeader
title="Bahan Baku" title="Bahan Baku"
actions={ actions={
<Link href={rawMaterialCreate.url()}> can('raw_materials.create') ? (
<Button> <Link href={rawMaterialCreate.url()}>
<Plus className="h-4 w-4" /> <Button>
Tambah <Plus className="h-4 w-4" />
</Button> Tambah
</Link> </Button>
</Link>
) : undefined
} }
/> />

View File

@ -3,6 +3,7 @@ import { RowActions } from '@/components/row-actions';
import { ToggleStatus } from '@/components/toggle-status'; import { ToggleStatus } from '@/components/toggle-status';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card'; import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import type { RawMaterial } from './columns'; import type { RawMaterial } from './columns';
@ -26,6 +27,7 @@ export function RawMaterialCardRow({
onDelete, onDelete,
toggleStatusUrl, toggleStatusUrl,
}: RawMaterialCardRowParams) { }: RawMaterialCardRowParams) {
const { can } = useCan();
const variants = rawMaterial.raw_material_prices ?? []; const variants = rawMaterial.raw_material_prices ?? [];
const totalStock = variants.reduce( const totalStock = variants.reduce(
(sum, v) => sum + (Number(v.stock) || 0), (sum, v) => sum + (Number(v.stock) || 0),
@ -97,6 +99,7 @@ export function RawMaterialCardRow({
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('raw_materials.update'),
onClick: () => onEdit(rawMaterial), onClick: () => onEdit(rawMaterial),
}, },
{ {
@ -104,6 +107,7 @@ export function RawMaterialCardRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('raw_materials.delete'),
onClick: () => onDelete(rawMaterial), onClick: () => onDelete(rawMaterial),
}, },
]} ]}

View File

@ -12,6 +12,7 @@ import {
TableHeader, TableHeader,
TableRow, TableRow,
} from '@/components/ui/table'; } from '@/components/ui/table';
import { useCan } from '@/hooks/use-can';
import { formatNumber } from '@/lib/format'; import { formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils'; import { formatCurrency } from '@/lib/utils';
import { import {
@ -25,6 +26,7 @@ export function RawMaterialVariantSubRow({
}: { }: {
rawMaterial: RawMaterial; rawMaterial: RawMaterial;
}) { }) {
const { can } = useCan();
const variants = rawMaterial.raw_material_prices ?? []; const variants = rawMaterial.raw_material_prices ?? [];
const [deletingVariant, setDeletingVariant] = const [deletingVariant, setDeletingVariant] =
useState<RawMaterialVariant | null>(null); useState<RawMaterialVariant | null>(null);
@ -107,6 +109,7 @@ export function RawMaterialVariantSubRow({
icon: ( icon: (
<Pencil className="h-4 w-4" /> <Pencil className="h-4 w-4" />
), ),
show: can('raw_materials.update'),
onClick: () => { onClick: () => {
router.visit( router.visit(
variantEdit.url({ variantEdit.url({
@ -122,6 +125,7 @@ export function RawMaterialVariantSubRow({
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('raw_materials.delete'),
onClick: () => onClick: () =>
setDeletingVariant(variant), setDeletingVariant(variant),
}, },

View File

@ -12,12 +12,13 @@ export type Supplier = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (supplier: Supplier) => void; handleEdit: (supplier: Supplier) => void;
handleDeleteClick: (supplier: Supplier) => void; handleDeleteClick: (supplier: Supplier) => void;
can: (permission: string) => boolean;
}; };
export function createSupplierColumns( export function createSupplierColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<Supplier>[] { ): ColumnDef<Supplier>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -61,6 +62,7 @@ export function createSupplierColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('suppliers.update'),
onClick: () => handleEdit(supplier), onClick: () => handleEdit(supplier),
}, },
{ {
@ -68,6 +70,7 @@ export function createSupplierColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('suppliers.delete'),
onClick: () => handleDeleteClick(supplier), onClick: () => handleDeleteClick(supplier),
}, },
]} ]}

View File

@ -11,6 +11,7 @@ import { PhoneNumberInput } from '@/components/phone-number-input';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label'; import { Label } from '@/components/ui/label';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
destroy, destroy,
@ -32,6 +33,7 @@ type Props = {
}; };
export default function SupplierIndex({ suppliers }: Props) { export default function SupplierIndex({ suppliers }: Props) {
const { can } = useCan();
const [createOpen, setCreateOpen] = useState(false); const [createOpen, setCreateOpen] = useState(false);
const [editing, setEditing] = useState<Supplier | null>(null); const [editing, setEditing] = useState<Supplier | null>(null);
const [deleting, setDeleting] = useState<Supplier | null>(null); const [deleting, setDeleting] = useState<Supplier | null>(null);
@ -66,6 +68,7 @@ export default function SupplierIndex({ suppliers }: Props) {
const columns = createSupplierColumns({ const columns = createSupplierColumns({
handleEdit: (supplier) => setEditing(supplier), handleEdit: (supplier) => setEditing(supplier),
handleDeleteClick: (supplier) => setDeleting(supplier), handleDeleteClick: (supplier) => setDeleting(supplier),
can,
}); });
return ( return (
@ -76,15 +79,17 @@ export default function SupplierIndex({ suppliers }: Props) {
<PageHeader <PageHeader
title="Supplier" title="Supplier"
actions={ actions={
<Button asChild> can('suppliers.create') ? (
<button <Button asChild>
type="button" <button
onClick={() => setCreateOpen(true)} type="button"
> onClick={() => setCreateOpen(true)}
<Plus className="h-4 w-4" /> >
Tambah <Plus className="h-4 w-4" />
</button> Tambah
</Button> </button>
</Button>
) : undefined
} }
/> />

View File

@ -11,12 +11,13 @@ export type Role = {
type CreateColumnsParams = { type CreateColumnsParams = {
handleEdit: (role: Role) => void; handleEdit: (role: Role) => void;
handleDeleteClick: (role: Role) => void; handleDeleteClick: (role: Role) => void;
can: (permission: string) => boolean;
}; };
export function createRoleColumns( export function createRoleColumns(
params: CreateColumnsParams, params: CreateColumnsParams,
): ColumnDef<Role>[] { ): ColumnDef<Role>[] {
const { handleEdit, handleDeleteClick } = params; const { handleEdit, handleDeleteClick, can } = params;
return [ return [
{ {
@ -59,6 +60,7 @@ export function createRoleColumns(
{ {
label: 'Edit', label: 'Edit',
icon: <Pencil className="h-4 w-4" />, icon: <Pencil className="h-4 w-4" />,
show: can('roles.update'),
onClick: () => handleEdit(role), onClick: () => handleEdit(role),
}, },
{ {
@ -66,6 +68,7 @@ export function createRoleColumns(
icon: ( icon: (
<Trash2 className="h-4 w-4 text-destructive" /> <Trash2 className="h-4 w-4 text-destructive" />
), ),
show: can('roles.delete'),
onClick: () => handleDeleteClick(role), onClick: () => handleDeleteClick(role),
}, },
]} ]}

View File

@ -6,6 +6,7 @@ import { DataTable } from '@/components/data-table';
import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog'; import { DeleteConfirmDialog } from '@/components/delete-confirm-dialog';
import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table'; import { useServerTable } from '@/hooks/use-server-table';
import { import {
index as rolesIndex, index as rolesIndex,
@ -27,6 +28,7 @@ type Props = {
}; };
export default function RoleIndex({ roles }: Props) { export default function RoleIndex({ roles }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Role | null>(null); const [deleting, setDeleting] = useState<Role | null>(null);
const pagination: PaginationState = { const pagination: PaginationState = {
@ -61,6 +63,7 @@ export default function RoleIndex({ roles }: Props) {
router.visit(roleEdit.url(role.id)); router.visit(roleEdit.url(role.id));
}, },
handleDeleteClick: (role) => setDeleting(role), handleDeleteClick: (role) => setDeleting(role),
can,
}); });
return ( return (
@ -71,12 +74,14 @@ export default function RoleIndex({ roles }: Props) {
<PageHeader <PageHeader
title="Role & Permission" title="Role & Permission"
actions={ actions={
<Button asChild> can('roles.create') ? (
<Link href={roleCreate.url()}> <Button asChild>
<Plus className="h-4 w-4" /> <Link href={roleCreate.url()}>
Tambah <Plus className="h-4 w-4" />
</Link> Tambah
</Button> </Link>
</Button>
) : undefined
} }
/> />