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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,6 +3,7 @@ import { ImagePreviewButton } from '@/components/image-preview-button';
import { RowActions } from '@/components/row-actions';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils';
import type { Cutting } from './columns';
@ -24,6 +25,7 @@ export function CuttingCardRow({
onEdit,
onDelete,
}: CuttingCardRowParams) {
const { can } = useCan();
const items = cutting.cutting_materials ?? [];
const result = cutting.cutting_results?.[0];
const materialCount = items.length;
@ -126,6 +128,7 @@ export function CuttingCardRow({
{
label: 'Edit',
icon: <Pencil className="h-4 w-4" />,
show: can('cuttings.update'),
onClick: () => onEdit(cutting),
},
{
@ -133,6 +136,7 @@ export function CuttingCardRow({
icon: (
<Trash2 className="h-4 w-4 text-destructive" />
),
show: can('cuttings.delete'),
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 { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table';
import {
destroy,
@ -28,6 +29,7 @@ type Props = {
};
export default function CuttingIndex({ cuttings }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Cutting | null>(null);
const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function CuttingIndex({ cuttings }: Props) {
<PageHeader
title="Cutting"
actions={
<Button asChild>
<Link href={cuttingCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</Link>
</Button>
can('cuttings.create') ? (
<Button asChild>
<Link href={cuttingCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</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 { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table';
import {
destroy,
@ -28,6 +29,7 @@ type Props = {
};
export default function PurchaseIndex({ purchases }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Purchase | null>(null);
const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function PurchaseIndex({ purchases }: Props) {
<PageHeader
title="Belanja"
actions={
<Button asChild>
<Link href={purchaseCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</Link>
</Button>
can('purchases.create') ? (
<Button asChild>
<Link href={purchaseCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</Link>
</Button>
) : undefined
}
/>

View File

@ -3,6 +3,7 @@ import { ImagePreviewButton } from '@/components/image-preview-button';
import { RowActions } from '@/components/row-actions';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useCan } from '@/hooks/use-can';
import { formatDateTime, formatNumber } from '@/lib/format';
import { formatCurrency } from '@/lib/utils';
import type { Purchase } from './columns';
@ -24,6 +25,7 @@ export function PurchaseCardRow({
onEdit,
onDelete,
}: PurchaseCardRowParams) {
const { can } = useCan();
const items = purchase.purchase_items ?? [];
const variantCount = items.length;
const rawMaterialName =
@ -129,6 +131,7 @@ export function PurchaseCardRow({
{
label: 'Edit',
icon: <Pencil className="h-4 w-4" />,
show: can('purchases.update'),
onClick: () => onEdit(purchase),
},
{
@ -136,6 +139,7 @@ export function PurchaseCardRow({
icon: (
<Trash2 className="h-4 w-4 text-destructive" />
),
show: can('purchases.delete'),
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 { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import { useCan } from '@/hooks/use-can';
import { useServerTable } from '@/hooks/use-server-table';
import {
destroy,
@ -28,6 +29,7 @@ type Props = {
};
export default function RestockIndex({ restocks }: Props) {
const { can } = useCan();
const [deleting, setDeleting] = useState<Restock | null>(null);
const expand = useCardTableExpand(true);
@ -66,12 +68,14 @@ export default function RestockIndex({ restocks }: Props) {
<PageHeader
title="Restock"
actions={
<Button asChild>
<Link href={restockCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</Link>
</Button>
can('restocks.create') ? (
<Button asChild>
<Link href={restockCreate.url()}>
<Plus className="h-4 w-4" />
Tambah
</Link>
</Button>
) : undefined
}
/>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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