- Introduced methods `selectAllPermissions` and `revokeAllPermissions` in Livewire component. - Updated permissions section UI with buttons for selecting and revoking all permissions. - Enhanced user experience by simplifying bulk permission management.
356 lines
13 KiB
PHP
356 lines
13 KiB
PHP
<?php
|
|
|
|
use App\Concerns\{Confirmation, HandlesOperations};
|
|
use App\Data\Ui\TableHeader;
|
|
use App\Models\User;
|
|
use App\Services\{UserService, RoleService};
|
|
use Livewire\Attributes\{Computed, Layout, Title};
|
|
use Livewire\Component;
|
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
|
use Spatie\LaravelData\{DataCollection, PaginatedDataCollection};
|
|
use App\Data\Role\{RoleData, RoleAppData};
|
|
use App\Livewire\Forms\AssignRolesForm;
|
|
use App\Data\Permissions\DeactivatedPermissionData;
|
|
|
|
new
|
|
#[Title('Users')]
|
|
class extends Component {
|
|
use HandlesOperations, Confirmation;
|
|
|
|
protected UserService $userService;
|
|
protected RoleService $roleService;
|
|
|
|
public AssignRolesForm $form;
|
|
public bool $showAssignRolesModal = false;
|
|
public int $currentUserId = 0;
|
|
public array $rolesSearchable = [];
|
|
|
|
#[DataCollectionOf(RoleData::class)]
|
|
public ?DataCollection $roleWithPermission = null;
|
|
|
|
#[DataCollectionOf(TableHeader::class)]
|
|
public DataCollection $headers;
|
|
|
|
public function boot(UserService $userService, RoleService $roleService): void
|
|
{
|
|
$this->userService = $userService;
|
|
$this->roleService = $roleService;
|
|
}
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->headers = TableHeader::collect([
|
|
new TableHeader(key: 'name', label: 'User'),
|
|
new TableHeader(key: 'roles', label: 'Roles'),
|
|
new TableHeader(key: 'permissions', label: 'Permissions'),
|
|
new TableHeader(key: 'apps', label: 'Connected Apps'),
|
|
], DataCollection::class);
|
|
}
|
|
|
|
#[Computed]
|
|
public function getUsers(): PaginatedDataCollection
|
|
{
|
|
return $this->userService->getAll();
|
|
}
|
|
|
|
public function delete(int $userId): void
|
|
{
|
|
$this->attempt(
|
|
action: function () use ($userId): void {
|
|
$this->userService->delete($userId);
|
|
unset($this->getUsers);
|
|
},
|
|
successMessage: 'User deleted successfully!',
|
|
);
|
|
}
|
|
|
|
public function openAssignRolesModal(int $userId): void
|
|
{
|
|
$this->attempt(
|
|
action: function () use ($userId) {
|
|
$this->currentUserId = $userId;
|
|
$this->form->reset();
|
|
$this->searchRoles();
|
|
$this->form->roleIds = User::findOrFail($userId)->roles()->pluck('id')->toArray();
|
|
$this->hydratePermissionInForm();
|
|
$this->showAssignRolesModal = true;
|
|
},
|
|
showSuccess: false
|
|
);
|
|
}
|
|
|
|
private function hydratePermissionInForm(): void
|
|
{
|
|
$this->roleWithPermission = $this->roleService->getPermissionsGroupedByRole($this->form->roleIds);
|
|
$allPermissionIds = [];
|
|
if ($this->roleWithPermission) {
|
|
foreach ($this->roleWithPermission->items() as $role) {
|
|
foreach ($role->permissions->items() as $permission) {
|
|
$allPermissionIds[] = $permission->id;
|
|
}
|
|
}
|
|
}
|
|
|
|
$restrictedIds = $this->userService->getRestrictedPermissionIds($this->currentUserId);
|
|
|
|
$this->form->permissions = array_values(array_diff($allPermissionIds, $restrictedIds));
|
|
}
|
|
|
|
/**
|
|
* Refresh the permission list whenever the roles selection changes.
|
|
* This method is automatically hooked by livewire cause of naming convention.
|
|
*/
|
|
public function updatedFormRoleIds(): void
|
|
{
|
|
$this->hydratePermissionInForm();
|
|
}
|
|
|
|
public function searchRoles(string $value = ''): void
|
|
{
|
|
$this->rolesSearchable = $this->roleService->searchRolesForSelect($value)->toArray();
|
|
}
|
|
|
|
public function selectAllRoles(): void
|
|
{
|
|
$this->form->roleIds = $this->roleService->getAllRoleIds();
|
|
$this->searchRoles();
|
|
$this->hydratePermissionInForm();
|
|
}
|
|
|
|
public function clearRoles(): void
|
|
{
|
|
$this->form->roleIds = [];
|
|
$this->hydratePermissionInForm();
|
|
}
|
|
|
|
public function selectAllPermissions(): void
|
|
{
|
|
$allPermissionIds = [];
|
|
if ($this->roleWithPermission) {
|
|
foreach ($this->roleWithPermission->items() as $role) {
|
|
foreach ($role->permissions->items() as $permission) {
|
|
$allPermissionIds[] = $permission->id;
|
|
}
|
|
}
|
|
}
|
|
$this->form->permissions = array_values(array_unique($allPermissionIds));
|
|
}
|
|
|
|
public function revokeAllPermissions(): void
|
|
{
|
|
$this->form->permissions = [];
|
|
}
|
|
|
|
/**
|
|
* Get the deactivated permissions represented as DTOs containing the role ID and permission ID.
|
|
*
|
|
* @return DataCollection<int, DeactivatedPermissionData>
|
|
*/
|
|
public function getDeactivatedPermissions(): DataCollection
|
|
{
|
|
$deactivated = [];
|
|
|
|
$activePermissions = array_map('intval', $this->form->permissions);
|
|
|
|
if ($this->roleWithPermission) {
|
|
foreach ($this->roleWithPermission->items() as $role) {
|
|
foreach ($role->permissions->items() as $permission) {
|
|
|
|
if (!in_array($permission->id, $activePermissions, true)) {
|
|
$deactivated[] = new DeactivatedPermissionData(
|
|
roleId: $role->id,
|
|
permissionId: $permission->id
|
|
);
|
|
}
|
|
|
|
}
|
|
}
|
|
}
|
|
|
|
return DeactivatedPermissionData::collect($deactivated, DataCollection::class);
|
|
}
|
|
|
|
public function saveRoles(): void
|
|
{
|
|
|
|
$this->attempt(
|
|
action: function () {
|
|
$this->form->validate();
|
|
$deactivatedPermissions = $this->getDeactivatedPermissions();
|
|
$this->userService->assignRoles($this->currentUserId, $this->form->roleIds);
|
|
$this->userService->syncRestrictedPermissions($this->currentUserId, $deactivatedPermissions);
|
|
$this->showAssignRolesModal = false;
|
|
$this->roleWithPermission = null;
|
|
$this->form->reset();
|
|
unset($this->getUsers);
|
|
},
|
|
onError: function () {
|
|
$this->showAssignRolesModal = false;
|
|
$this->roleWithPermission = null;
|
|
$this->form->reset();
|
|
},
|
|
successMessage: 'Roles updated successfully!'
|
|
);
|
|
}
|
|
};
|
|
?>
|
|
|
|
<div>
|
|
<x-shared.card title="Users" icon="lucide-users" class="pb-2">
|
|
<x-mary-table
|
|
:headers="$headers->toArray()"
|
|
:rows="$this->getUsers->items()"
|
|
>
|
|
@scope('cell_name', $row)
|
|
<div class="flex items-center gap-3">
|
|
<x-mary-avatar :placeholder="$row->initials" class="bg-blue-100 text-blue-600"/>
|
|
<div>
|
|
<div class="font-medium text-gray-900">{{ $row->name }}</div>
|
|
<div class="text-sm text-gray-500">{{ $row->email }}</div>
|
|
</div>
|
|
</div>
|
|
@endscope
|
|
|
|
@scope('cell_roles', $row)
|
|
<div class="flex flex-wrap gap-1">
|
|
@foreach($row->roles as $role)
|
|
<x-mary-badge class="badge-soft badge-neutral" :value="ucfirst($role->name)"/>
|
|
@endforeach
|
|
</div>
|
|
@endscope
|
|
|
|
@scope('cell_permissions', $row)
|
|
<div class="flex flex-wrap gap-1">
|
|
@php
|
|
$displayedPermissions = [];
|
|
@endphp
|
|
@foreach($row->roles as $role)
|
|
@if($role->permissions)
|
|
@foreach($role->permissions as $permission)
|
|
@if(!in_array($permission->id, $displayedPermissions))
|
|
@php
|
|
$displayedPermissions[] = $permission->id;
|
|
$isRestricted = in_array($permission->id, $row->restrictedPermissionIds ?? []);
|
|
@endphp
|
|
@if($isRestricted)
|
|
<x-mary-badge class="bg-base-300 text-base-content/50 line-through"
|
|
:value="$permission->name"/>
|
|
@else
|
|
<x-mary-badge class="badge-soft badge-primary" :value="$permission->name"/>
|
|
@endif
|
|
@endif
|
|
@endforeach
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endscope
|
|
|
|
@scope('cell_apps', $row)
|
|
<div class="flex flex-wrap gap-1">
|
|
@foreach($row->roles as $role)
|
|
@if($role->apps)
|
|
@foreach ($role->apps as $app)
|
|
<x-mary-badge class="badge-soft badge-secondary" :value="$app->appName"/>
|
|
@endforeach
|
|
@endif
|
|
@endforeach
|
|
</div>
|
|
@endscope
|
|
|
|
@scope('actions', $row)
|
|
<div class="flex gap-1">
|
|
<x-mary-button
|
|
icon="lucide.shield-plus"
|
|
wire:click="openAssignRolesModal({{ $row->id }})"
|
|
tooltip="Manage roles"
|
|
class="btn-sm btn-circle btn-soft"
|
|
spinner="openAssignRolesModal({{ $row->id }})"
|
|
/>
|
|
<x-mary-button
|
|
icon="lucide.trash"
|
|
wire:click="requireConfirmation('delete', {{ $row->id }});"
|
|
spinner="requireConfirmation('delete', {{ $row->id }});"
|
|
variant="danger"
|
|
:tooltip="__('Delete user')"
|
|
class="btn-sm btn-circle btn-error btn-soft"
|
|
/>
|
|
</div>
|
|
@endscope
|
|
</x-mary-table>
|
|
</x-shared.card>
|
|
|
|
<x-mary-modal wire:model="showAssignRolesModal" title="Assign Roles">
|
|
<x-mary-form wire:submit="saveRoles">
|
|
<x-shared.choices
|
|
wire:model.live="form.roleIds"
|
|
:options="$rolesSearchable"
|
|
search-function="searchRoles"
|
|
select-all-action="selectAllRoles"
|
|
clear-action="clearRoles"
|
|
label="Roles"
|
|
/>
|
|
|
|
@if ($roleWithPermission)
|
|
<fieldset class="mt-5 space-y-4 w-full">
|
|
<legend class="flex w-full items-center justify-between gap-4">
|
|
<p class="text-xs font-bold">Permissions</p>
|
|
<div class="flex items-center gap-2 font-bold">
|
|
|
|
<label class="flex cursor-pointer items-center gap-1 text-xs text-primary">
|
|
<button
|
|
type="button"
|
|
wire:click="selectAllPermissions()"
|
|
>
|
|
Select All
|
|
</button>
|
|
</label>
|
|
<label class="flex cursor-pointer items-center gap-1 text-xs text-error">
|
|
<button
|
|
type="button"
|
|
class="text-error"
|
|
wire:click="revokeAllPermissions()"
|
|
>
|
|
Revoke All
|
|
</button>
|
|
</label>
|
|
|
|
</div>
|
|
</legend>
|
|
|
|
@foreach ($roleWithPermission?->items() as $role)
|
|
<div class="rounded-xl border border-base-300 bg-base-200/50 p-4">
|
|
<div class="mb-3 flex items-center gap-2">
|
|
<span
|
|
class="inline-flex items-center rounded-md bg-primary/10 px-2 py-0.5 text-xs font-medium text-primary ring-1 ring-inset ring-primary/20">
|
|
{{ $role->name }}
|
|
</span>
|
|
<span
|
|
class="text-xs text-base-content/50">{{$role->permissions->count()}} {{ $role->permissions->count() >= 2 ? 'Permissions' : 'Permission' }}</span>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 gap-2 sm:grid-cols-2">
|
|
@foreach ($role->permissions as $permission)
|
|
<x-mary-toggle
|
|
class="toggle-primary"
|
|
:label="$permission->name" wire:model="form.permissions"
|
|
:value="$permission->id"
|
|
/>
|
|
@endforeach
|
|
</div>
|
|
</div>
|
|
@endforeach
|
|
</fieldset>
|
|
@endif
|
|
|
|
<x-slot:actions>
|
|
<x-mary-button @click="$wire.showAssignRolesModal = false" class="btn-ghost">
|
|
Cancel
|
|
</x-mary-button>
|
|
<x-mary-button type="submit" spinner="saveRoles" class="btn-primary">
|
|
Save Roles
|
|
</x-mary-button>
|
|
</x-slot:actions>
|
|
</x-mary-form>
|
|
</x-mary-modal>
|
|
</div>
|