Add Module Role

pull/2/head
sundayenglish 4 weeks ago
parent 8ca9f346d9
commit 157d805555
  1. 70
      app/Components/Role/Manager.php
  2. 1
      app/Http/Controllers/DashboardController.php
  3. 1
      resources/views/admin/dashboard.blade.php
  4. 6
      resources/views/components/permission/manager.blade.php
  5. 142
      resources/views/components/role/manager.blade.php

@ -6,51 +6,30 @@ use Livewire\Component;
use Livewire\Attributes\On; use Livewire\Attributes\On;
use Livewire\WithPagination; use Livewire\WithPagination;
use Spatie\Permission\Models\Role; use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class Manager extends Component class Manager extends Component
{ {
use WithPagination; use WithPagination;
/**
* Các tùy chọn số bản ghi mỗi trang.
*
* @var int[]
*/
public array $perPageOptions = [5, 10, 25, 50, 100]; public array $perPageOptions = [5, 10, 25, 50, 100];
/**
* Số bản ghi mỗi trang (mặc định).
*/
public int $perPage = 10; public int $perPage = 10;
/**
* Giao diện phân trang (bootstrap hoặc tailwind).
*/
protected string $paginationTheme = 'bootstrap'; protected string $paginationTheme = 'bootstrap';
/**
* Chế độ UI: 'index' (danh sách) hoặc 'form' (tạo/sửa).
*/
public string $mode = 'index'; public string $mode = 'index';
/**
* ID của role đang sửa (null khi tạo mới).
*/
public ?int $editingId = null; public ?int $editingId = null;
/**
* Chuỗi tìm kiếm hiện tại.
*/
public string $search = ''; public string $search = '';
/**
* Tên role dùng trong form.
*/
public string $name = ''; public string $name = '';
/** public array $allPermissions = [];
* Khi thay đổi số bản ghi mỗi trang, reset về trang 1. public array $assignedPermissions = [];
*/
public function mount()
{
$this->allPermissions = Permission::orderBy('name')
->get(['id', 'name'])
->toArray();
}
public function updatedPerPage(int $value): void public function updatedPerPage(int $value): void
{ {
$this->resetPage(); $this->resetPage();
@ -63,6 +42,7 @@ class Manager extends Component
$this->resetPage(); $this->resetPage();
$this->name = ''; $this->name = '';
$this->editingId = null; $this->editingId = null;
$this->assignedPermissions = [];
} }
public function updatedSearch(string $value): void public function updatedSearch(string $value): void
@ -74,11 +54,23 @@ class Manager extends Component
{ {
$this->mode = 'form'; $this->mode = 'form';
$this->editingId = $id; $this->editingId = $id;
$this->name = $id
? Role::findOrFail($id)->name if ($id) {
: ''; $role = Role::findOrFail($id);
$this->name = $role->name;
$this->assignedPermissions = $role
->permissions
->pluck('id')
->toArray();
} else {
$this->name = '';
$this->assignedPermissions = [];
}
} }
/**
* Delete a role by its ID.
*/
public function delete(int $id): void public function delete(int $id): void
{ {
Role::findOrFail($id)->delete(); Role::findOrFail($id)->delete();
@ -93,13 +85,17 @@ class Manager extends Component
$this->validate([ $this->validate([
'name' => "required|string|{$uniqueRule}", 'name' => "required|string|{$uniqueRule}",
'assignedPermissions.*' => 'integer|exists:permissions,id',
]); ]);
Role::updateOrCreate( $role = Role::updateOrCreate(
['id' => $this->editingId], ['id' => $this->editingId],
['name' => $this->name] ['name' => $this->name]
); );
// Sync permissions theo ID
$role->permissions()->sync($this->assignedPermissions);
session()->flash('message', 'Role saved.'); session()->flash('message', 'Role saved.');
$this->dispatch('roleSaved'); $this->dispatch('roleSaved');
} }
@ -124,7 +120,7 @@ class Manager extends Component
public function render() public function render()
{ {
$roles = Role::query() $roles = Role::with('permissions') // ← Eager-load permissions
->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%")) ->when($this->search, fn($q) => $q->where('name', 'like', "%{$this->search}%"))
->orderByDesc('id') ->orderByDesc('id')
->paginate($this->perPage); ->paginate($this->perPage);

@ -7,7 +7,6 @@ class DashboardController extends Controller
{ {
public function index() public function index()
{ {
// Trả về Blade wrapper, Livewire component sẽ được gọi trong đó
return view('admin.dashboard'); return view('admin.dashboard');
} }
} }

@ -3,6 +3,5 @@
@section('title', 'Dashboard') @section('title', 'Dashboard')
@section('content') @section('content')
{{-- Đây chính là chỗ Livewire inject component --}}
<livewire:dashboard /> <livewire:dashboard />
@endsection @endsection

@ -11,7 +11,7 @@
@if ($mode === 'index') @if ($mode === 'index')
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
{{-- 1. Dropdown chọn số bản ghi --}} {{-- 1. Dropdown --}}
<div class="me-2"> <div class="me-2">
<select wire:model="perPage" wire:change="resetPage" class="form-select form-select-sm" <select wire:model="perPage" wire:change="resetPage" class="form-select form-select-sm"
style="width: 70px; display:inline-block;"> style="width: 70px; display:inline-block;">
@ -74,7 +74,7 @@
</button> </button>
<button x-data <button x-data
@click.prevent=" @click.prevent="
if (confirm('Bạn có chắc chắn muốn xóa permission này?')) { if (confirm('Are you sure you want to delete this permission?')) {
$wire.delete({{ $p->id }}) $wire.delete({{ $p->id }})
} }
" "
@ -119,7 +119,7 @@
</button> </button>
<button type="button" <button type="button"
@click.prevent=" @click.prevent="
if (confirm('Bạn có chắc chắn muốn {{ $editingId ? 'cập nhật' : 'tạo' }} permission này?')) { if (confirm('Are you sure you want to {{ $editingId ? 'update' : 'create' }} this permission?')) {
$wire.save() $wire.save()
} }
" "

@ -1,5 +1,3 @@
{{-- resources/views/livewire/role-manager.blade.php --}}
<div class="row"> <div class="row">
<div class="col-12"> <div class="col-12">
<div class="card mb-4"> <div class="card mb-4">
@ -10,51 +8,30 @@
@if ($mode === 'index') @if ($mode === 'index')
<div class="d-flex align-items-center"> <div class="d-flex align-items-center">
{{-- dropdown perPage --}}
{{-- 1. Dropdown chọn số bản ghi --}}
<div class="me-2"> <div class="me-2">
<select <select wire:model="perPage" wire:change="resetPage" class="form-select form-select-sm"
wire:model="perPage" style="width:100px; display:inline-block;">
wire:change="resetPage"
class="form-select form-select-sm"
style="width: 100px; display:inline-block;"
>
@foreach ($perPageOptions as $opt) @foreach ($perPageOptions as $opt)
<option value="{{ $opt }}">{{ $opt }}</option> <option value="{{ $opt }}">{{ $opt }}</option>
@endforeach @endforeach
</select> </select>
</div> </div>
{{-- search --}}
{{-- 2. Search box --}}
<div class="input-group input-group-outline me-2"> <div class="input-group input-group-outline me-2">
<input <input type="text" wire:model.debounce.500ms="search" wire:keydown.enter="resetPage"
type="text" class="form-control" placeholder="Search...">
wire:model.debounce.500ms="search" </div>
wire:keydown.enter="resetPage" <button wire:click="resetPage" class="btn btn-sm btn-outline-success me-2 m-0">
class="form-control"
placeholder="Search..."
>
</div>
<button
wire:click="resetPage"
class="btn btn-sm btn-outline-success me-2 m-0"
>
<i class="fas fa-search me-1" style="font-size:1.2rem;"></i> <i class="fas fa-search me-1" style="font-size:1.2rem;"></i>
</button> </button>
{{-- new --}}
{{-- 3. Create button --}} <button wire:click="showForm" class="btn btn-sm btn-info m-0">
<button
wire:click="showForm"
class="btn btn-sm btn-info m-0"
>
Create Create
</button> </button>
</div> </div>
@else @else
<button <button wire:click="showIndex" class="btn btn-sm btn-secondary">
wire:click="showIndex"
class="btn btn-sm btn-secondary"
>
← Back to list ← Back to list
</button> </button>
@endif @endif
@ -67,103 +44,114 @@
@endif @endif
@if ($mode === 'index') @if ($mode === 'index')
{{-- SKELETON TABLE --}} {{-- index --}}
<x-skeleton-table :columns="3" :rows="5" height="3.5rem" /> <x-skeleton-table :columns="4" :rows="5" height="3.5rem" />
<div wire:loading.remove class="table-responsive">
{{-- ACTUAL TABLE --}} <table class="table mb-0 align-middle">
<div wire:loading.remove class="table-responsive w-100">
<table class="table align-items-center mb-0 w-100">
<thead> <thead>
<tr style="height:3.5rem"> <tr style="height:3.5rem">
<th class="text-center">ID</th> <th class="text-center">ID</th>
<th>Name</th> <th>Name</th>
<th>Permissions</th> {{-- cột mới --}}
<th class="text-center">Actions</th> <th class="text-center">Actions</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
@forelse($roles as $r) @forelse($roles as $r)
<tr style="height:3.5rem"> <tr style="height:3.5rem">
<td class="text-center align-middle">{{ $r->id }}</td> <td class="text-center">{{ $r->id }}</td>
<td class="align-middle">{{ $r->name }}</td> <td>{{ $r->name }}</td>
<td class="text-center align-middle"> <td>
<button @forelse($r->permissions as $p)
wire:click="showForm({{ $r->id }})" <span class="badge bg-secondary me-1">{{ $p->name }}</span>
class="btn btn-sm btn-outline-info me-1" @empty
> <span class="text-muted"></span>
@endforelse
</td>
<td class="text-center">
<button wire:click="showForm({{ $r->id }})"
class="btn btn-sm btn-outline-info me-1">
Edit Edit
</button> </button>
<button <button x-data
x-data
@click.prevent=" @click.prevent="
if (confirm('Bạn có chắc chắn muốn xóa role này?')) { if (confirm('Delete role?')) {
$wire.delete({{ $r->id }}) $wire.delete({{ $r->id }})
} }
" "
class="btn btn-sm btn-danger" class="btn btn-sm btn-danger">
>
Delete Delete
</button> </button>
</td> </td>
</tr> </tr>
@empty @empty
<tr> <tr>
<td colspan="3" class="text-center">No roles found.</td> <td colspan="4" class="text-center">No roles found.</td>
</tr> </tr>
@endforelse @endforelse
</tbody> </tbody>
</table> </table>
</div> </div>
{{-- Pagination --}}
<div wire:loading.remove class="mt-3"> <div wire:loading.remove class="mt-3">
@if ($roles->lastPage() > 1) @if ($roles->lastPage() > 1)
{{ $roles->links() }} {{ $roles->links() }}
@endif @endif
</div> </div>
@else @else
{{-- SKELETON FORM --}} {{-- form edit/create --}}
<x-skeleton-form :fields="1" :button-count="2" /> <x-skeleton-form :fields="1" :button-count="2" />
<div wire:loading.remove>
{{-- ACTUAL FORM --}}
<div wire:loading.remove x-data>
<form> <form>
<div class="mb-3"> <div class="mb-3">
<label class="form-label">Name</label> <label class="form-label">Name</label>
<input <input type="text" wire:model.defer="name" class="form-control"
type="text" placeholder="Role name">
wire:model.defer="name"
class="form-control"
placeholder="Enter role name"
>
@error('name') @error('name')
<div class="text-danger text-xs mt-1">{{ $message }}</div> <div class="text-danger small">{{ $message }}</div>
@enderror @enderror
</div> </div>
{{-- checkbox list permissions --}}
<div class="mb-3">
<label class="form-label">Permissions</label>
<div class="row">
@foreach ($allPermissions as $perm)
<div class="col-6 col-md-3">
<div class="form-check">
<input class="form-check-input" type="checkbox"
wire:model="assignedPermissions" value="{{ $perm['id'] }}"
id="perm-{{ $perm['id'] }}">
<label class="form-check-label small" for="perm-{{ $perm['id'] }}">
{{ $perm['name'] }}
</label>
</div>
</div>
@endforeach
</div>
@error('assignedPermissions')
<div class="text-danger small mt-1">{{ $message }}</div>
@enderror
</div>
<div class="d-flex justify-content-end"> <div class="d-flex justify-content-end">
<button <button type="button" wire:click="showIndex" class="btn btn-secondary btn-sm me-2">
wire:click="showIndex"
type="button"
class="btn btn-secondary btn-sm me-2"
>
Cancel Cancel
</button> </button>
<button <button type="button"
type="button"
@click.prevent=" @click.prevent="
if (confirm('Bạn có chắc chắn muốn {{ $editingId ? 'cập nhật' : 'tạo' }} role này?')) { if (confirm('Do you really want to {{ $editingId ? 'update' : 'create' }} this role?')) {
$wire.save() $wire.save()
} }
" "
class="btn btn-info btn-sm" class="btn btn-primary btn-sm">
>
{{ $editingId ? 'Update' : 'Create' }} {{ $editingId ? 'Update' : 'Create' }}
</button> </button>
</div> </div>
</form> </form>
</div> </div>
@endif @endif
</div> </div>
</div> </div>
</div> </div>

Loading…
Cancel
Save