create feat management role menu

This commit is contained in:
arifal
2025-02-11 17:59:03 +07:00
parent cb90f69d1e
commit 2bf4b8b327
13 changed files with 296 additions and 51 deletions

View File

@@ -2,9 +2,13 @@
namespace App\Http\Controllers;
use App\Models\Menu;
use App\Models\Role;
use App\Models\RoleMenu;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Schema;
class RolesController extends Controller
{
@@ -100,4 +104,48 @@ class RolesController extends Controller
return redirect()->back()->with("error", $e->getMessage());
}
}
public function menu_permission(string $role_id){
try{
$role = Role::findOrFail($role_id);
$menus = Menu::all();
$role_menus = RoleMenu::where('role_id', $role_id)->get() ?? collect();
return view('roles.role_menu', compact('role', 'menus', 'role_menus'));
}catch(\Exception $e){
return redirect()->back()->with("error", $e->getMessage());
}
}
public function update_menu_permission(Request $request, string $role_id){
try{
$validateData = $request->validate([
"permissions" => "array",
"permissions.*.allow_show" => "nullable|boolean",
"permissions.*.allow_create" => "nullable|boolean",
"permissions.*.allow_update" => "nullable|boolean",
"permissions.*.allow_destroy" => "nullable|boolean"
]);
$role = Role::find($role_id);
$permissionsArray = [];
foreach ($validateData['permissions'] as $menu_id => $permission) {
$permissionsArray[$menu_id] = [
"allow_show" => (int) ($permission["allow_show"] ?? 0),
"allow_create" => (int) ($permission["allow_create"] ?? 0),
"allow_update" => (int) ($permission["allow_update"] ?? 0),
"allow_destroy" => (int) ($permission["allow_destroy"] ?? 0),
"updated_at" => now(),
];
}
// Sync will update existing records and insert new ones
$role->menus()->sync($permissionsArray);
return redirect()->route("role-menu.permission", $role_id)->with('success','Menu Permission updated successfully');
}catch(\Exception $e){
Log::error("Error updating role_menu:", ["error" => $e->getMessage()]);
return redirect()->route("role-menu.permission", $role_id)->with("error", $e->getMessage());
}
}
}

30
app/Models/RoleMenu.php Normal file
View File

@@ -0,0 +1,30 @@
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class RoleMenu extends Model
{
protected $table = 'role_menu';
protected $primary = ['role_id', 'menu_id'];
public $incrementing = false;
protected $fillable = [
'role_id',
'menu_id',
'allow_show',
'allow_create',
'allow_update',
'allow_destroy',
];
public $timestamps = true;
public function role(){
return $this->belongsTo(Role::class, 'role_id');
}
public function menu(){
return $this->belongsTo(Menu::class,'menu_id');
}
}

View File

