diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php
index 9ffc373..77ac6a3 100644
--- a/app/Http/Middleware/HandleInertiaRequests.php
+++ b/app/Http/Middleware/HandleInertiaRequests.php
@@ -39,7 +39,9 @@ public function share(Request $request): array
...parent::share($request),
'name' => config('app.name'),
'auth' => [
- 'user' => $request->user()?->load('profile'),
+ 'user' => $request->user() ? $request->user()->load('profile') : null,
+ 'roles' => $request->user() ? $request->user()->getRoleNames() : [],
+ 'permissions' => $request->user() ? $request->user()->getAllPermissions()->pluck('name') : [],
],
'sidebarOpen' => ! $request->hasCookie('sidebar_state') || $request->cookie('sidebar_state') === 'true',
'flash' => [
diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx
index 7f84a1d..b258612 100644
--- a/resources/js/components/app-sidebar.tsx
+++ b/resources/js/components/app-sidebar.tsx
@@ -21,17 +21,21 @@ import purchase from '@/routes/purchase';
import system from '@/routes/system';
import user from '@/routes/user';
import type { NavItem } from '@/types';
+import { usePage } from '@inertiajs/react';
+import type { Auth } from '@/types/auth';
const mainNavItems: NavItem[] = [
{
title: 'Dasbor',
href: dashboard().url,
icon: LayoutGrid,
+ permission: 'View:Dashboard',
},
{
title: 'Analisa',
href: analysis().url,
icon: BarChart3,
+ permission: 'View:Analysis',
},
];
@@ -40,16 +44,19 @@ const masterNavItems: NavItem[] = [
title: 'Pegawai',
href: user.index().url,
icon: User,
+ permission: 'View:User',
},
{
title: 'Kategori',
href: category.index().url,
icon: List,
+ permission: 'View:Category',
},
{
title: 'Produk',
href: product.index().url,
icon: Boxes,
+ permission: 'View:Product',
},
];
@@ -58,11 +65,13 @@ const manageNavItems: NavItem[] = [
title: 'Pesanan',
href: order.index().url,
icon: ShoppingBag,
+ permission: 'View:Order',
},
{
title: 'Belanja',
href: purchase.index().url,
icon: ShoppingCart,
+ permission: 'View:Purchase',
},
];
@@ -71,11 +80,13 @@ const financeNavItems: NavItem[] = [
title: 'Pengeluaran',
href: expense.index().url,
icon: Wallet,
+ permission: 'View:Expense',
},
{
title: 'Penggajian',
href: payroll.index().url,
icon: DollarSign,
+ permission: 'View:Payroll',
},
];
@@ -84,20 +95,32 @@ const systemNavItems: NavItem[] = [
title: 'Pengaturan',
href: system.settings.index().url,
icon: Settings,
+ permission: 'View:Setting',
},
{
title: 'Logs',
href: system.logs.index().url,
icon: ScrollText,
+ permission: 'View:Log',
},
{
title: 'Riwayat',
href: system.activityLogs.index().url,
icon: History,
+ permission: 'View:Activity',
},
];
export function AppSidebar() {
+ const { auth } = usePage<{auth: Auth}>().props;
+
+ const filterNavItems = (items: NavItem[]) => {
+ return items.filter(item => {
+ if (!item.permission) return true;
+ return auth.permissions.includes(item.permission) || auth.roles.includes('Developer');
+ });
+ };
+
return (
@@ -113,11 +136,11 @@ export function AppSidebar() {
-
-
-
-
-
+ {filterNavItems(mainNavItems).length > 0 && }
+ {filterNavItems(masterNavItems).length > 0 && }
+ {filterNavItems(manageNavItems).length > 0 && }
+ {filterNavItems(financeNavItems).length > 0 && }
+ {filterNavItems(systemNavItems).length > 0 && }
);
diff --git a/resources/js/hooks/use-permission.ts b/resources/js/hooks/use-permission.ts
new file mode 100644
index 0000000..1e1ce5c
--- /dev/null
+++ b/resources/js/hooks/use-permission.ts
@@ -0,0 +1,17 @@
+import { usePage } from '@inertiajs/react';
+import type { Auth } from '@/types/auth';
+
+export function usePermission() {
+ const { auth } = usePage<{auth: Auth}>().props;
+
+ const hasPermission = (permission: string) => {
+ if (auth.roles.includes('Developer')) return true;
+ return auth.permissions.includes(permission);
+ };
+
+ const hasRole = (role: string) => {
+ return auth.roles.includes(role);
+ };
+
+ return { hasPermission, hasRole };
+}
diff --git a/resources/js/pages/admin/finance/expense/index.tsx b/resources/js/pages/admin/finance/expense/index.tsx
index 7a09fb4..f3010ed 100644
--- a/resources/js/pages/admin/finance/expense/index.tsx
+++ b/resources/js/pages/admin/finance/expense/index.tsx
@@ -10,8 +10,10 @@ import type { Expense } from '@/types';
import { useExpenseIndex } from './hooks/use-expense-index';
import { getColumns } from './partials/columns';
import { ExpenseFormModal } from './partials/expense-form-modal';
+import { usePermission } from '@/hooks/use-permission';
export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
+ const { hasPermission } = usePermission();
const {
selectedProof,
isFormOpen,
@@ -35,7 +37,9 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
closeImagePreview
} = useExpenseIndex();
- const columns = getColumns({ onEdit, onDelete, onPreviewImage });
+ const columns = getColumns({ onEdit, onDelete, onPreviewImage }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Expense') || hasPermission('Delete:Expense')
+ );
return (
@@ -45,10 +49,12 @@ export default function ExpenseIndex({ expenses }: { expenses: Expense[] }) {
Pengeluaran
-
+ {hasPermission('Create:Expense') && (
+
+ )}
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Expense')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/finance/expense/partials/columns.tsx b/resources/js/pages/admin/finance/expense/partials/columns.tsx
index 514f815..6e152e6 100644
--- a/resources/js/pages/admin/finance/expense/partials/columns.tsx
+++ b/resources/js/pages/admin/finance/expense/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import type { ColumnDef } from '@tanstack/react-table';
import { Pencil, Trash2, ImagePlus } from 'lucide-react';
import { DataTableColumnHeader } from '@/components/data-table-column-header';
@@ -68,29 +69,34 @@ export const getColumns = ({ onEdit, onDelete, onPreviewImage }: ColumnProps): C
header: "Aksi",
cell: ({ row }) => {
const expense = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
-
-
-
- Ubah
-
-
-
-
-
-
-
- Hapus
-
-
+ {hasPermission('Edit:Expense') && (
+
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:Expense') && (
+
+
+
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/finance/payroll/index.tsx b/resources/js/pages/admin/finance/payroll/index.tsx
index 45a53f0..a8add24 100644
--- a/resources/js/pages/admin/finance/payroll/index.tsx
+++ b/resources/js/pages/admin/finance/payroll/index.tsx
@@ -12,6 +12,7 @@ import type { Payroll } from '@/types';
import { usePayrollIndex } from './hooks/use-payroll-index';
import { getColumns } from './partials/columns';
import { PayrollFormModal } from './partials/payroll-form-modal';
+import { usePermission } from '@/hooks/use-permission';
export default function PayrollIndex({
payrolls,
@@ -20,6 +21,7 @@ export default function PayrollIndex({
payrolls: Payroll[],
currentMonthPayrolls: Payroll[],
}) {
+ const { hasPermission } = usePermission();
const {
isFormOpen,
selectedPayroll,
@@ -42,7 +44,9 @@ export default function PayrollIndex({
closeForm,
} = usePayrollIndex();
- const columns = getColumns({ onEdit, onDelete, onTogglePaid });
+ const columns = getColumns({ onEdit, onDelete, onTogglePaid }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:PayrollAdjustment') || hasPermission('Delete:Payroll')
+ );
return (
@@ -52,10 +56,12 @@ export default function PayrollIndex({
Penggajian
-
+ {hasPermission('Generate:Payroll') && (
+
+ )}
@@ -64,7 +70,11 @@ export default function PayrollIndex({
onEdit(payroll)}
+ onClick={() => {
+ if (hasPermission('Edit:PayrollAdjustment')) {
+ onEdit(payroll);
+ }
+ }}
>
@@ -111,6 +121,7 @@ export default function PayrollIndex({
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Payroll')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/finance/payroll/partials/columns.tsx b/resources/js/pages/admin/finance/payroll/partials/columns.tsx
index 99fd6b7..3194f07 100644
--- a/resources/js/pages/admin/finance/payroll/partials/columns.tsx
+++ b/resources/js/pages/admin/finance/payroll/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import type { ColumnDef } from '@tanstack/react-table';
import { PencilRuler, Trash2 } from 'lucide-react';
import { DataTableColumnHeader } from '@/components/data-table-column-header';
@@ -72,12 +73,14 @@ export const getColumns = ({ onEdit, onDelete, onTogglePaid }: ColumnProps): Col
header: "Status",
cell: ({ row }) => {
const payroll = row.original;
+ const { hasPermission } = usePermission();
return (
onTogglePaid(payroll.id)}
+ disabled={!hasPermission('Pay:Payroll')}
/>
{
const payroll = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
-
-
-
- Ubah / Sesuaikan
-
-
-
-
-
-
-
- Hapus
-
-
+ {hasPermission('Edit:PayrollAdjustment') && (
+
+
+
+
+
+ Ubah / Sesuaikan
+
+
+ )}
+ {hasPermission('Delete:Payroll') && (
+
+
+
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/manage/order/index.tsx b/resources/js/pages/admin/manage/order/index.tsx
index ce7484b..48bd485 100644
--- a/resources/js/pages/admin/manage/order/index.tsx
+++ b/resources/js/pages/admin/manage/order/index.tsx
@@ -11,8 +11,10 @@ import { useOrderIndex } from './hooks/use-order-index';
import { useOrderPrint } from './hooks/use-order-print';
import { getColumns } from './partials/columns';
import { PrinterSettings } from './partials/printer-settings';
+import { usePermission } from '@/hooks/use-permission';
export default function OrderIndex({ orders }: { orders: Order[] }) {
+ const { hasPermission } = usePermission();
const {
isDeleteDialogOpen,
isBulkDeleteDialogOpen,
@@ -39,7 +41,9 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
handlePrint
} = useOrderPrint();
- const columns = getColumns({ onDelete, onPrint: handlePrint });
+ const columns = getColumns({ onDelete, onPrint: handlePrint }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Order') || hasPermission('Delete:Order')
+ );
return (
@@ -60,12 +64,14 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
onPaperSizeChange={setPaperSize}
/>
-
-
-
+ {hasPermission('Create:Order') && (
+
+
+
+ )}
@@ -74,19 +80,24 @@ export default function OrderIndex({ orders }: { orders: Order[] }) {
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Order')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/manage/order/partials/columns.tsx b/resources/js/pages/admin/manage/order/partials/columns.tsx
index dd1e292..40efdd2 100644
--- a/resources/js/pages/admin/manage/order/partials/columns.tsx
+++ b/resources/js/pages/admin/manage/order/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import { Link } from '@inertiajs/react';
import type { ColumnDef } from '@tanstack/react-table';
import { format } from 'date-fns';
@@ -100,6 +101,7 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef
header: "Aksi",
cell: ({ row }) => {
const order = row.original;
+ const { hasPermission } = usePermission();
return (
@@ -113,28 +115,32 @@ export const getColumns = ({ onDelete, onPrint }: ColumnProps): ColumnDef
Cetak Struk
-
-
-
-
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/manage/purchase/index.tsx b/resources/js/pages/admin/manage/purchase/index.tsx
index 2918f8a..4973355 100644
--- a/resources/js/pages/admin/manage/purchase/index.tsx
+++ b/resources/js/pages/admin/manage/purchase/index.tsx
@@ -12,8 +12,10 @@ import purchaseRoutes from '@/routes/purchase';
import type { Purchase } from '@/types';
import { usePurchaseIndex } from './hooks/use-purchase-index';
import { getColumns } from './partials/columns';
+import { usePermission } from '@/hooks/use-permission';
export default function PurchaseIndex({ purchases }: { purchases: Purchase[] }) {
+ const { hasPermission } = usePermission();
const {
isDeleteDialogOpen,
isBulkDeleteDialogOpen,
@@ -29,7 +31,9 @@ export default function PurchaseIndex({ purchases }: { purchases: Purchase[] })
confirmBulkDelete,
} = usePurchaseIndex();
- const columns = getColumns({ onDelete });
+ const columns = getColumns({ onDelete }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Purchase') || hasPermission('Delete:Purchase')
+ );
return (
@@ -39,12 +43,14 @@ export default function PurchaseIndex({ purchases }: { purchases: Purchase[] })
Belanja
-
-
-
- Tambah
-
-
+ {hasPermission('Create:Purchase') && (
+
+
+
+ Tambah
+
+
+ )}
@@ -52,19 +58,24 @@ export default function PurchaseIndex({ purchases }: { purchases: Purchase[] })
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Purchase')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/manage/purchase/partials/columns.tsx b/resources/js/pages/admin/manage/purchase/partials/columns.tsx
index 79a9da5..b38586d 100644
--- a/resources/js/pages/admin/manage/purchase/partials/columns.tsx
+++ b/resources/js/pages/admin/manage/purchase/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import { Link } from '@inertiajs/react';
import type { ColumnDef } from '@tanstack/react-table';
import { format } from 'date-fns';
@@ -81,31 +82,36 @@ export const getColumns = ({ onDelete }: ColumnProps): ColumnDef[] =>
header: "Aksi",
cell: ({ row }) => {
const purchase = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
-
-
-
+ {hasPermission('Edit:Purchase') && (
+
+
+
+
+
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:Purchase') && (
+
+
+ onDelete(purchase)}>
+
-
-
-
- Ubah
-
-
-
-
- onDelete(purchase)}>
-
-
-
-
- Hapus
-
-
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/master/category/index.tsx b/resources/js/pages/admin/master/category/index.tsx
index 1bc1c14..ce66000 100644
--- a/resources/js/pages/admin/master/category/index.tsx
+++ b/resources/js/pages/admin/master/category/index.tsx
@@ -9,8 +9,10 @@ import type { Category } from '@/types';
import { useCategoryIndex } from './hooks/use-category-index';
import { CategoryFormModal } from './partials/category-form-modal';
import { getColumns } from './partials/columns';
+import { usePermission } from '@/hooks/use-permission';
export default function CategoryIndex({ categories }: { categories: Category[] }) {
+ const { hasPermission } = usePermission();
const {
isFormOpen,
selectedCategory,
@@ -32,7 +34,9 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
closeForm,
} = useCategoryIndex();
- const columns = getColumns({ onEdit, onDelete, onToggleStatus });
+ const columns = getColumns({ onEdit, onDelete, onToggleStatus }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Category') || hasPermission('Delete:Category')
+ );
return (
@@ -42,10 +46,12 @@ export default function CategoryIndex({ categories }: { categories: Category[] }
Kategori
-
-
- Tambah
-
+ {hasPermission('Create:Category') && (
+
+
+ Tambah
+
+ )}
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Category')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/master/category/partials/columns.tsx b/resources/js/pages/admin/master/category/partials/columns.tsx
index 7dbf3a2..b611c34 100644
--- a/resources/js/pages/admin/master/category/partials/columns.tsx
+++ b/resources/js/pages/admin/master/category/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import type { ColumnDef } from '@tanstack/react-table';
import { Pencil, Trash2 } from 'lucide-react';
import { DataTableColumnHeader } from '@/components/data-table-column-header';
@@ -26,11 +27,13 @@ export const getColumns = ({ onEdit, onDelete, onToggleStatus }: ColumnProps): C
meta: { title: "Status" },
cell: ({ row }) => {
const category = row.original;
+ const { hasPermission } = usePermission();
return (
onToggleStatus(category.id)}
+ disabled={!hasPermission('ToggleStatus:Category')}
/>
);
}
@@ -40,39 +43,44 @@ export const getColumns = ({ onEdit, onDelete, onToggleStatus }: ColumnProps): C
header: "Aksi",
cell: ({ row }) => {
const category = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
- onEdit(category)}
- >
-
-
-
-
- Ubah
-
-
-
-
- onDelete(category)}
- >
-
-
-
-
- Hapus
-
-
+ {hasPermission('Edit:Category') && (
+
+
+ onEdit(category)}
+ >
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:Category') && (
+
+
+ onDelete(category)}
+ >
+
+
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/master/product/index.tsx b/resources/js/pages/admin/master/product/index.tsx
index 28bb2fd..12c9ed0 100644
--- a/resources/js/pages/admin/master/product/index.tsx
+++ b/resources/js/pages/admin/master/product/index.tsx
@@ -10,8 +10,10 @@ import type { Product, Category } from '@/types';
import { ImagePreviewDialog } from '../../../../components/modal/image-preview';
import { useProductIndex } from './hooks/use-product-index';
import { getColumns } from './partials/columns';
+import { usePermission } from '@/hooks/use-permission';
export default function ProductIndex({ products, categories }: { products: Product[], categories: Category[] }) {
+ const { hasPermission } = usePermission();
const {
selectedImage,
isDeleteDialogOpen,
@@ -31,7 +33,9 @@ export default function ProductIndex({ products, categories }: { products: Produ
closeImagePreview,
} = useProductIndex();
- const columns = getColumns({ onDelete, onToggleStatus, onPreviewImage });
+ const columns = getColumns({ onDelete, onToggleStatus, onPreviewImage }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Product') || hasPermission('Delete:Product')
+ );
return (
@@ -41,12 +45,14 @@ export default function ProductIndex({ products, categories }: { products: Produ
Produk
-
-
-
- Tambah
-
-
+ {hasPermission('Create:Product') && (
+
+
+
+ Tambah
+
+
+ )}
@@ -54,6 +60,7 @@ export default function ProductIndex({ products, categories }: { products: Produ
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:Product')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/master/product/partials/columns.tsx b/resources/js/pages/admin/master/product/partials/columns.tsx
index c31d695..ccbb92e 100644
--- a/resources/js/pages/admin/master/product/partials/columns.tsx
+++ b/resources/js/pages/admin/master/product/partials/columns.tsx
@@ -1,3 +1,4 @@
+import { usePermission } from '@/hooks/use-permission';
import { Link } from '@inertiajs/react';
import type { ColumnDef } from '@tanstack/react-table';
import { Pencil, Trash2, ImagePlus } from 'lucide-react';
@@ -52,8 +53,8 @@ export const getColumns = ({ onDelete, onToggleStatus, onPreviewImage }: ColumnP
const categories = row.getValue(id) as Category[];
if (!categories) {
-return false;
-}
+ return false;
+ }
return categories.some(cat => String(cat.id) === String(value));
},
@@ -124,11 +125,13 @@ return false;
meta: { title: "Status" },
cell: ({ row }) => {
const product = row.original;
+ const { hasPermission } = usePermission();
return (
onToggleStatus(product.id)}
+ disabled={!hasPermission('ToggleStatus:Product')}
/>
);
}
@@ -138,31 +141,36 @@ return false;
header: "Aksi",
cell: ({ row }) => {
const product = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
-
-
-
+ {hasPermission('Edit:Product') && (
+
+
+
+
+
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:Product') && (
+
+
+ onDelete(product)}>
+
-
-
-
- Ubah
-
-
-
-
- onDelete(product)}>
-
-
-
-
- Hapus
-
-
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/pages/admin/master/user/index.tsx b/resources/js/pages/admin/master/user/index.tsx
index ef829fc..6b323b1 100644
--- a/resources/js/pages/admin/master/user/index.tsx
+++ b/resources/js/pages/admin/master/user/index.tsx
@@ -1,5 +1,3 @@
-import { Head, Link } from '@inertiajs/react';
-import { Trash2, KeyRound, Plus, X } from 'lucide-react';
import { DataTable } from '@/components/data-table';
import { DeleteConfirmation } from '@/components/modal/delete-confirmation';
import {
@@ -12,16 +10,20 @@ import {
AlertDialogHeader,
AlertDialogMedia,
AlertDialogTitle,
-} from "@/components/ui/alert-dialog"
+} from "@/components/ui/alert-dialog";
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import userRoutes from '@/routes/user';
import type { User } from '@/types';
+import { Head, Link } from '@inertiajs/react';
+import { KeyRound, Plus, Trash2, X } from 'lucide-react';
+import { usePermission } from '@/hooks/use-permission';
import { useUserIndex } from './hooks/use-user-index';
import { getColumns } from './partials/columns';
export default function UserIndex({ users, defaultPassword }: { users: User[], defaultPassword: string }) {
+ const { hasPermission } = usePermission();
const {
isDeleteDialogOpen,
isBulkDeleteDialogOpen,
@@ -42,7 +44,9 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
confirmBulkDelete,
} = useUserIndex();
- const columns = getColumns({ onResetPassword, onDelete });
+ const columns = getColumns({ onResetPassword, onDelete }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:User') || hasPermission('Delete:User') || hasPermission('ResetPassword:User')
+ );
return (
@@ -52,12 +56,14 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
Pegawai
-
-
-
- Tambah
-
-
+ {hasPermission('Create:User') && (
+
+
+
+ Tambah
+
+
+ )}
@@ -65,19 +71,24 @@ export default function UserIndex({ users, defaultPassword }: { users: User[], d
{
- setRowsToDelete(rows);
- setIsBulkDeleteDialogOpen(true);
- },
- icon: Trash2,
- variant: 'destructive'
- },
- ]}
+ bulkActions={
+ hasPermission('DeleteAny:User')
+ ? [
+ {
+ label: 'Hapus Terpilih',
+ onClick: (rows) => {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
/>
diff --git a/resources/js/pages/admin/master/user/partials/columns.tsx b/resources/js/pages/admin/master/user/partials/columns.tsx
index 275523e..77ad482 100644
--- a/resources/js/pages/admin/master/user/partials/columns.tsx
+++ b/resources/js/pages/admin/master/user/partials/columns.tsx
@@ -1,12 +1,13 @@
-import { Link } from '@inertiajs/react';
-import type { ColumnDef } from '@tanstack/react-table';
-import { KeyRound, Pencil, Trash2 } from 'lucide-react';
import { DataTableColumnHeader } from '@/components/data-table-column-header';
import { Button } from '@/components/ui/button';
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
import { UserInfo } from '@/components/user-info';
+import { usePermission } from '@/hooks/use-permission';
import userRoutes from '@/routes/user';
import type { User } from '@/types';
+import { Link } from '@inertiajs/react';
+import type { ColumnDef } from '@tanstack/react-table';
+import { KeyRound, Pencil, Trash2 } from 'lucide-react';
interface ColumnProps {
onResetPassword: (user: User) => void;
@@ -50,55 +51,62 @@ export const getColumns = ({ onResetPassword, onDelete }: ColumnProps): ColumnDe
header: "Aksi",
cell: ({ row }) => {
const user = row.original;
+ const { hasPermission } = usePermission();
return (
-
-
- onResetPassword(user)}
- >
-
-
-
-
- Reset Kata Sandi
-
-
-
-
-
+ {hasPermission('ResetPassword:User') && (
+
+
onResetPassword(user)}
>
-
+
-
-
-
- Ubah
-
-
-
-
- onDelete(user)}
- >
-
-
-
-
- Hapus
-
-
+
+
+ Reset Kata Sandi
+
+
+ )}
+ {hasPermission('Edit:User') && (
+
+
+
+
+
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:User') && (
+
+
+ onDelete(user)}
+ >
+
+
+
+
+ Hapus
+
+
+ )}
);
},
diff --git a/resources/js/types/auth.ts b/resources/js/types/auth.ts
index 8086a2b..b2e24cc 100644
--- a/resources/js/types/auth.ts
+++ b/resources/js/types/auth.ts
@@ -27,6 +27,8 @@ export type User = {
export type Auth = {
user: User;
+ roles: string[];
+ permissions: string[];
};
export type TwoFactorSetupData = {
diff --git a/resources/js/types/navigation.ts b/resources/js/types/navigation.ts
index dbf6f83..06b8f79 100644
--- a/resources/js/types/navigation.ts
+++ b/resources/js/types/navigation.ts
@@ -11,4 +11,5 @@ export type NavItem = {
href: NonNullable;
icon?: LucideIcon | null;
isActive?: boolean;
+ permission?: string;
};
diff --git a/routes/finance.php b/routes/finance.php
index 6e680f4..c3245c3 100644
--- a/routes/finance.php
+++ b/routes/finance.php
@@ -6,17 +6,17 @@
Route::middleware(['auth'])->group(function () {
Route::prefix('admin/finance')->group(function () {
- Route::get('expenses', [ExpenseController::class, 'index'])->name('expense.index');
- Route::post('expense/store', [ExpenseController::class, 'store'])->name('expense.store');
- Route::patch('expense/update/{expense}', [ExpenseController::class, 'update'])->name('expense.update');
- Route::delete('expense/destroy/{expense}', [ExpenseController::class, 'destroy'])->name('expense.destroy');
- Route::delete('expense/bulk-destroy', [ExpenseController::class, 'bulkDestroy'])->name('expense.bulkDestroy');
+ Route::get('expenses', [ExpenseController::class, 'index'])->name('expense.index')->middleware('can:View:Expense');
+ Route::post('expense/store', [ExpenseController::class, 'store'])->name('expense.store')->middleware('can:Create:Expense');
+ Route::patch('expense/update/{expense}', [ExpenseController::class, 'update'])->name('expense.update')->middleware('can:Edit:Expense');
+ Route::delete('expense/destroy/{expense}', [ExpenseController::class, 'destroy'])->name('expense.destroy')->middleware('can:Delete:Expense');
+ Route::delete('expense/bulk-destroy', [ExpenseController::class, 'bulkDestroy'])->name('expense.bulkDestroy')->middleware('can:DeleteAny:Expense');
- Route::get('payrolls', [PayrollController::class, 'index'])->name('payroll.index');
- Route::post('payroll/generate', [PayrollController::class, 'generate'])->name('payroll.generate');
- Route::patch('payroll/update/{payroll}', [PayrollController::class, 'update'])->name('payroll.update');
- Route::patch('payroll/toggle-paid/{payroll}', [PayrollController::class, 'togglePaid'])->name('payroll.togglePaid');
- Route::delete('payroll/destroy/{payroll}', [PayrollController::class, 'destroy'])->name('payroll.destroy');
- Route::delete('payroll/bulk-destroy', [PayrollController::class, 'bulkDestroy'])->name('payroll.bulkDestroy');
+ Route::get('payrolls', [PayrollController::class, 'index'])->name('payroll.index')->middleware('can:View:Payroll');
+ Route::post('payroll/generate', [PayrollController::class, 'generate'])->name('payroll.generate')->middleware('can:Generate:Payroll');
+ Route::patch('payroll/update/{payroll}', [PayrollController::class, 'update'])->name('payroll.update')->middleware('can:Edit:PayrollAdjustment');
+ Route::patch('payroll/toggle-paid/{payroll}', [PayrollController::class, 'togglePaid'])->name('payroll.togglePaid')->middleware('can:Pay:Payroll');
+ Route::delete('payroll/destroy/{payroll}', [PayrollController::class, 'destroy'])->name('payroll.destroy')->middleware('can:Delete:Payroll');
+ Route::delete('payroll/bulk-destroy', [PayrollController::class, 'bulkDestroy'])->name('payroll.bulkDestroy')->middleware('can:DeleteAny:Payroll');
});
});
diff --git a/routes/manage.php b/routes/manage.php
index 461b319..a8f3350 100644
--- a/routes/manage.php
+++ b/routes/manage.php
@@ -8,30 +8,30 @@
Route::middleware(['auth'])->group(function () {
Route::prefix('admin/manage')->group(function () {
- Route::get('purchases', [PurchaseController::class, 'index'])->name('purchase.index');
- Route::get('purchase/create', [PurchaseController::class, 'create'])->name('purchase.create');
- Route::post('purchase/store', [PurchaseController::class, 'store'])->name('purchase.store');
- Route::get('purchase/edit/{purchase}', [PurchaseController::class, 'edit'])->name('purchase.edit');
- Route::patch('purchase/update/{purchase}', [PurchaseController::class, 'update'])->name('purchase.update');
- Route::delete('purchase/destroy/{purchase}', [PurchaseController::class, 'destroy'])->name('purchase.destroy');
- Route::delete('purchase/bulk-destroy', [PurchaseController::class, 'bulkDestroy'])->name('purchase.bulkDestroy');
+ Route::get('purchases', [PurchaseController::class, 'index'])->name('purchase.index')->middleware('can:View:Purchase');
+ Route::get('purchase/create', [PurchaseController::class, 'create'])->name('purchase.create')->middleware('can:Create:Purchase');
+ Route::post('purchase/store', [PurchaseController::class, 'store'])->name('purchase.store')->middleware('can:Create:Purchase');
+ Route::get('purchase/edit/{purchase}', [PurchaseController::class, 'edit'])->name('purchase.edit')->middleware('can:Edit:Purchase');
+ Route::patch('purchase/update/{purchase}', [PurchaseController::class, 'update'])->name('purchase.update')->middleware('can:Edit:Purchase');
+ Route::delete('purchase/destroy/{purchase}', [PurchaseController::class, 'destroy'])->name('purchase.destroy')->middleware('can:Delete:Purchase');
+ Route::delete('purchase/bulk-destroy', [PurchaseController::class, 'bulkDestroy'])->name('purchase.bulkDestroy')->middleware('can:DeleteAny:Purchase');
// Cart routes
- Route::post('purchase/add-to-cart', [PurchaseCartController::class, 'addToCart'])->name('purchase.addToCart');
- Route::delete('purchase/remove-from-cart/{purchaseItem}', [PurchaseCartController::class, 'removeFromCart'])->name('purchase.removeFromCart');
- Route::patch('purchase/update-cart-item/{purchaseItem}', [PurchaseCartController::class, 'updateCartItem'])->name('purchase.updateCartItem');
+ Route::post('purchase/add-to-cart', [PurchaseCartController::class, 'addToCart'])->name('purchase.addToCart')->middleware('can:Create:Purchase');
+ Route::delete('purchase/remove-from-cart/{purchaseItem}', [PurchaseCartController::class, 'removeFromCart'])->name('purchase.removeFromCart')->middleware('can:Create:Purchase');
+ Route::patch('purchase/update-cart-item/{purchaseItem}', [PurchaseCartController::class, 'updateCartItem'])->name('purchase.updateCartItem')->middleware('can:Create:Purchase');
- Route::get('orders', [OrderController::class, 'index'])->name('order.index');
- Route::get('order/create', [OrderController::class, 'create'])->name('order.create');
- Route::post('order/store', [OrderController::class, 'store'])->name('order.store');
- Route::get('order/edit/{order}', [OrderController::class, 'edit'])->name('order.edit');
- Route::patch('order/update/{order}', [OrderController::class, 'update'])->name('order.update');
- Route::delete('order/destroy/{order}', [OrderController::class, 'destroy'])->name('order.destroy');
- Route::delete('order/bulk-destroy', [OrderController::class, 'bulkDestroy'])->name('order.bulkDestroy');
+ Route::get('orders', [OrderController::class, 'index'])->name('order.index')->middleware('can:View:Order');
+ Route::get('order/create', [OrderController::class, 'create'])->name('order.create')->middleware('can:Create:Order');
+ Route::post('order/store', [OrderController::class, 'store'])->name('order.store')->middleware('can:Create:Order');
+ Route::get('order/edit/{order}', [OrderController::class, 'edit'])->name('order.edit')->middleware('can:Edit:Order');
+ Route::patch('order/update/{order}', [OrderController::class, 'update'])->name('order.update')->middleware('can:Edit:Order');
+ Route::delete('order/destroy/{order}', [OrderController::class, 'destroy'])->name('order.destroy')->middleware('can:Delete:Order');
+ Route::delete('order/bulk-destroy', [OrderController::class, 'bulkDestroy'])->name('order.bulkDestroy')->middleware('can:DeleteAny:Order');
// Order Cart routes
- Route::post('order/add-to-cart', [OrderCartController::class, 'addToCart'])->name('order.addToCart');
- Route::delete('order/remove-from-cart/{orderItem}', [OrderCartController::class, 'removeFromCart'])->name('order.removeFromCart');
- Route::patch('order/update-cart-item/{orderItem}', [OrderCartController::class, 'updateCartItem'])->name('order.updateCartItem');
+ Route::post('order/add-to-cart', [OrderCartController::class, 'addToCart'])->name('order.addToCart')->middleware('can:Create:Order');
+ Route::delete('order/remove-from-cart/{orderItem}', [OrderCartController::class, 'removeFromCart'])->name('order.removeFromCart')->middleware('can:Create:Order');
+ Route::patch('order/update-cart-item/{orderItem}', [OrderCartController::class, 'updateCartItem'])->name('order.updateCartItem')->middleware('can:Create:Order');
});
});
diff --git a/routes/master.php b/routes/master.php
index 0a06398..0731626 100644
--- a/routes/master.php
+++ b/routes/master.php
@@ -7,29 +7,29 @@
Route::middleware(['auth'])->group(function () {
Route::prefix('admin/master')->group(function () {
- Route::get('categories', [CategoryController::class, 'index'])->name('category.index');
- Route::post('category/store', [CategoryController::class, 'store'])->name('category.store');
- Route::patch('category/update/{category}', [CategoryController::class, 'update'])->name('category.update');
- Route::delete('category/destroy/{category}', [CategoryController::class, 'destroy'])->name('category.destroy');
- Route::delete('category/bulk-destroy', [CategoryController::class, 'bulkDestroy'])->name('category.bulkDestroy');
- Route::patch('category/toggle-status/{category}', [CategoryController::class, 'toggleStatus'])->name('category.toggleStatus');
+ Route::get('categories', [CategoryController::class, 'index'])->name('category.index')->middleware('can:View:Category');
+ Route::post('category/store', [CategoryController::class, 'store'])->name('category.store')->middleware('can:Create:Category');
+ Route::patch('category/update/{category}', [CategoryController::class, 'update'])->name('category.update')->middleware('can:Edit:Category');
+ Route::delete('category/destroy/{category}', [CategoryController::class, 'destroy'])->name('category.destroy')->middleware('can:Delete:Category');
+ Route::delete('category/bulk-destroy', [CategoryController::class, 'bulkDestroy'])->name('category.bulkDestroy')->middleware('can:DeleteAny:Category');
+ Route::patch('category/toggle-status/{category}', [CategoryController::class, 'toggleStatus'])->name('category.toggleStatus')->middleware('can:ToggleStatus:Category');
- Route::get('products', [ProductController::class, 'index'])->name('product.index');
- Route::get('product/create', [ProductController::class, 'create'])->name('product.create');
- Route::post('product/store', [ProductController::class, 'store'])->name('product.store');
- Route::get('product/{product}/edit', [ProductController::class, 'edit'])->name('product.edit');
- Route::patch('product/update/{product}', [ProductController::class, 'update'])->name('product.update');
- Route::delete('product/destroy/{product}', [ProductController::class, 'destroy'])->name('product.destroy');
- Route::delete('product/bulk-destroy', [ProductController::class, 'bulkDestroy'])->name('product.bulkDestroy');
- Route::patch('product/toggle-status/{product}', [ProductController::class, 'toggleStatus'])->name('product.toggleStatus');
+ Route::get('products', [ProductController::class, 'index'])->name('product.index')->middleware('can:View:Product');
+ Route::get('product/create', [ProductController::class, 'create'])->name('product.create')->middleware('can:Create:Product');
+ Route::post('product/store', [ProductController::class, 'store'])->name('product.store')->middleware('can:Create:Product');
+ Route::get('product/{product}/edit', [ProductController::class, 'edit'])->name('product.edit')->middleware('can:Edit:Product');
+ Route::patch('product/update/{product}', [ProductController::class, 'update'])->name('product.update')->middleware('can:Edit:Product');
+ Route::delete('product/destroy/{product}', [ProductController::class, 'destroy'])->name('product.destroy')->middleware('can:Delete:Product');
+ Route::delete('product/bulk-destroy', [ProductController::class, 'bulkDestroy'])->name('product.bulkDestroy')->middleware('can:DeleteAny:Product');
+ Route::patch('product/toggle-status/{product}', [ProductController::class, 'toggleStatus'])->name('product.toggleStatus')->middleware('can:ToggleStatus:Product');
- Route::get('users', [UserController::class, 'index'])->name('user.index');
- Route::get('user/create', [UserController::class, 'create'])->name('user.create');
- Route::post('user/store', [UserController::class, 'store'])->name('user.store');
- Route::get('user/{user}/edit', [UserController::class, 'edit'])->name('user.edit');
- Route::patch('user/update/{user}', [UserController::class, 'update'])->name('user.update');
- Route::patch('user/reset-password/{user}', [UserController::class, 'resetPassword'])->name('user.resetPassword');
- Route::delete('user/destroy/{user}', [UserController::class, 'destroy'])->name('user.destroy');
- Route::delete('user/bulk-destroy', [UserController::class, 'bulkDestroy'])->name('user.bulkDestroy');
+ Route::get('users', [UserController::class, 'index'])->name('user.index')->middleware('can:View:User');
+ Route::get('user/create', [UserController::class, 'create'])->name('user.create')->middleware('can:Create:User');
+ Route::post('user/store', [UserController::class, 'store'])->name('user.store')->middleware('can:Create:User');
+ Route::get('user/{user}/edit', [UserController::class, 'edit'])->name('user.edit')->middleware('can:Edit:User');
+ Route::patch('user/update/{user}', [UserController::class, 'update'])->name('user.update')->middleware('can:Edit:User');
+ Route::patch('user/reset-password/{user}', [UserController::class, 'resetPassword'])->name('user.resetPassword')->middleware('can:ResetPassword:User');
+ Route::delete('user/destroy/{user}', [UserController::class, 'destroy'])->name('user.destroy')->middleware('can:Delete:User');
+ Route::delete('user/bulk-destroy', [UserController::class, 'bulkDestroy'])->name('user.bulkDestroy')->middleware('can:DeleteAny:User');
});
});
diff --git a/routes/system.php b/routes/system.php
index 0643053..93fe8a5 100644
--- a/routes/system.php
+++ b/routes/system.php
@@ -7,12 +7,12 @@
Route::middleware(['auth'])->group(function () {
Route::prefix('admin/system')->group(function () {
- Route::get('logs', [LogController::class, 'index'])->name('system.logs.index');
- Route::get('activity-logs', [ActivityLogController::class, 'index'])->name('system.activity-logs.index');
+ Route::get('logs', [LogController::class, 'index'])->name('system.logs.index')->middleware('can:View:Log');
+ Route::get('activity-logs', [ActivityLogController::class, 'index'])->name('system.activity-logs.index')->middleware('can:View:Activity');
Route::controller(GeneralSettingController::class)->prefix('settings')->group(function () {
- Route::get('/', 'index')->name('system.settings.index');
- Route::post('/', 'update')->name('system.settings.update');
+ Route::get('/', 'index')->name('system.settings.index')->middleware('can:View:Setting');
+ Route::post('/', 'update')->name('system.settings.update')->middleware('can:Edit:Setting');
});
});
});
diff --git a/routes/web.php b/routes/web.php
index c74c013..19605a3 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -5,8 +5,8 @@
use Illuminate\Support\Facades\Route;
Route::middleware(['auth', 'verified'])->group(function () {
- Route::get('dashboard', DashboardController::class)->name('dashboard');
- Route::get('analysis', AnalysisController::class)->name('analysis');
+ Route::get('dashboard', DashboardController::class)->name('dashboard')->middleware('can:View:Dashboard');
+ Route::get('analysis', AnalysisController::class)->name('analysis')->middleware('can:View:Analysis');
});
require __DIR__.'/settings.php';