- {table.getFilteredSelectedRowModel().rows.length} dari{" "}
+ {table.getFilteredSelectedRowModel().rows.length} dari{' '}
{table.getFilteredRowModel().rows.length} baris dipilih.
+
Baris per halaman
+ );
+}
+
+RoleEdit.layout = {
+ breadcrumbs: [{ title: 'Sistem' }],
+};
diff --git a/resources/js/pages/admin/system/role/hooks/use-role-index.ts b/resources/js/pages/admin/system/role/hooks/use-role-index.ts
new file mode 100644
index 0000000..44fcd66
--- /dev/null
+++ b/resources/js/pages/admin/system/role/hooks/use-role-index.ts
@@ -0,0 +1,60 @@
+import { router } from '@inertiajs/react';
+import { useState } from 'react';
+import { toast } from 'sonner';
+import roleRoutes from '@/routes/system/role';
+import type { Role } from '@/types';
+
+export function useRoleIndex() {
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false);
+ const [isBulkDeleteDialogOpen, setIsBulkDeleteDialogOpen] = useState(false);
+ const [roleToDelete, setRoleToDelete] = useState
(null);
+ const [rowsToDelete, setRowsToDelete] = useState([]);
+ const [rowSelection, setRowSelection] = useState({});
+
+ const onDelete = (role: Role) => {
+ setRoleToDelete(role);
+ setIsDeleteDialogOpen(true);
+ };
+
+ const confirmDelete = () => {
+ if (roleToDelete) {
+ router.delete(roleRoutes.destroy(roleToDelete.id).url, {
+ onSuccess: (response: any) => {
+ toast.success(response.props.flash.success);
+ setIsDeleteDialogOpen(false);
+ setRoleToDelete(null);
+ setRowSelection({});
+ },
+ });
+ }
+ };
+
+ const confirmBulkDelete = () => {
+ router.post(roleRoutes.bulkDestroy().url, {
+ ids: rowsToDelete.map((row: any) => row.id),
+ _method: 'DELETE'
+ }, {
+ onSuccess: (response: any) => {
+ toast.success(response.props.flash.success);
+ setIsBulkDeleteDialogOpen(false);
+ setRowsToDelete([]);
+ setRowSelection({});
+ },
+ });
+ };
+
+ return {
+ isDeleteDialogOpen,
+ isBulkDeleteDialogOpen,
+ roleToDelete,
+ rowsToDelete,
+ rowSelection,
+ setRowSelection,
+ setRowsToDelete,
+ setIsDeleteDialogOpen,
+ setIsBulkDeleteDialogOpen,
+ onDelete,
+ confirmDelete,
+ confirmBulkDelete,
+ };
+}
diff --git a/resources/js/pages/admin/system/role/index.tsx b/resources/js/pages/admin/system/role/index.tsx
new file mode 100644
index 0000000..3693739
--- /dev/null
+++ b/resources/js/pages/admin/system/role/index.tsx
@@ -0,0 +1,114 @@
+import { Head, Link } from '@inertiajs/react';
+import { Trash2, Plus } from 'lucide-react';
+import { DataTable } from '@/components/data-table';
+import { DeleteConfirmation } from '@/components/modal/delete-confirmation';
+import { Button } from '@/components/ui/button';
+import { Card, CardContent } from '@/components/ui/card';
+import roleRoutes from '@/routes/system/role';
+import type { Role } from '@/types';
+import { useRoleIndex } from './hooks/use-role-index';
+import { getColumns } from './partials/columns';
+import { usePermission } from '@/hooks/use-permission';
+
+export default function RoleIndex({ roles }: { roles: Role[] }) {
+ const { hasPermission } = usePermission();
+ const {
+ isDeleteDialogOpen,
+ isBulkDeleteDialogOpen,
+ roleToDelete,
+ rowsToDelete,
+ rowSelection,
+ setRowSelection,
+ setRowsToDelete,
+ setIsDeleteDialogOpen,
+ setIsBulkDeleteDialogOpen,
+ onDelete,
+ confirmDelete,
+ confirmBulkDelete,
+ } = useRoleIndex();
+
+ const columns = getColumns({ onDelete }).filter(
+ (col) => col.id !== 'actions' || hasPermission('Edit:Role') || hasPermission('Delete:Role')
+ );
+
+ return (
+
+
+
+
+
+
Role & Izin Akses
+
+ {hasPermission('Create:Role') && (
+
+
+
+ Tambah
+
+
+ )}
+
+
+
+
+ {
+ setRowsToDelete(rows);
+ setIsBulkDeleteDialogOpen(true);
+ },
+ icon: Trash2,
+ variant: 'destructive',
+ },
+ ]
+ : []
+ }
+ />
+
+
+
+ {/* Single Delete Confirmation */}
+
+ Tindakan ini tidak dapat dibatalkan. Role {roleToDelete?.name} akan dihapus secara permanen.
+ >
+ }
+ onDelete={confirmDelete}
+ />
+
+ {/* Bulk Delete Confirmation */}
+
+ Tindakan ini tidak dapat dibatalkan. {rowsToDelete.length} item yang terpilih akan dihapus secara permanen.
+ >
+ }
+ onDelete={confirmBulkDelete}
+ />
+
+ );
+}
+
+RoleIndex.layout = {
+ breadcrumbs: [
+ {
+ title: 'Sistem',
+ },
+ ],
+};
diff --git a/resources/js/pages/admin/system/role/partials/columns.tsx b/resources/js/pages/admin/system/role/partials/columns.tsx
new file mode 100644
index 0000000..db36b73
--- /dev/null
+++ b/resources/js/pages/admin/system/role/partials/columns.tsx
@@ -0,0 +1,114 @@
+import { DataTableColumnHeader } from '@/components/data-table-column-header';
+import { Badge } from '@/components/ui/badge';
+import { Button } from '@/components/ui/button';
+import {
+ Tooltip,
+ TooltipContent,
+ TooltipTrigger,
+} from '@/components/ui/tooltip';
+import { usePermission } from '@/hooks/use-permission';
+import roleRoutes from '@/routes/system/role';
+import type { Role } from '@/types';
+import { Link } from '@inertiajs/react';
+import type { ColumnDef } from '@tanstack/react-table';
+import { Pencil, Trash2 } from 'lucide-react';
+
+interface ColumnProps {
+ onDelete: (role: Role) => void;
+}
+
+export const getColumns = ({ onDelete }: ColumnProps): ColumnDef
[] => [
+ {
+ accessorKey: 'name',
+ header: ({ column }) => (
+
+ ),
+ meta: { title: 'Nama' },
+ },
+ {
+ accessorKey: 'permissions',
+ header: 'Izin Akses',
+ meta: { title: 'Izin Akses' },
+ cell: ({ row }) => {
+ const permissions = row.original.permissions || [];
+
+ if (permissions.length === 0) {
+ return (
+
+ Tidak ada izin akses
+
+ );
+ }
+
+ return (
+
+ {permissions.map((p) => (
+
+ {p.name}
+
+ ))}
+
+ );
+ },
+ },
+ {
+ accessorKey: 'guard_name',
+ header: ({ column }) => (
+
+ ),
+ meta: { title: 'Guard' },
+ },
+ {
+ id: 'actions',
+ header: 'Aksi',
+ cell: ({ row }) => {
+ const role = row.original;
+ const { hasPermission } = usePermission();
+
+ return (
+
+ {hasPermission('Edit:Role') && (
+
+
+
+
+
+
+
+
+
+ Ubah
+
+
+ )}
+ {hasPermission('Delete:Role') && (
+
+
+ onDelete(role)}
+ >
+
+
+
+
+ Hapus
+
+
+ )}
+
+ );
+ },
+ meta: { title: 'Aksi' },
+ },
+];
diff --git a/resources/js/types/index.ts b/resources/js/types/index.ts
index 6f249fb..5457605 100644
--- a/resources/js/types/index.ts
+++ b/resources/js/types/index.ts
@@ -10,3 +10,5 @@ export type * from './general-setting';
export type * from './activity';
export type * from './system-log';
export type * from './dashboard';
+export type * from './role';
+export type * from './permission';
diff --git a/resources/js/types/permission.ts b/resources/js/types/permission.ts
new file mode 100644
index 0000000..937d09d
--- /dev/null
+++ b/resources/js/types/permission.ts
@@ -0,0 +1,7 @@
+export interface Permission {
+ id: number;
+ name: string;
+ guard_name: string;
+ created_at: string;
+ updated_at: string | null;
+}
diff --git a/resources/js/types/role.ts b/resources/js/types/role.ts
new file mode 100644
index 0000000..c6fed2f
--- /dev/null
+++ b/resources/js/types/role.ts
@@ -0,0 +1,10 @@
+import type { Permission } from './permission';
+
+export interface Role {
+ id: number;
+ name: string;
+ guard_name: string;
+ permissions?: Permission[];
+ created_at: string;
+ updated_at: string | null;
+}
diff --git a/routes/system.php b/routes/system.php
index 93fe8a5..2d7238b 100644
--- a/routes/system.php
+++ b/routes/system.php
@@ -3,6 +3,7 @@
use App\Http\Controllers\Admin\System\ActivityLogController;
use App\Http\Controllers\Admin\System\GeneralSettingController;
use App\Http\Controllers\Admin\System\LogController;
+use App\Http\Controllers\Admin\System\RoleController;
use Illuminate\Support\Facades\Route;
Route::middleware(['auth'])->group(function () {
@@ -10,6 +11,16 @@
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::prefix('role')->controller(RoleController::class)->group(function () {
+ Route::get('/', 'index')->name('system.role.index')->middleware('can:View:Role');
+ Route::get('/create', 'create')->name('system.role.create')->middleware('can:Create:Role');
+ Route::post('/', 'store')->name('system.role.store')->middleware('can:Create:Role');
+ Route::get('/{role}/edit', 'edit')->name('system.role.edit')->middleware('can:Edit:Role');
+ Route::put('/{role}', 'update')->name('system.role.update')->middleware('can:Edit:Role');
+ Route::delete('/{role}', 'destroy')->name('system.role.destroy')->middleware('can:Delete:Role');
+ Route::post('/bulk-destroy', 'bulkDestroy')->name('system.role.bulk-destroy')->middleware('can:DeleteAny:Role');
+ });
+
Route::controller(GeneralSettingController::class)->prefix('settings')->group(function () {
Route::get('/', 'index')->name('system.settings.index')->middleware('can:View:Setting');
Route::post('/', 'update')->name('system.settings.update')->middleware('can:Edit:Setting');