feat(roles/users): add-user form on role view + per-user direct permissions form

1. Role view (Details tab): a small form to add users to the role (select of
   users not yet in the role + Add) and a per-row remove button. Uses
   assignRole/removeRole.
2. User view (Permissions tab): the same grouped, collapsible permissions form
   with switches — operating on the user's DIRECT permissions
   (givePermissionTo/revokePermissionTo). Permissions inherited from a role show
   as checked+disabled with a 'from role' tag; per-group All/None too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 18:51:59 +02:00
parent 7f20399337
commit 0120c4bfb8
7 changed files with 188 additions and 33 deletions
+27 -4
View File
@@ -6,6 +6,7 @@ use Livewire\Component;
use Livewire\Attributes\Layout;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Str;
use App\Models\User;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
use Spatie\Permission\PermissionRegistrar;
@@ -15,6 +16,7 @@ class RoleView extends Component
{
public Role $role;
public string $tab = 'ficha'; // ficha | permisos
public $newUserId = '';
private const PROTECTED_ROLES = ['Admin'];
private const CORE_PERMISSION = 'manage all';
@@ -51,6 +53,23 @@ class RoleView extends Component
$this->dispatch('notify', 'Permisos actualizados');
}
public function addUser(): void
{
$this->validate(['newUserId' => 'required|exists:users,id'], [], ['newUserId' => 'usuario']);
User::findOrFail($this->newUserId)->assignRole($this->role->name);
$this->newUserId = '';
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->dispatch('notify', 'Usuario añadido al rol');
}
public function removeUser(int $userId): void
{
User::findOrFail($userId)->removeRole($this->role->name);
app(PermissionRegistrar::class)->forgetCachedPermissions();
$this->dispatch('notify', 'Usuario quitado del rol');
}
public function setGroup(string $group, bool $enabled): void
{
$names = Permission::where('group', $group)->pluck('name');
@@ -110,11 +129,15 @@ class RoleView extends Component
return $i === false ? 999 : $i;
});
$availableUsers = User::whereDoesntHave('roles', fn ($q) => $q->where('roles.id', $this->role->id))
->orderBy('first_name')->orderBy('name')->get();
return view('livewire.roles.role-view', [
'users' => $users,
'grouped' => $grouped,
'rolePerms' => $this->role->permissions->pluck('name')->toArray(),
'isProtected' => in_array($this->role->name, self::PROTECTED_ROLES, true),
'users' => $users,
'availableUsers' => $availableUsers,
'grouped' => $grouped,
'rolePerms' => $this->role->permissions->pluck('name')->toArray(),
'isProtected' => in_array($this->role->name, self::PROTECTED_ROLES, true),
]);
}
}