@@ -34,7 +34,11 @@ class AppServiceProvider extends ServiceProvider
$menus = Menu::whereHas('roles', function ($query) use ($user){
$query->where('roles.id', $user->roles->pluck('id'));
})
->with('children')
->with(['children' => function ($query) {
$query->whereHas('roles', function ($subQuery) {
$subQuery->where('role_menu.allow_show', 1);
});
}])
->orderBy('sort_order', 'asc')
->get();
}else{

View File

@@ -0,0 +1,31 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('role_menu', function (Blueprint $table) {
$table->boolean('allow_show')->default(true)->after('menu_id');
$table->boolean('allow_create')->default(true)->after('allow_show');
$table->boolean('allow_update')->default(true)->after('allow_create');
$table->boolean('allow_destroy')->default(true)->after('allow_update');
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('role_menu', function (Blueprint $table) {
$table->dropColumn(['allow_show','allow_create', 'allow_update', 'allow_destroy']);
});
}
};

View File

@@ -71,7 +71,7 @@ class UsersRoleMenuSeeder extends Seeder
];
foreach ($parent_menus as $parent_menu) {
Menu::create($parent_menu);
Menu::firstOrCreate(['name' => $parent_menu['name']], $parent_menu);
}
// Attach Menus to Roles
@@ -85,21 +85,7 @@ class UsersRoleMenuSeeder extends Seeder
$dataSettings = Menu::where('name', 'Data Settings')->first();
$data = Menu::where('name', 'Data')->first();
// Superadmin gets all menus
$superadmin->menus()->attach([$dashboard->id, $master->id, $settings->id, $dataSettings->id, $data->id]);
// Admin gets limited menus
$admin->menus()->attach([$dashboard->id, $master->id, $settings->id]);
// Operator gets only basic menus
$operator->menus()->attach([$dashboard->id, $data->id]);
// Attach User to role super admin
User::findOrFail(1)->roles()->attach($superadmin->id);
// create children menu
// dashboard children
$children_menus = [
[
"name" => "Dashboard Pimpinan",
@@ -143,13 +129,6 @@ class UsersRoleMenuSeeder extends Seeder
"parent_id" => $settings->id,
"sort_order" => 3,
],
[
"name" => "Assign Role Menu",
"url" => "roles.index",
"icon" => null,
"parent_id" => $settings->id,
"sort_order" => 4,
],
[
"name" => "Setting Dashboard",
"url" => "data-settings.index",
@@ -167,7 +146,48 @@ class UsersRoleMenuSeeder extends Seeder
];
foreach ($children_menus as $child_menu) {
Menu::create($child_menu);
Menu::firstOrCreate(['name' => $child_menu['name']], $child_menu);
}
$dashboard_pimpinan = Menu::where('name', 'Dashboard Pimpinan')->first();
$dashboard_pbg = Menu::where('name', 'Dashboard PBG')->first();
$users = Menu::where('name', 'Users')->first();
$syncronize = Menu::where('name', 'Syncronize')->first();
$setting_menu = Menu::where('name', 'Menu')->first();
$setting_role = Menu::where('name', 'Role')->first();
$setting_dashboard = Menu::where('name', 'Setting Dashboard')->first();
$setting_pbg = Menu::where('name', 'PBG')->first();
// Superadmin gets all menus
$superadmin->menus()->sync([
$dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$master->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$settings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dataSettings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dashboard_pimpinan->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$dashboard_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$users->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$syncronize->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$setting_menu->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$setting_role->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$setting_dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$setting_pbg->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]);
// Admin gets limited menus
$admin->menus()->sync([
$dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$master->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$settings->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]);
// Operator gets only basic menus with full permissions
$operator->menus()->sync([
$dashboard->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
$data->id => ["allow_show" => true, "allow_create" => true, "allow_update" => true, "allow_destroy" => true],
]);
// Attach User to role super admin
User::findOrFail(1)->roles()->sync([$superadmin->id]);
}
}

View File

