diff --git a/app/Livewire/RoleManager.php b/app/Livewire/RoleManager.php new file mode 100644 index 0000000..dd641e2 --- /dev/null +++ b/app/Livewire/RoleManager.php @@ -0,0 +1,174 @@ +can(self::CORE_PERMISSION), 403); + } + + private function flushCache(): void + { + app(PermissionRegistrar::class)->forgetCachedPermissions(); + } + + public function updatedSelectAll($value): void + { + $this->selected = $value + ? Role::pluck('id')->map(fn ($id) => (string) $id)->toArray() + : []; + } + + // ── Create / Edit ──────────────────────────────────────────────────────── + + public function openCreate(): void + { + $this->resetForm(); + $this->showForm = true; + } + + public function openEdit(int $id): void + { + $role = Role::with('permissions')->findOrFail($id); + $this->editingRole = $role->id; + $this->name = $role->name; + $this->description = $role->description ?? ''; + $this->rolePermissions = $role->permissions->pluck('name')->toArray(); + $this->resetErrorBag(); + $this->viewingRole = null; // close the view modal if open + $this->showForm = true; + } + + public function save(): void + { + $this->validate([ + 'name' => 'required|string|max:50|unique:roles,name' . ($this->editingRole ? ',' . $this->editingRole : ''), + 'description' => 'nullable|string|max:255', + ], [], ['name' => 'nombre', 'description' => 'descripción']); + + if ($this->editingRole) { + $role = Role::findOrFail($this->editingRole); + $isProtected = in_array($role->name, self::PROTECTED_ROLES, true); + + // Protected roles can't be renamed + if (! $isProtected) { + $role->name = $this->name; + } + $role->description = $this->description ?: null; + $role->save(); + + $perms = $this->rolePermissions; + // Admin always keeps the core permission + if ($role->name === 'Admin' && ! in_array(self::CORE_PERMISSION, $perms, true)) { + $perms[] = self::CORE_PERMISSION; + } + $role->syncPermissions($perms); + } else { + $role = Role::create([ + 'name' => $this->name, + 'description' => $this->description ?: null, + ]); + if (! empty($this->rolePermissions)) { + $role->syncPermissions($this->rolePermissions); + } + } + + $this->flushCache(); + $this->closeForm(); + $this->dispatch('notify', 'Rol guardado correctamente'); + } + + public function closeForm(): void + { + $this->showForm = false; + $this->resetForm(); + } + + private function resetForm(): void + { + $this->reset(['editingRole', 'name', 'description', 'rolePermissions']); + $this->resetErrorBag(); + } + + // ── View ───────────────────────────────────────────────────────────────── + + public function openView(int $id): void + { + $this->viewingRole = $id; + } + + public function closeView(): void + { + $this->viewingRole = null; + } + + // ── Delete (single / bulk) ───────────────────────────────────────────────── + + public function delete(int $id): void + { + $role = Role::findOrFail($id); + if (in_array($role->name, self::PROTECTED_ROLES, true)) { + $this->dispatch('notify', "El rol '{$role->name}' está protegido y no se puede borrar."); + return; + } + $role->delete(); + $this->selected = array_values(array_diff($this->selected, [(string) $id, $id])); + $this->flushCache(); + $this->dispatch('notify', 'Rol eliminado'); + } + + public function bulkDelete(): void + { + $roles = Role::whereIn('id', $this->selected)->get(); + $deleted = 0; + $skipped = 0; + foreach ($roles as $role) { + if (in_array($role->name, self::PROTECTED_ROLES, true)) { $skipped++; continue; } + $role->delete(); + $deleted++; + } + $this->selected = []; + $this->flushCache(); + $msg = "{$deleted} rol(es) eliminados"; + if ($skipped) $msg .= " ({$skipped} protegido(s) omitido(s))"; + $this->dispatch('notify', $msg); + } + + public function render() + { + return view('livewire.role-manager', [ + 'roles' => Role::with('permissions')->withCount('users')->orderBy('name')->get(), + 'permissions' => Permission::orderBy('name')->get(), + 'viewing' => $this->viewingRole + ? Role::with('permissions')->withCount('users')->find($this->viewingRole) + : null, + ]); + } +} diff --git a/database/migrations/2026_06_17_170000_add_description_to_roles_table.php b/database/migrations/2026_06_17_170000_add_description_to_roles_table.php new file mode 100644 index 0000000..bfdcf51 --- /dev/null +++ b/database/migrations/2026_06_17_170000_add_description_to_roles_table.php @@ -0,0 +1,30 @@ +getTable(), 'description')) { + $table->string('description')->nullable()->after('name'); + } + }); + } + + public function down(): void + { + $table = config('permission.table_names.roles', 'roles'); + + Schema::table($table, function (Blueprint $table) { + if (Schema::hasColumn($table->getTable(), 'description')) { + $table->dropColumn('description'); + } + }); + } +}; diff --git a/resources/views/admin/users.blade.php b/resources/views/admin/users.blade.php index 443d252..c9ac290 100644 --- a/resources/views/admin/users.blade.php +++ b/resources/views/admin/users.blade.php @@ -6,8 +6,8 @@
{{ __('Manage role groups and the permissions assigned to each.') }}
+| + + | +{{ __('Name') }} | +{{ __('Description') }} | +{{ __('Permissions') }} | +{{ __('Users') }} | +{{ __('Actions') }} | +
|---|---|---|---|---|---|
| + + | ++ {{ $role->name }} + @if(in_array($role->name, ['Admin'], true)) + {{ __('protected') }} + @endif + | +{{ $role->description ?: '—' }} | ++ {{ $role->permissions->count() }} + | ++ {{ $role->users_count }} + | +
+
+
+
+ @unless(in_array($role->name, ['Admin'], true))
+
+ @endunless
+
+ |
+
| {{ __('No roles') }} | |||||
{{ $viewing->description ?: __('No description') }}
+{{ $viewing->users_count }} {{ __('users') }} · {{ $viewing->permissions->count() }} {{ __('permissions') }}
+ +{{ __('No permissions') }}
+ @else +