feat: implement role management system with CRUD interface and permission redefinitions
This commit is contained in:
parent
cffea55157
commit
691916e6de
@ -103,6 +103,11 @@ enum Permission: string
|
||||
|
||||
case ACTIVITY_LOGS_VIEW = 'activity-logs.view';
|
||||
|
||||
case ROLES_VIEW = 'roles.view';
|
||||
case ROLES_CREATE = 'roles.create';
|
||||
case ROLES_UPDATE = 'roles.update';
|
||||
case ROLES_DELETE = 'roles.delete';
|
||||
|
||||
public function label(): string
|
||||
{
|
||||
return match ($this) {
|
||||
@ -200,6 +205,11 @@ public function label(): string
|
||||
self::SETTINGS_UPDATE => 'Ubah Pengaturan Aplikasi',
|
||||
|
||||
self::ACTIVITY_LOGS_VIEW => 'Lihat Log Aktivitas',
|
||||
|
||||
self::ROLES_VIEW => 'Lihat Role',
|
||||
self::ROLES_CREATE => 'Tambah Role',
|
||||
self::ROLES_UPDATE => 'Ubah Role',
|
||||
self::ROLES_DELETE => 'Hapus Role',
|
||||
};
|
||||
}
|
||||
|
||||
@ -241,6 +251,8 @@ public function group(): string
|
||||
self::PAYROLL_CLOSE => 'Gaji',
|
||||
self::SETTINGS_VIEW, self::SETTINGS_UPDATE => 'Pengaturan Aplikasi',
|
||||
self::ACTIVITY_LOGS_VIEW => 'Log Aktivitas',
|
||||
self::ROLES_VIEW, self::ROLES_CREATE, self::ROLES_UPDATE,
|
||||
self::ROLES_DELETE => 'Role & Permission',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@ -36,113 +36,84 @@ public function permissions(): array
|
||||
{
|
||||
return match ($this) {
|
||||
self::DEVELOPER, self::OWNER => Permission::cases(),
|
||||
|
||||
self::DIREKTUR => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
Permission::EMPLOYEES_CREATE,
|
||||
Permission::EMPLOYEES_UPDATE,
|
||||
Permission::EMPLOYEES_DELETE,
|
||||
Permission::EMPLOYEES_RESET_PASSWORD,
|
||||
Permission::EMPLOYEES_TOGGLE_STATUS,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
Permission::ATTENDANCES_MANAGE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
Permission::PRODUCTS_CREATE,
|
||||
Permission::PRODUCTS_UPDATE,
|
||||
Permission::PRODUCTS_DELETE,
|
||||
Permission::PRODUCTS_TOGGLE_STATUS,
|
||||
|
||||
Permission::RAW_MATERIALS_VIEW,
|
||||
Permission::RAW_MATERIALS_CREATE,
|
||||
Permission::RAW_MATERIALS_UPDATE,
|
||||
Permission::RAW_MATERIALS_DELETE,
|
||||
Permission::RAW_MATERIALS_TOGGLE_STATUS,
|
||||
|
||||
Permission::PURCHASES_VIEW,
|
||||
Permission::PURCHASES_CREATE,
|
||||
Permission::PURCHASES_UPDATE,
|
||||
Permission::PURCHASES_DELETE,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
Permission::ORDERS_DELETE,
|
||||
Permission::ORDERS_SEND,
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
|
||||
Permission::CUTTINGS_VIEW,
|
||||
Permission::CUTTINGS_CREATE,
|
||||
Permission::CUTTINGS_UPDATE,
|
||||
Permission::CUTTINGS_DELETE,
|
||||
Permission::CUTTINGS_COMPLETE,
|
||||
Permission::CUTTINGS_VERIFY,
|
||||
Permission::CUTTINGS_REJECT,
|
||||
|
||||
Permission::CASH_VIEW,
|
||||
Permission::CASH_DEPOSIT,
|
||||
Permission::CASH_UPDATE,
|
||||
Permission::CASH_DELETE,
|
||||
|
||||
Permission::EXPENSES_VIEW,
|
||||
Permission::EXPENSES_CREATE,
|
||||
Permission::EXPENSES_UPDATE,
|
||||
Permission::EXPENSES_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
Permission::PAYROLL_PAY,
|
||||
Permission::PAYROLL_ADJUST,
|
||||
Permission::PAYROLL_CLOSE,
|
||||
Permission::ACTIVITY_LOGS_VIEW,
|
||||
],
|
||||
|
||||
self::ADMIN_TOKO => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
Permission::EMPLOYEES_CREATE,
|
||||
Permission::EMPLOYEES_UPDATE,
|
||||
Permission::EMPLOYEES_RESET_PASSWORD,
|
||||
Permission::EMPLOYEES_TOGGLE_STATUS,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
Permission::ATTENDANCES_MANAGE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
Permission::PRODUCTS_CREATE,
|
||||
Permission::PRODUCTS_UPDATE,
|
||||
Permission::PRODUCTS_DELETE,
|
||||
Permission::PRODUCTS_TOGGLE_STATUS,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
@ -150,84 +121,128 @@ public function permissions(): array
|
||||
Permission::ORDERS_SEND,
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
|
||||
Permission::CUTTINGS_VIEW,
|
||||
Permission::CUTTINGS_VERIFY,
|
||||
Permission::CUTTINGS_REJECT,
|
||||
|
||||
Permission::CASH_VIEW,
|
||||
Permission::CASH_DEPOSIT,
|
||||
Permission::CASH_UPDATE,
|
||||
Permission::CASH_DELETE,
|
||||
|
||||
Permission::EXPENSES_VIEW,
|
||||
Permission::EXPENSES_CREATE,
|
||||
Permission::EXPENSES_UPDATE,
|
||||
Permission::EXPENSES_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
Permission::PAYROLL_PAY,
|
||||
Permission::PAYROLL_ADJUST,
|
||||
Permission::PAYROLL_CLOSE,
|
||||
Permission::SETTINGS_VIEW,
|
||||
Permission::SETTINGS_UPDATE,
|
||||
Permission::ACTIVITY_LOGS_VIEW,
|
||||
],
|
||||
|
||||
self::ADMIN_BAHAN_BAKU => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
Permission::RAW_MATERIALS_VIEW,
|
||||
Permission::RAW_MATERIALS_CREATE,
|
||||
Permission::RAW_MATERIALS_UPDATE,
|
||||
Permission::RAW_MATERIALS_DELETE,
|
||||
Permission::RAW_MATERIALS_TOGGLE_STATUS,
|
||||
Permission::PURCHASES_VIEW,
|
||||
Permission::PURCHASES_CREATE,
|
||||
Permission::PURCHASES_UPDATE,
|
||||
Permission::PURCHASES_DELETE,
|
||||
Permission::CUTTINGS_VIEW,
|
||||
Permission::CUTTINGS_CREATE,
|
||||
Permission::CUTTINGS_UPDATE,
|
||||
Permission::CUTTINGS_DELETE,
|
||||
Permission::CUTTINGS_COMPLETE,
|
||||
],
|
||||
self::MARKETING => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
Permission::ATTENDANCES_DELETE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
Permission::PRODUCTS_CREATE,
|
||||
Permission::PRODUCTS_UPDATE,
|
||||
Permission::PRODUCTS_DELETE,
|
||||
Permission::PRODUCTS_TOGGLE_STATUS,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
Permission::ORDERS_DELETE,
|
||||
Permission::ORDERS_SEND,
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
|
||||
Permission::CUTTINGS_VIEW,
|
||||
Permission::CUTTINGS_VERIFY,
|
||||
|
||||
Permission::CASH_VIEW,
|
||||
Permission::CASH_DEPOSIT,
|
||||
Permission::CASH_UPDATE,
|
||||
Permission::CASH_DELETE,
|
||||
|
||||
Permission::EXPENSES_VIEW,
|
||||
Permission::EXPENSES_CREATE,
|
||||
Permission::EXPENSES_UPDATE,
|
||||
Permission::EXPENSES_DELETE,
|
||||
|
||||
Permission::EMPLOYEE_ADVANCES_VIEW,
|
||||
Permission::EMPLOYEE_ADVANCES_CREATE,
|
||||
Permission::EMPLOYEE_ADVANCES_UPDATE,
|
||||
Permission::EMPLOYEE_ADVANCES_DELETE,
|
||||
Permission::EMPLOYEE_ADVANCES_PAY,
|
||||
|
||||
Permission::PAYROLL_VIEW,
|
||||
Permission::PAYROLL_PAY,
|
||||
Permission::PAYROLL_ADJUST,
|
||||
Permission::PAYROLL_CLOSE,
|
||||
],
|
||||
|
||||
self::MARKETING => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
|
||||
Permission::EMPLOYEES_VIEW,
|
||||
|
||||
Permission::ATTENDANCES_VIEW,
|
||||
Permission::ATTENDANCES_CREATE,
|
||||
|
||||
Permission::LEAVE_REQUESTS_VIEW,
|
||||
Permission::LEAVE_REQUESTS_CREATE,
|
||||
Permission::LEAVE_REQUESTS_UPDATE,
|
||||
Permission::LEAVE_REQUESTS_DELETE,
|
||||
|
||||
Permission::CATEGORIES_VIEW,
|
||||
Permission::CATEGORIES_CREATE,
|
||||
Permission::CATEGORIES_UPDATE,
|
||||
Permission::CATEGORIES_DELETE,
|
||||
|
||||
Permission::SUPPLIERS_VIEW,
|
||||
Permission::SUPPLIERS_CREATE,
|
||||
Permission::SUPPLIERS_UPDATE,
|
||||
Permission::SUPPLIERS_DELETE,
|
||||
|
||||
Permission::CUSTOMERS_VIEW,
|
||||
Permission::CUSTOMERS_CREATE,
|
||||
Permission::CUSTOMERS_UPDATE,
|
||||
Permission::CUSTOMERS_DELETE,
|
||||
|
||||
Permission::PRODUCTS_VIEW,
|
||||
|
||||
Permission::ORDERS_VIEW,
|
||||
Permission::ORDERS_CREATE,
|
||||
Permission::ORDERS_UPDATE,
|
||||
@ -235,6 +250,7 @@ public function permissions(): array
|
||||
Permission::ORDERS_COMPLETE,
|
||||
Permission::ORDERS_CANCEL,
|
||||
],
|
||||
|
||||
self::NON_OPERATOR => [
|
||||
Permission::DASHBOARD_VIEW,
|
||||
],
|
||||
|
||||
109
app/Http/Controllers/Admin/System/RoleController.php
Normal file
109
app/Http/Controllers/Admin/System/RoleController.php
Normal file
@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Admin\System;
|
||||
|
||||
use App\Enums\Permission as PermissionEnum;
|
||||
use App\Enums\Role as EnumsRole;
|
||||
use App\Http\Controllers\Concerns\FlashesEntityMessage;
|
||||
use App\Http\Controllers\Concerns\ParsesDataTableQuery;
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Admin\System\RoleRequest;
|
||||
use App\Services\System\RoleService;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
use Inertia\Response;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleController extends Controller
|
||||
{
|
||||
use FlashesEntityMessage;
|
||||
use ParsesDataTableQuery;
|
||||
|
||||
public function __construct(
|
||||
private readonly RoleService $roleService,
|
||||
) {}
|
||||
|
||||
public function index(Request $request): Response
|
||||
{
|
||||
$tableQuery = $this->parseDataTableQuery($request);
|
||||
|
||||
return Inertia::render('admin/system/roles/Index', [
|
||||
'roles' => $this->roleService->paginateForIndex($tableQuery),
|
||||
'filters' => $this->dataTableFilters($tableQuery),
|
||||
]);
|
||||
}
|
||||
|
||||
public function create(): Response
|
||||
{
|
||||
$permissions = collect(PermissionEnum::cases())
|
||||
->map(fn (PermissionEnum $permission) => [
|
||||
'value' => $permission->value,
|
||||
'label' => $permission->label(),
|
||||
'group' => $permission->group(),
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
return Inertia::render('admin/system/roles/Create', [
|
||||
'permissions' => $permissions,
|
||||
]);
|
||||
}
|
||||
|
||||
public function store(RoleRequest $request): RedirectResponse
|
||||
{
|
||||
$this->roleService->create($request->validated());
|
||||
|
||||
$this->flashCreated('Role');
|
||||
|
||||
return redirect()->route('admin.system.roles.index');
|
||||
}
|
||||
|
||||
public function edit(Role $role): Response
|
||||
{
|
||||
$role->load('permissions');
|
||||
|
||||
$permissions = collect(PermissionEnum::cases())
|
||||
->map(fn (PermissionEnum $permission) => [
|
||||
'value' => $permission->value,
|
||||
'label' => $permission->label(),
|
||||
'group' => $permission->group(),
|
||||
])
|
||||
->values()
|
||||
->all();
|
||||
|
||||
// Check if role is developer or owner to protect it
|
||||
$isProtected = in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true);
|
||||
|
||||
return Inertia::render('admin/system/roles/Edit', [
|
||||
'role' => [
|
||||
'id' => $role->id,
|
||||
'name' => $role->name,
|
||||
'permission_names' => $role->permissions->pluck('name')->all(),
|
||||
],
|
||||
'permissions' => $permissions,
|
||||
'isProtected' => $isProtected,
|
||||
]);
|
||||
}
|
||||
|
||||
public function update(RoleRequest $request, Role $role): RedirectResponse
|
||||
{
|
||||
$this->roleService->update($role, $request->validated());
|
||||
|
||||
$this->flashUpdated('Role');
|
||||
|
||||
return redirect()->route('admin.system.roles.index');
|
||||
}
|
||||
|
||||
public function destroy(Role $role): RedirectResponse
|
||||
{
|
||||
try {
|
||||
$this->roleService->delete($role);
|
||||
$this->flashDeleted('Role');
|
||||
} catch (\InvalidArgumentException $e) {
|
||||
$this->flashError($e->getMessage());
|
||||
}
|
||||
|
||||
return redirect()->route('admin.system.roles.index');
|
||||
}
|
||||
}
|
||||
60
app/Http/Requests/Admin/System/RoleRequest.php
Normal file
60
app/Http/Requests/Admin/System/RoleRequest.php
Normal file
@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Requests\Admin\System;
|
||||
|
||||
use App\Enums\Permission;
|
||||
use Illuminate\Foundation\Http\FormRequest;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
|
||||
class RoleRequest extends FormRequest
|
||||
{
|
||||
public function authorize(): bool
|
||||
{
|
||||
$permission = $this->isMethod('POST')
|
||||
? Permission::ROLES_CREATE
|
||||
: Permission::ROLES_UPDATE;
|
||||
|
||||
return $this->user()?->can($permission->value) ?? false;
|
||||
}
|
||||
|
||||
protected function prepareForValidation(): void
|
||||
{
|
||||
if ($this->has('name')) {
|
||||
$this->merge([
|
||||
'name' => Str::slug($this->input('name')),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function rules(): array
|
||||
{
|
||||
$roleId = $this->route('role')?->id;
|
||||
|
||||
return [
|
||||
'name' => [
|
||||
'required',
|
||||
'string',
|
||||
'max:50',
|
||||
'regex:/^[a-z0-9\-]+$/',
|
||||
Rule::unique('roles', 'name')->ignore($roleId),
|
||||
],
|
||||
'permissions' => ['nullable', 'array'],
|
||||
'permissions.*' => ['string', Rule::exists('permissions', 'name')],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
return [
|
||||
'name' => 'nama',
|
||||
'permissions' => 'hak akses',
|
||||
];
|
||||
}
|
||||
}
|
||||
@ -93,7 +93,13 @@ public function roleLabel(): Attribute
|
||||
get: function () {
|
||||
$role = $this->roles->first();
|
||||
|
||||
return $role ? Role::from($role->name)->label() : null;
|
||||
if (! $role) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$enum = Role::tryFrom($role->name);
|
||||
|
||||
return $enum ? $enum->label() : ucwords(str_replace('-', ' ', $role->name));
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
87
app/Services/System/RoleService.php
Normal file
87
app/Services/System/RoleService.php
Normal file
@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\System;
|
||||
|
||||
use App\Enums\Role as EnumsRole;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Permission\Models\Role;
|
||||
|
||||
class RoleService
|
||||
{
|
||||
/**
|
||||
* @param array{search: string, sort: string, direction: 'asc'|'desc'} $tableQuery
|
||||
*/
|
||||
public function paginateForIndex(array $tableQuery): LengthAwarePaginator
|
||||
{
|
||||
$query = Role::query()
|
||||
->withCount('permissions')
|
||||
->when($tableQuery['search'] !== '', function (Builder $query) use ($tableQuery): void {
|
||||
$search = $tableQuery['search'];
|
||||
$query->where(function (Builder $query) use ($search): void {
|
||||
$query->where('name', 'like', "%{$search}%");
|
||||
});
|
||||
});
|
||||
|
||||
$this->applySorting($query, $tableQuery['sort'], $tableQuery['direction']);
|
||||
|
||||
return $query
|
||||
->paginate(10)
|
||||
->withQueryString();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
public function create(array $validated): void
|
||||
{
|
||||
DB::transaction(function () use ($validated): void {
|
||||
$role = Role::create([
|
||||
'name' => Str::slug($validated['name']),
|
||||
'guard_name' => 'web',
|
||||
]);
|
||||
|
||||
if (! empty($validated['permissions'])) {
|
||||
$role->syncPermissions($validated['permissions']);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $validated
|
||||
*/
|
||||
public function update(Role $role, array $validated): void
|
||||
{
|
||||
DB::transaction(function () use ($role, $validated): void {
|
||||
// Protect developer and owner names from changing
|
||||
if (! in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
|
||||
$role->name = Str::slug($validated['name']);
|
||||
}
|
||||
$role->save();
|
||||
|
||||
$role->syncPermissions($validated['permissions'] ?? []);
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(Role $role): void
|
||||
{
|
||||
if (in_array($role->name, [EnumsRole::DEVELOPER->value, EnumsRole::OWNER->value], true)) {
|
||||
throw new \InvalidArgumentException('Role developer atau owner tidak dapat dihapus.');
|
||||
}
|
||||
|
||||
$role->delete();
|
||||
}
|
||||
|
||||
private function applySorting(Builder $query, string $sort, string $direction): void
|
||||
{
|
||||
if (in_array($sort, ['name'], true)) {
|
||||
$query->orderBy($sort, $direction);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$query->latest();
|
||||
}
|
||||
}
|
||||
@ -33,30 +33,6 @@ public function run(): void
|
||||
);
|
||||
}
|
||||
|
||||
$legacyRoleMap = [
|
||||
'super-admin' => RoleEnum::DEVELOPER->value,
|
||||
'admin' => RoleEnum::DIREKTUR->value,
|
||||
'hr' => RoleEnum::ADMIN_TOKO->value,
|
||||
'user' => RoleEnum::NON_OPERATOR->value,
|
||||
];
|
||||
|
||||
foreach ($legacyRoleMap as $oldRole => $newRole) {
|
||||
$oldRoleModel = Role::query()->where('name', $oldRole)->where('guard_name', 'web')->first();
|
||||
|
||||
if (! $oldRoleModel) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$newRoleModel = Role::findByName($newRole, 'web');
|
||||
|
||||
foreach ($oldRoleModel->users as $user) {
|
||||
$user->removeRole($oldRole);
|
||||
$user->assignRole($newRoleModel);
|
||||
}
|
||||
|
||||
$oldRoleModel->delete();
|
||||
}
|
||||
|
||||
Role::query()
|
||||
->where('guard_name', 'web')
|
||||
->whereNotIn('name', RoleEnum::values())
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { Link, usePage } from '@inertiajs/vue3';
|
||||
import { Banknote, CalendarDays, Clock, FolderTree, History, Layers, LayoutDashboard, Package, Receipt, Scissors, Settings2, ShoppingBag, ShoppingCart, User, UserCheck, Users, Wallet, WalletCards } from '@lucide/vue';
|
||||
import { Banknote, CalendarDays, Clock, FolderTree, History, Layers, LayoutDashboard, Package, Receipt, Scissors, Settings2, Shield, ShoppingBag, ShoppingCart, User, UserCheck, Users, Wallet, WalletCards } from '@lucide/vue';
|
||||
import {
|
||||
Sidebar,
|
||||
SidebarContent,
|
||||
@ -75,6 +75,7 @@ const menuGroups: MenuGroup[] = [
|
||||
{
|
||||
label: 'Sistem',
|
||||
items: [
|
||||
{ title: 'Role & Permission', href: '/admin/system/roles', icon: Shield, permission: 'roles.view' },
|
||||
{ title: 'Pengaturan', href: '/admin/system/setting', icon: Settings2, permission: 'settings.view' },
|
||||
{ title: 'Log Aktivitas', href: '/admin/system/activity-logs', icon: History, permission: 'activity-logs.view' },
|
||||
],
|
||||
|
||||
227
resources/js/components/admin/system/roles/RoleForm.vue
Normal file
227
resources/js/components/admin/system/roles/RoleForm.vue
Normal file
@ -0,0 +1,227 @@
|
||||
<script setup lang="ts">
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
import {
|
||||
Field,
|
||||
FieldError,
|
||||
FieldGroup,
|
||||
FieldLabel,
|
||||
FieldSet,
|
||||
} from '@/components/ui/field';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { formErrors } from '@/lib/form';
|
||||
import { useForm } from '@inertiajs/vue3';
|
||||
import { Save } from '@lucide/vue';
|
||||
import { computed } from 'vue';
|
||||
import { toast } from 'vue-sonner';
|
||||
|
||||
interface PermissionOption {
|
||||
value: string;
|
||||
label: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
permissions: PermissionOption[];
|
||||
initialData?: {
|
||||
name?: string;
|
||||
permission_names?: string[];
|
||||
};
|
||||
submitUrl: string;
|
||||
method?: 'post' | 'put';
|
||||
submitLabel?: string;
|
||||
isProtected?: boolean;
|
||||
}>(),
|
||||
{
|
||||
method: 'post',
|
||||
submitLabel: 'Simpan',
|
||||
isProtected: false,
|
||||
},
|
||||
);
|
||||
|
||||
const form = useForm({
|
||||
name: props.initialData?.name ?? '',
|
||||
permissions: props.initialData?.permission_names ?? [],
|
||||
});
|
||||
|
||||
// Group permissions by group/module
|
||||
const groupedPermissions = computed(() => {
|
||||
const groups: Record<string, PermissionOption[]> = {};
|
||||
props.permissions.forEach((perm) => {
|
||||
if (!groups[perm.group]) {
|
||||
groups[perm.group] = [];
|
||||
}
|
||||
groups[perm.group].push(perm);
|
||||
});
|
||||
return groups;
|
||||
});
|
||||
|
||||
function togglePermission(permValue: string, checked: boolean) {
|
||||
if (checked) {
|
||||
if (!form.permissions.includes(permValue)) {
|
||||
form.permissions = [...form.permissions, permValue];
|
||||
}
|
||||
} else {
|
||||
form.permissions = form.permissions.filter((p) => p !== permValue);
|
||||
}
|
||||
}
|
||||
|
||||
function isPermissionChecked(permValue: string): boolean {
|
||||
return form.permissions.includes(permValue);
|
||||
}
|
||||
|
||||
// Check/Uncheck all for a specific module group
|
||||
function isGroupAllChecked(groupName: string): boolean {
|
||||
const groupPerms = groupedPermissions.value[groupName] || [];
|
||||
if (groupPerms.length === 0) return false;
|
||||
return groupPerms.every((perm) => form.permissions.includes(perm.value));
|
||||
}
|
||||
|
||||
function toggleGroupAll(groupName: string, checked: boolean) {
|
||||
const groupPerms = groupedPermissions.value[groupName] || [];
|
||||
const groupValues = groupPerms.map((p) => p.value);
|
||||
|
||||
if (checked) {
|
||||
// Add all group permissions that are not already in form.permissions
|
||||
const toAdd = groupValues.filter((v) => !form.permissions.includes(v));
|
||||
form.permissions = [...form.permissions, ...toAdd];
|
||||
} else {
|
||||
// Remove all group permissions
|
||||
form.permissions = form.permissions.filter(
|
||||
(p) => !groupValues.includes(p),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function submit() {
|
||||
form[props.method](props.submitUrl, {
|
||||
onSuccess: () => {
|
||||
toast.success(
|
||||
`Role berhasil ${props.method === 'post' ? 'dibuat' : 'diperbarui'}.`,
|
||||
);
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Gagal menyimpan data. Periksa kembali formulir.');
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<form @submit.prevent="submit">
|
||||
<div class="grid gap-6">
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Informasi Role</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<FieldGroup>
|
||||
<FieldSet class="grid gap-4">
|
||||
<Field>
|
||||
<FieldLabel for="name" required
|
||||
>Nama</FieldLabel
|
||||
>
|
||||
<Input
|
||||
id="name"
|
||||
v-model="form.name"
|
||||
type="text"
|
||||
placeholder="Contoh: admin-gudang"
|
||||
:disabled="isProtected"
|
||||
/>
|
||||
<span
|
||||
v-if="isProtected"
|
||||
class="mt-1 text-xs text-muted-foreground"
|
||||
>
|
||||
Nama bawaan sistem (developer/owner) tidak
|
||||
dapat diubah.
|
||||
</span>
|
||||
<FieldError
|
||||
:errors="formErrors(form, 'name')"
|
||||
/>
|
||||
</Field>
|
||||
</FieldSet>
|
||||
</FieldGroup>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle>Hak Akses (Permissions)</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div class="space-y-6">
|
||||
<div
|
||||
v-for="(perms, groupName) in groupedPermissions"
|
||||
:key="groupName"
|
||||
class="border-b pb-6 last:border-b-0 last:pb-0"
|
||||
>
|
||||
<div class="mb-3 flex items-center justify-between">
|
||||
<h3
|
||||
class="text-base font-semibold text-foreground"
|
||||
>
|
||||
{{ groupName }}
|
||||
</h3>
|
||||
<label
|
||||
class="flex cursor-pointer items-center gap-1.5 text-xs text-muted-foreground hover:text-foreground"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="size-3.5 cursor-pointer rounded border-input"
|
||||
:checked="isGroupAllChecked(groupName)"
|
||||
@change="
|
||||
toggleGroupAll(
|
||||
groupName,
|
||||
(
|
||||
$event.target as HTMLInputElement
|
||||
).checked,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<span>Pilih Semua</span>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="grid gap-3 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4"
|
||||
>
|
||||
<label
|
||||
v-for="perm in perms"
|
||||
:key="perm.value"
|
||||
class="flex cursor-pointer items-center gap-2 rounded-md border px-3 py-2 transition-colors hover:bg-muted/50"
|
||||
>
|
||||
<input
|
||||
type="checkbox"
|
||||
class="size-4 cursor-pointer rounded border-input"
|
||||
:checked="
|
||||
isPermissionChecked(perm.value)
|
||||
"
|
||||
@change="
|
||||
togglePermission(
|
||||
perm.value,
|
||||
(
|
||||
$event.target as HTMLInputElement
|
||||
).checked,
|
||||
)
|
||||
"
|
||||
/>
|
||||
<span class="text-sm">{{
|
||||
perm.label
|
||||
}}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<FieldError :errors="formErrors(form, 'permissions')" />
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<div class="flex items-center justify-end">
|
||||
<Button type="submit" :disabled="form.processing">
|
||||
<Save class="size-4" />
|
||||
{{ form.processing ? 'Menyimpan...' : submitLabel }}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</template>
|
||||
62
resources/js/components/admin/system/roles/columns.ts
Normal file
62
resources/js/components/admin/system/roles/columns.ts
Normal file
@ -0,0 +1,62 @@
|
||||
import DataTableActions from '@/components/admin/system/roles/data-table-actions.vue';
|
||||
import { DataTableColumnHeader } from '@/components/data-table';
|
||||
import type { ColumnDef } from '@tanstack/vue-table';
|
||||
import { h } from 'vue';
|
||||
|
||||
export interface RoleListItem {
|
||||
id: number;
|
||||
name: string;
|
||||
permissions_count: number;
|
||||
}
|
||||
|
||||
export function createColumns(): ColumnDef<RoleListItem>[] {
|
||||
return [
|
||||
{
|
||||
accessorKey: 'name',
|
||||
enableSorting: true,
|
||||
header: () =>
|
||||
h(DataTableColumnHeader, { title: 'Nama', column: 'name' }),
|
||||
cell: ({ row }) => {
|
||||
const name = row.original.name;
|
||||
const formattedName = name
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
|
||||
return h('div', { class: 'flex flex-col' }, [
|
||||
h(
|
||||
'span',
|
||||
{ class: 'font-medium text-foreground' },
|
||||
formattedName,
|
||||
),
|
||||
h(
|
||||
'span',
|
||||
{ class: 'text-xs text-muted-foreground font-mono' },
|
||||
name,
|
||||
),
|
||||
]);
|
||||
},
|
||||
},
|
||||
{
|
||||
accessorKey: 'permissions_count',
|
||||
enableSorting: false,
|
||||
header: 'Jumlah Hak Akses',
|
||||
cell: ({ row }) => {
|
||||
return h(
|
||||
'span',
|
||||
{ class: 'text-sm' },
|
||||
`${row.original.permissions_count} hak akses`,
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'actions',
|
||||
enableSorting: false,
|
||||
enableHiding: false,
|
||||
cell: ({ row }) =>
|
||||
h(DataTableActions, {
|
||||
role: row.original,
|
||||
}),
|
||||
},
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,84 @@
|
||||
<script setup lang="ts">
|
||||
import { Link, router } from '@inertiajs/vue3';
|
||||
import { Pencil, Trash2 } from '@lucide/vue';
|
||||
import { computed, ref } from 'vue';
|
||||
import { toast } from 'vue-sonner';
|
||||
import ConfirmDialog from '@/components/ConfirmDialog.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import type { RoleListItem } from './columns';
|
||||
|
||||
const props = defineProps<{
|
||||
role: RoleListItem;
|
||||
}>();
|
||||
|
||||
const { can } = useCan();
|
||||
|
||||
const deleteConfirmOpen = ref(false);
|
||||
const deleteProcessing = ref(false);
|
||||
|
||||
const isSystemRole = computed(() => {
|
||||
return ['developer', 'owner'].includes(props.role.name);
|
||||
});
|
||||
|
||||
function destroyRole() {
|
||||
if (isSystemRole.value) {
|
||||
toast.error('Role sistem tidak dapat dihapus.');
|
||||
return;
|
||||
}
|
||||
|
||||
deleteProcessing.value = true;
|
||||
|
||||
router.delete(`/admin/system/roles/${props.role.id}`, {
|
||||
preserveScroll: true,
|
||||
onSuccess: () => {
|
||||
deleteConfirmOpen.value = false;
|
||||
toast.success('Role berhasil dihapus.');
|
||||
},
|
||||
onError: () => {
|
||||
toast.error('Gagal menghapus role.');
|
||||
},
|
||||
onFinish: () => {
|
||||
deleteProcessing.value = false;
|
||||
},
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex items-center justify-end gap-1">
|
||||
<Tooltip v-if="can('roles.update')">
|
||||
<TooltipTrigger as-child>
|
||||
<Button variant="ghost" size="icon" class="size-8" as-child>
|
||||
<Link :href="`/admin/system/roles/${role.id}/edit`">
|
||||
<Pencil class="size-4" />
|
||||
<span class="sr-only">Ubah</span>
|
||||
</Link>
|
||||
</Button>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>Ubah</TooltipContent>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip v-if="can('roles.delete')">
|
||||
<TooltipTrigger as-child>
|
||||
<span>
|
||||
<Button variant="ghost" size="icon"
|
||||
class="text-destructive hover:text-destructive size-8"
|
||||
:disabled="isSystemRole"
|
||||
@click="deleteConfirmOpen = true">
|
||||
<Trash2 class="size-4" />
|
||||
<span class="sr-only">Hapus</span>
|
||||
</Button>
|
||||
</span>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
{{ isSystemRole ? 'Role sistem tidak dapat dihapus' : 'Hapus' }}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</div>
|
||||
|
||||
<ConfirmDialog v-if="can('roles.delete') && !isSystemRole" v-model:open="deleteConfirmOpen" title="Hapus role?"
|
||||
:description="`Role ${role.name} akan dihapus secara permanen. Tindakan ini tidak dapat dibatalkan.`"
|
||||
confirm-label="Hapus" cancel-label="Batal" destructive :loading="deleteProcessing" @confirm="destroyRole" />
|
||||
</template>
|
||||
40
resources/js/pages/admin/system/roles/Create.vue
Normal file
40
resources/js/pages/admin/system/roles/Create.vue
Normal file
@ -0,0 +1,40 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { ArrowLeft } from '@lucide/vue';
|
||||
import RoleForm from '@/components/admin/system/roles/RoleForm.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
|
||||
interface PermissionOption {
|
||||
value: string;
|
||||
label: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
defineProps<{
|
||||
permissions: PermissionOption[];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Tambah Role" />
|
||||
|
||||
<AdminLayout>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-2xl font-bold tracking-tight">
|
||||
Tambah Role
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
|
||||
<Link href="/admin/system/roles">
|
||||
<ArrowLeft class="size-4" />
|
||||
Kembali
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<RoleForm submit-url="/admin/system/roles" method="post" submit-label="Simpan" :permissions="permissions" />
|
||||
</AdminLayout>
|
||||
</template>
|
||||
49
resources/js/pages/admin/system/roles/Edit.vue
Normal file
49
resources/js/pages/admin/system/roles/Edit.vue
Normal file
@ -0,0 +1,49 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { ArrowLeft } from '@lucide/vue';
|
||||
import RoleForm from '@/components/admin/system/roles/RoleForm.vue';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
|
||||
interface PermissionOption {
|
||||
value: string;
|
||||
label: string;
|
||||
group: string;
|
||||
}
|
||||
|
||||
interface RoleData {
|
||||
id: number;
|
||||
name: string;
|
||||
permission_names: string[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
role: RoleData;
|
||||
permissions: PermissionOption[];
|
||||
isProtected: boolean;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Ubah Role" />
|
||||
|
||||
<AdminLayout>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-2xl font-bold tracking-tight">
|
||||
Ubah Role
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Button variant="outline" as-child class="shrink-0 self-start sm:self-center">
|
||||
<Link href="/admin/system/roles">
|
||||
<ArrowLeft class="size-4" />
|
||||
Kembali
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<RoleForm :submit-url="`/admin/system/roles/${role.id}`" method="put" submit-label="Perbarui"
|
||||
:initial-data="role" :permissions="permissions" :is-protected="isProtected" />
|
||||
</AdminLayout>
|
||||
</template>
|
||||
102
resources/js/pages/admin/system/roles/Index.vue
Normal file
102
resources/js/pages/admin/system/roles/Index.vue
Normal file
@ -0,0 +1,102 @@
|
||||
<script setup lang="ts">
|
||||
import { Head, Link } from '@inertiajs/vue3';
|
||||
import { Plus } from '@lucide/vue';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import { createColumns } from '@/components/admin/system/roles/columns';
|
||||
import { DataTable } from '@/components/data-table';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useCan } from '@/composables/useCan';
|
||||
import { useDataTableQuery, useDataTableQuerySync } from '@/composables/useDataTableQuery';
|
||||
import AdminLayout from '@/layouts/AdminLayout.vue';
|
||||
import type { DataTableSort } from '@/types/data-table';
|
||||
import type { RoleListItem } from '@/components/admin/system/roles/columns';
|
||||
|
||||
interface PaginatedRoles {
|
||||
current_page: number;
|
||||
per_page: number;
|
||||
last_page: number;
|
||||
total: number;
|
||||
data: RoleListItem[];
|
||||
links: any[];
|
||||
}
|
||||
|
||||
const props = defineProps<{
|
||||
roles: PaginatedRoles;
|
||||
filters: {
|
||||
search: string;
|
||||
sort?: string;
|
||||
direction?: 'asc' | 'desc';
|
||||
};
|
||||
}>();
|
||||
|
||||
const { can } = useCan();
|
||||
const search = ref(props.filters.search ?? '');
|
||||
|
||||
const { query, setSearch, setSort, resetFilters, syncFromServer } = useDataTableQuery({
|
||||
url: '/admin/system/roles',
|
||||
initial: { ...props.filters },
|
||||
});
|
||||
|
||||
useDataTableQuerySync(() => props.filters, syncFromServer);
|
||||
|
||||
const columns = computed(() => createColumns());
|
||||
|
||||
const currentSort = computed<DataTableSort | null>(() => {
|
||||
if (!query.value.sort || !query.value.direction) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
column: query.value.sort,
|
||||
direction: query.value.direction,
|
||||
};
|
||||
});
|
||||
|
||||
const pagination = computed(() => ({
|
||||
currentPage: props.roles.current_page,
|
||||
perPage: props.roles.per_page,
|
||||
lastPage: props.roles.last_page,
|
||||
total: props.roles.total,
|
||||
}));
|
||||
|
||||
watch(search, (value) => {
|
||||
setSearch(value);
|
||||
});
|
||||
|
||||
watch(
|
||||
() => props.filters.search,
|
||||
(value) => {
|
||||
search.value = value ?? '';
|
||||
},
|
||||
);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Head title="Role & Permission" />
|
||||
|
||||
<AdminLayout>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
|
||||
<div class="space-y-1">
|
||||
<h2 class="text-2xl font-bold tracking-tight">
|
||||
Role & Permission
|
||||
</h2>
|
||||
</div>
|
||||
|
||||
<Button v-if="can('roles.create')" class="shrink-0 self-start sm:self-center" as-child>
|
||||
<Link href="/admin/system/roles/create">
|
||||
<Plus class="size-4" />
|
||||
Tambah
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Card class="min-w-0">
|
||||
<CardContent class="min-w-0">
|
||||
<DataTable v-model:search="search" :columns="columns" :data="roles.data" :pagination="pagination"
|
||||
:pagination-links="roles.links" :sort="currentSort" @sort-change="setSort"
|
||||
@filters-reset="resetFilters" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
</AdminLayout>
|
||||
</template>
|
||||
@ -24,6 +24,7 @@
|
||||
use App\Http\Controllers\Admin\Master\RawMaterialController;
|
||||
use App\Http\Controllers\Admin\Master\SupplierController;
|
||||
use App\Http\Controllers\Admin\System\ActivityLogController;
|
||||
use App\Http\Controllers\Admin\System\RoleController;
|
||||
use App\Http\Controllers\Admin\System\SettingController;
|
||||
use App\Http\Controllers\Auth\LoginController;
|
||||
use App\Http\Controllers\Auth\LogoutController;
|
||||
@ -273,6 +274,27 @@
|
||||
});
|
||||
|
||||
Route::prefix('system')->name('system.')->group(function () {
|
||||
Route::prefix('roles')->name('roles.')
|
||||
->middleware('permission:'.Permission::ROLES_VIEW->value)
|
||||
->group(function () {
|
||||
Route::get('/', [RoleController::class, 'index'])->name('index');
|
||||
Route::get('create', [RoleController::class, 'create'])
|
||||
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
||||
->name('create');
|
||||
Route::post('/', [RoleController::class, 'store'])
|
||||
->middleware('permission:'.Permission::ROLES_CREATE->value)
|
||||
->name('store');
|
||||
Route::get('{role}/edit', [RoleController::class, 'edit'])
|
||||
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
||||
->name('edit');
|
||||
Route::put('{role}', [RoleController::class, 'update'])
|
||||
->middleware('permission:'.Permission::ROLES_UPDATE->value)
|
||||
->name('update');
|
||||
Route::delete('{role}', [RoleController::class, 'destroy'])
|
||||
->middleware('permission:'.Permission::ROLES_DELETE->value)
|
||||
->name('destroy');
|
||||
});
|
||||
|
||||
Route::prefix('activity-logs')->name('activity-logs.')
|
||||
->middleware('permission:'.Permission::ACTIVITY_LOGS_VIEW->value)
|
||||
->group(function () {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user