@@ -18,10 +18,12 @@ class Roles {
name: "Action",
formatter: (cell) =>
gridjs.html(`
<div class="d-flex justify-content-end gap-x-2">
<a href="/roles/${cell}/edit" class="btn btn-yellow me-2">Update</a>
<button class="btn btn-red btn-delete btn-delete-role" data-id="${cell}">Delete</button>
`),
<div class="d-flex justify-content-end gap-x-2">
<a href="/roles/${cell}/edit" class="btn btn-yellow me-2">Update</a>
<a href="/roles/role-menu/${cell}" class="btn btn-yellow me-2">Role Menu</a>
<button class="btn btn-red btn-delete btn-delete-role" data-id="${cell}">Delete</button>
<div>
`),
},
],
pagination: {

View File

@@ -0,0 +1,36 @@
class RoleMenus {
init() {
this.initCheckboxRoles();
}
initCheckboxRoles() {
const childPermissions =
document.querySelectorAll(".child-permissions");
childPermissions.forEach((child) => {
child.addEventListener("change", function () {
const parentId = this.dataset.parentId;
const parentShow = document.querySelector(
`input[name='permissions[${parentId}][allow_show]']`
);
if (parentShow) {
// If any child permission is checked, check parent "Show"
if (
document.querySelectorAll(
`.child-permission[data-parent-id="${parentId}"]:checked`
).length > 0
) {
parentShow.checked = true;
} else {
parentShow.checked = false;
}
}
});
});
}
}
document.addEventListener("DOMContentLoaded", function (event) {
new RoleMenus().init();
});

View File

@@ -19,13 +19,15 @@
@foreach ($menus as $menu)
<li class="nav-item">
<!-- parent menu -->
<a class="nav-link menu-arrow" href="#sidebar-{{$menu->id}}" data-bs-toggle="collapse" role="button"
aria-expanded="true" aria-controls="sidebar-{{$menu->id}}">
<span class="nav-icon">
<iconify-icon icon="{{$menu->icon}}"></iconify-icon>
</span>
<span class="nav-text">{{$menu->name}}</span>
</a>
@if ($menu->parent_id == null)
<a class="nav-link menu-arrow" href="#sidebar-{{$menu->id}}" data-bs-toggle="collapse" role="button"
aria-expanded="true" aria-controls="sidebar-{{$menu->id}}">
<span class="nav-icon">
<iconify-icon icon="{{$menu->icon}}"></iconify-icon>
</span>
<span class="nav-text">{{$menu->name}}</span>
</a>
@endif
<!-- children menu foreach -->
@if ($menu->children->count() > 0)
<div class="collapse" id="sidebar-{{$menu->id}}">
@@ -33,8 +35,8 @@
@foreach ( $menu->children as $child)
<li class="sub-nav-item">
<a class="sub-nav-link" href="{{ $child->url ? (Route::has($child->url) ? route($child->url) : $child->url) : '#' }}">
{{ $child->name }}
</a>
{{ $child->name }}
</a>
</li>
@endforeach
</ul>

View File

@@ -1,9 +1,5 @@
@extends('layouts.vertical', ['subtitle' => 'Role'])
@section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
@endsection
@section('content')
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
@@ -32,6 +28,3 @@
</div>
@endsection
@section('scripts')
@endsection

View File

@@ -1,9 +1,5 @@
@extends('layouts.vertical', ['subtitle' => 'Role'])
@section('css')
@vite(['node_modules/gridjs/dist/theme/mermaid.min.css'])
@endsection
@section('content')
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
@@ -33,6 +29,3 @@
</div>
@endsection
@section('scripts')
@endsection

View File

@@ -0,0 +1,81 @@
@extends('layouts.vertical', ['subtitle' => 'Role'])
@section('content')
@include('layouts.partials/page-title', ['title' => 'Settings', 'subtitle' => 'Role'])
<div class="row d-flex justify-content-center">
<div class="col-md-12">
<div class="card">
<div class="card-body">
<h5>Manage Permissions for Role: {{ $role->name }}</h5>
<form action="{{route("role-menu.permission.update", $role->id)}}" method="post">
@csrf
@method("put")
<table class="table">
<thead>
<tr>
<th>Menu</th>
<th>Show</th>
<th>Create</th>
<th>Update</th>
<th>Destroy</th>
</tr>
</thead>
<tbody>
@foreach($menus->where('parent_id', null) as $parent)
@php
$role_menu = optional($role_menus->firstWhere('menu_id', $parent->id));
@endphp
<tr>
<td><strong>{{ $parent->name }}</strong></td>
<td>
<input type="checkbox" class="parent-show" name="permissions[{{ $parent->id }}][allow_show]" value="1"
{{ $role_menu && $role_menu->allow_show ? 'checked' : '' }} readonly>
</td>
<td colspan="3"></td>
</tr>
@foreach($menus->where('parent_id', $parent->id) as $child)
@php
$child_role_menu = optional($role_menus->firstWhere('menu_id', $child->id));
@endphp
<tr>
<td> {{ $child->name }}</td>
<td>
<input type="checkbox" class="child-permission" data-parent-id="{{ $parent->id }}"
name="permissions[{{ $child->id }}][allow_show]" value="1"
{{ $child_role_menu && $child_role_menu->allow_show ? 'checked' : '' }}>
</td>
<td>
<input type="checkbox" class="child-permission" data-parent-id="{{ $parent->id }}"
name="permissions[{{ $child->id }}][allow_create]" value="1"
{{ $child_role_menu && $child_role_menu->allow_create ? 'checked' : '' }}>
</td>
<td>
<input type="checkbox" class="child-permission" data-parent-id="{{ $parent->id }}"
name="permissions[{{ $child->id }}][allow_update]" value="1"
{{ $child_role_menu && $child_role_menu->allow_update ? 'checked' : '' }}>
</td>
<td>
<input type="checkbox" class="child-permission" data-parent-id="{{ $parent->id }}"
name="permissions[{{ $child->id }}][allow_destroy]" value="1"
{{ $child_role_menu && $child_role_menu->allow_destroy ? 'checked' : '' }}>
</td>
</tr>
@endforeach
@endforeach
</tbody>
</table>
<button type="submit" class="btn btn-success">Update</button>
</form>
</div>
</div>
</div>
</div>
@endsection
@section('scripts')
@vite(['resources/roles/role_menu.js'])
@endsection

View File

@@ -48,4 +48,8 @@ Route::group(['middleware' => 'auth'], function(){
// roles
Route::resource('/roles', RolesController::class);
Route::group(['prefix' => '/roles'], function (){
Route::get('/role-menu/{role_id}', [RolesController::class, 'menu_permission'])->name('role-menu.permission');
Route::put('/role-menu/{role_id}', [RolesController::class, 'update_menu_permission'])->name('role-menu.permission.update');
});
});

View File

@@ -54,6 +54,7 @@ export default defineConfig({
"resources/js/tables/common-table.js",
"resources/js/menus/index.js",
"resources/js/roles/index.js",
"resources/roles/role_menu.js",
],
refresh: true,
}),