Add Module Role

master
sundayenglish 4 weeks ago
parent 8ca9f346d9
commit 157d805555
  1. 88
      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. 152
      resources/views/components/role/manager.blade.php
  6. 30
      resources/views/components/skeleton-form.blade.php
  7. 44
      resources/views/components/skeleton-table.blade.php

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

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

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

@ -11,7 +11,7 @@
@if ($mode === 'index')
<div class="d-flex align-items-center">
{{-- 1. Dropdown chọn số bản ghi --}}
{{-- 1. Dropdown --}}
<div class="me-2">
<select wire:model="perPage" wire:change="resetPage" class="form-select form-select-sm"
style="width: 70px; display:inline-block;">
@ -74,7 +74,7 @@
</button>
<button x-data
@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 }})
}
"
@ -119,7 +119,7 @@
</button>
<button type="button"
@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()
}
"

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

@ -1,22 +1,22 @@
@props(['fields', 'buttonCount'])
<div wire:loading class="w-100">
<div class="p-4 border rounded-lg shadow-sm max-w-md mx-auto space-y-4">
{{-- Title --}}
<div class="skeleton rounded" style="width:10rem; height:2rem;"></div>
<div class="p-4 border rounded-lg shadow-sm max-w-md mx-auto space-y-4">
{{-- Title --}}
<div class="skeleton rounded" style="width:10rem; height:2rem;"></div>
{{-- Fields --}}
<div class="space-y-3 mt-2">
@for ($i = 0; $i < $fields; $i++)
<div class="skeleton rounded" style="width:100%; height:3.5rem;"></div>
@endfor
</div>
{{-- Fields --}}
<div class="space-y-3 mt-2">
@for ($i = 0; $i < $fields; $i++)
<div class="skeleton rounded" style="width:100%; height:3.5rem;"></div>
@endfor
</div>
{{-- Buttons --}}
<div class="d-flex justify-content-end gap-3 mt-2">
@for ($i = 0; $i < $buttonCount; $i++)
<div class="skeleton rounded" style="width:5.5rem; height:2.5rem;"></div>
@endfor
{{-- Buttons --}}
<div class="d-flex justify-content-end gap-3 mt-2">
@for ($i = 0; $i < $buttonCount; $i++)
<div class="skeleton rounded" style="width:5.5rem; height:2.5rem;"></div>
@endfor
</div>
</div>
</div>
</div>

@ -1,26 +1,26 @@
@props(['columns', 'rows', 'height'])
<div wire:loading class="w-100">
<div class="table-responsive w-100">
<table class="table mb-0 w-100">
<thead>
<tr style="visibility:hidden; height:{{ $height }}">
@for ($i = 0; $i < $columns; $i++)
<th>&nbsp;</th>
@endfor
</tr>
</thead>
<tbody>
@for ($r = 0; $r < $rows; $r++)
<tr style="height:{{ $height }}">
@for ($c = 0; $c < $columns; $c++)
<td class="align-middle">
<div class="skeleton rounded w-100" style="height:1.75rem"></div>
</td>
@endfor
</tr>
@endfor
</tbody>
</table>
</div>
<div class="table-responsive w-100">
<table class="table mb-0 w-100">
<thead>
<tr style="visibility:hidden; height:{{ $height }}">
@for ($i = 0; $i < $columns; $i++)
<th>&nbsp;</th>
@endfor
</tr>
</thead>
<tbody>
@for ($r = 0; $r < $rows; $r++)
<tr style="height:{{ $height }}">
@for ($c = 0; $c < $columns; $c++)
<td class="align-middle">
<div class="skeleton rounded w-100" style="height:1.75rem"></div>
</td>
@endfor
</tr>
@endfor
</tbody>
</table>
</div>
</div>

Loading…
Cancel
Save